Compare commits

..

1 Commits

Author SHA1 Message Date
Spring Builds e6585e0250 Release v6.1.0 2023-11-16 14:21:14 +00:00
3018 changed files with 33702 additions and 47822 deletions
@@ -1,33 +0,0 @@
name: Send notification
description: Sends a Google Chat message as a notification of the job's outcome
inputs:
webhook-url:
description: 'Google Chat Webhook URL'
required: true
status:
description: 'Status of the job'
required: true
build-scan-url:
description: 'URL of the build scan to include in the notification'
run-name:
description: 'Name of the run to include in the notification'
default: ${{ format('{0} {1}', github.ref_name, github.job) }}
runs:
using: composite
steps:
- shell: bash
run: |
echo "BUILD_SCAN=${{ inputs.build-scan-url == '' && ' [build scan unavailable]' || format(' [<{0}|Build Scan>]', inputs.build-scan-url) }}" >> "$GITHUB_ENV"
echo "RUN_URL=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" >> "$GITHUB_ENV"
- shell: bash
if: ${{ inputs.status == 'success' }}
run: |
curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: "<${{ env.RUN_URL }}|${{ inputs.run-name }}> was successful ${{ env.BUILD_SCAN }}"}' || true
- shell: bash
if: ${{ inputs.status == 'failure' }}
run: |
curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: "<users/all> *<${{ env.RUN_URL }}|${{ inputs.run-name }}> failed* ${{ env.BUILD_SCAN }}"}' || true
- shell: bash
if: ${{ inputs.status == 'cancelled' }}
run: |
curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: "<${{ env.RUN_URL }}|${{ inputs.run-name }}> was cancelled"}' || true
+4 -6
View File
@@ -18,13 +18,11 @@ jobs:
pull-requests: write
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v4
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
with:
distribution: 'liberica'
java-version: 17
distribution: 'temurin'
java-version: '17'
- name: Download BackportBot
run: wget https://github.com/spring-io/backport-bot/releases/download/latest/backport-bot-0.0.1-SNAPSHOT.jar
- name: Backport
@@ -1,64 +0,0 @@
name: Build and deploy snapshot
on:
push:
branches:
- 6.1.x
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
build-and-deploy-snapshot:
if: ${{ github.repository == 'spring-projects/spring-framework' }}
name: Build and deploy snapshot
runs-on: ubuntu-latest
steps:
- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: 'liberica'
java-version: 17
- name: Check out code
uses: actions/checkout@v4
- name: Set up Gradle
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5
with:
cache-read-only: false
- name: Configure Gradle properties
shell: bash
run: |
mkdir -p $HOME/.gradle
echo 'systemProp.user.name=spring-builds+github' >> $HOME/.gradle/gradle.properties
echo 'systemProp.org.gradle.internal.launcher.welcomeMessageEnabled=false' >> $HOME/.gradle/gradle.properties
echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties
echo 'org.gradle.daemon=4' >> $HOME/.gradle/gradle.properties
- name: Build and publish
id: build
env:
CI: 'true'
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
run: ./gradlew -PdeploymentRepository=$(pwd)/deployment-repository build publishAllPublicationsToDeploymentRepository
- name: Deploy
uses: spring-io/artifactory-deploy-action@v0.0.1
with:
uri: 'https://repo.spring.io'
username: ${{ secrets.ARTIFACTORY_USERNAME }}
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
build-name: ${{ format('spring-framework-{0}', github.ref_name)}}
repository: 'libs-snapshot-local'
folder: 'deployment-repository'
signing-key: ${{ secrets.GPG_PRIVATE_KEY }}
signing-passphrase: ${{ secrets.GPG_PASSPHRASE }}
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
- name: Send notification
uses: ./.github/actions/send-notification
if: always()
with:
webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
status: ${{ job.status }}
build-scan-url: ${{ steps.build.outputs.build-scan-url }}
run-name: ${{ format('{0} | Linux | Java 17', github.ref_name) }}
-80
View File
@@ -1,80 +0,0 @@
name: CI
on:
push:
branches:
- 6.1.x
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
ci:
if: ${{ github.repository == 'spring-projects/spring-framework' }}
strategy:
matrix:
os:
- id: ubuntu-latest
name: Linux
java:
- version: 17
toolchain: false
- version: 21
toolchain: true
exclude:
- os:
name: Linux
java:
version: 17
name: '${{ matrix.os.name}} | Java ${{ matrix.java.version}}'
runs-on: ${{ matrix.os.id }}
steps:
- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: 'liberica'
java-version: |
${{ matrix.java.version }}
${{ matrix.java.toolchain && '17' || '' }}
- name: Prepare Windows runner
if: ${{ runner.os == 'Windows' }}
run: |
git config --global core.autocrlf true
git config --global core.longPaths true
Stop-Service -name Docker
- name: Check out code
uses: actions/checkout@v4
- name: Set up Gradle
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5
with:
cache-read-only: false
- name: Configure Gradle properties
shell: bash
run: |
mkdir -p $HOME/.gradle
echo 'systemProp.user.name=spring-builds+github' >> $HOME/.gradle/gradle.properties
echo 'systemProp.org.gradle.internal.launcher.welcomeMessageEnabled=false' >> $HOME/.gradle/gradle.properties
echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties
echo 'org.gradle.daemon=4' >> $HOME/.gradle/gradle.properties
- name: Configure toolchain properties
if: ${{ matrix.java.toolchain }}
shell: bash
run: |
echo toolchainVersion=${{ matrix.java.version }} >> $HOME/.gradle/gradle.properties
echo systemProp.org.gradle.java.installations.auto-detect=false >> $HOME/.gradle/gradle.properties
echo systemProp.org.gradle.java.installations.auto-download=false >> $HOME/.gradle/gradle.properties
echo systemProp.org.gradle.java.installations.paths=${{ format('$JAVA_HOME_{0}_X64', matrix.java.version) }} >> $HOME/.gradle/gradle.properties
- name: Build
id: build
env:
CI: 'true'
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
run: ./gradlew check antora
- name: Send notification
uses: ./.github/actions/send-notification
if: always()
with:
webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
status: ${{ job.status }}
build-scan-url: ${{ steps.build.outputs.build-scan-url }}
run-name: ${{ format('{0} | {1} | Java {2}', github.ref_name, matrix.os.name, matrix.java.version) }}
+6 -8
View File
@@ -1,14 +1,12 @@
name: Deploy Docs
on:
push:
branches:
- 'main'
- '*.x'
- '!gh-pages'
tags:
- 'v*'
branches-ignore: [ gh-pages ]
tags: '**'
repository_dispatch:
types: request-build-reference # legacy
schedule:
- cron: '0 10 * * *' # Once per day at 10am UTC
workflow_dispatch:
permissions:
actions: write
@@ -17,8 +15,8 @@ jobs:
runs-on: ubuntu-latest
if: github.repository_owner == 'spring-projects'
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Checkout
uses: actions/checkout@v3
with:
ref: docs-build
fetch-depth: 1
@@ -9,5 +9,5 @@ jobs:
name: "Validation"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: gradle/wrapper-validation-action@v2
- uses: actions/checkout@v3
- uses: gradle/wrapper-validation-action@v1
+36
View File
@@ -0,0 +1,36 @@
Juergen Hoeller <jhoeller@vmware.com>
Juergen Hoeller <jhoeller@vmware.com> <jhoeller@pivotal.io>
Juergen Hoeller <jhoeller@vmware.com> <jhoeller@gopivotal.com>
Rossen Stoyanchev <rstoyanchev@vmware.com>
Rossen Stoyanchev <rstoyanchev@vmware.com> <rstoyanchev@pivotal.io>
Rossen Stoyanchev <rstoyanchev@vmware.com> <rstoyanchev@gopivotal.com>
Phillip Webb <pwebb@vmware.com>
Phillip Webb <pwebb@vmware.com> <pwebb@pivotal.io>
Phillip Webb <pwebb@vmware.com> <pwebb@gopivotal.com>
Chris Beams <cbeams@vmware.com>
Chris Beams <cbeams@vmware.com> <cbeams@pivotal.io>
Chris Beams <cbeams@vmware.com> <cbeams@gopivotal.com>
Arjen Poutsma <poutsmaa@vmware.com>
Arjen Poutsma <poutsmaa@vmware.com> <apoutsma@pivotal.io>
Arjen Poutsma <poutsmaa@vmware.com> <apoutsma@gopivotal.com>
Arjen Poutsma <poutsmaa@vmware.com> <poutsma@mac.com>
Arjen Poutsma <poutsmaa@vmware.com> <apoutsma@vmware.com>
Oliver Drotbohm <odrotbohm@vmware.com>
Oliver Drotbohm <odrotbohm@vmware.com> <ogierke@vmware.com>
Oliver Drotbohm <odrotbohm@vmware.com> <ogierke@pivotal.io>
Oliver Drotbohm <odrotbohm@vmware.com> <ogierke@gopivotal.com>
Dave Syer <dsyer@vmware.com>
Dave Syer <dsyer@vmware.com> <dsyer@pivotal.io>
Dave Syer <dsyer@vmware.com> <dsyer@gopivotal.com>
Dave Syer <dsyer@vmware.com> <david_syer@hotmail.com>
Andy Clement <aclement@vmware.com>
Andy Clement <aclement@vmware.com> <aclement@pivotal.io>
Andy Clement <aclement@vmware.com> <aclement@gopivotal.com>
Andy Clement <aclement@vmware.com> <andrew.clement@gmail.com>
Sam Brannen <sbrannen@vmware.com>
Sam Brannen <sbrannen@vmware.com> <sbrannen@pivotal.io>
Sam Brannen <sbrannen@vmware.com> <sam@sambrannen.com>
Simon Basle <sbasle@vmware.com>
Simon Baslé <sbasle@vmware.com>
<dmitry.katsubo@gmail.com> <dmitry.katsubo@gmai.com>
Nick Williams <nicholas@nicholaswilliams.net>
+1 -1
View File
@@ -1,3 +1,3 @@
# Enable auto-env through the sdkman_auto_env config
# Add key=value pairs of SDKs to use below
java=17.0.10-librca
java=17.0.8.1-librca
+2 -2
View File
@@ -1,4 +1,4 @@
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [![Build Status](https://github.com/spring-projects/spring-framework/actions/workflows/build-and-deploy-snapshot.yml/badge.svg?branch=6.1.x)](https://github.com/spring-projects/spring-framework/actions/workflows/build-and-deploy-snapshot.yml?query=branch%3A6.1.x) [![Revved up by Develocity](https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.spring.io/scans?search.rootProjectNames=spring)
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [![Build Status](https://ci.spring.io/api/v1/teams/spring-framework/pipelines/spring-framework-6.0.x/jobs/build/badge)](https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-6.0.x?groups=Build") [![Revved up by Gradle Enterprise](https://img.shields.io/badge/Revved%20up%20by-Gradle%20Enterprise-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.spring.io/scans?search.rootProjectNames=spring)
This is the home of the Spring Framework: the foundation for all [Spring projects](https://spring.io/projects). Collectively the Spring Framework and the family of Spring projects are often referred to simply as "Spring".
@@ -31,7 +31,7 @@ Information regarding CI builds can be found in the [Spring Framework Concourse
## Stay in Touch
Follow [@SpringCentral](https://twitter.com/springcentral), [@SpringFramework](https://twitter.com/springframework), and its [team members](https://twitter.com/springframework/lists/team/members) on 𝕏. In-depth articles can be found at [The Spring Blog](https://spring.io/blog/), and releases are announced via our [releases feed](https://spring.io/blog/category/releases).
Follow [@SpringCentral](https://twitter.com/springcentral), [@SpringFramework](https://twitter.com/springframework), and its [team members](https://twitter.com/springframework/lists/team/members) on Twitter. In-depth articles can be found at [The Spring Blog](https://spring.io/blog/), and releases are announced via our [releases feed](https://spring.io/blog/category/releases).
## License
+8 -4
View File
@@ -4,7 +4,7 @@ plugins {
id 'org.jetbrains.kotlin.plugin.serialization' version "${kotlinVersion}" apply false
id 'org.jetbrains.dokka' version '1.8.20'
id 'org.unbroken-dome.xjc' version '2.0.0' apply false
id 'com.github.ben-manes.versions' version '0.51.0'
id 'com.github.ben-manes.versions' version '0.49.0'
id 'com.github.johnrengelman.shadow' version '8.1.1' apply false
id 'de.undercouch.download' version '5.4.0'
id 'me.champeau.jmh' version '0.7.2' apply false
@@ -16,8 +16,6 @@ ext {
javaProjects = subprojects.findAll { !it.name.startsWith("framework-") }
}
description = "Spring Framework"
configure(allprojects) { project ->
apply plugin: "org.springframework.build.localdev"
group = "org.springframework"
@@ -90,6 +88,7 @@ configure([rootProject] + javaProjects) { project ->
"https://docs.oracle.com/en/java/javase/17/docs/api/",
"https://jakarta.ee/specifications/platform/9/apidocs/",
"https://docs.oracle.com/cd/E13222_01/wls/docs90/javadocs/", // CommonJ and weblogic.* packages
"https://www.ibm.com/docs/api/v1/content/SSEQTP_8.5.5/com.ibm.websphere.javadoc.doc/web/apidocs/", // com.ibm.*
"https://docs.jboss.org/jbossas/javadoc/4.0.5/connector/", // org.jboss.resource.*
"https://docs.jboss.org/hibernate/orm/5.6/javadocs/",
"https://eclipse.dev/aspectj/doc/released/aspectj5rt-api",
@@ -103,7 +102,7 @@ configure([rootProject] + javaProjects) { project ->
// TODO Uncomment link to JUnit 5 docs once we execute Gradle with Java 18+.
// See https://github.com/spring-projects/spring-framework/issues/27497
//
// "https://junit.org/junit5/docs/5.10.2/api/",
// "https://junit.org/junit5/docs/5.10.1/api/",
"https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/",
//"https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/",
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
@@ -117,3 +116,8 @@ configure([rootProject] + javaProjects) { project ->
configure(moduleProjects) { project ->
apply from: "${rootDir}/gradle/spring-module.gradle"
}
configure(rootProject) {
description = "Spring Framework"
apply plugin: 'org.springframework.build.api-diff'
}
+15
View File
@@ -22,6 +22,21 @@ but doesn't affect the classpath of dependent projects.
This plugin does not provide a `provided` configuration, as the native `compileOnly` and `testCompileOnly`
configurations are preferred.
### API Diff
This plugin uses the [Gradle JApiCmp](https://github.com/melix/japicmp-gradle-plugin) plugin
to generate API Diff reports for each Spring Framework module. This plugin is applied once on the root
project and creates tasks in each framework module. Unlike previous versions of this part of the build,
there is no need for checking out a specific tag. The plugin will fetch the JARs we want to compare the
current working version with. You can generate the reports for all modules or a single module:
```
./gradlew apiDiff -PbaselineVersion=5.1.0.RELEASE
./gradlew :spring-core:apiDiff -PbaselineVersion=5.1.0.RELEASE
```
The reports are located under `build/reports/api-diff/$OLDVERSION_to_$NEWVERSION/`.
### RuntimeHints Java Agent
+6 -1
View File
@@ -21,13 +21,18 @@ dependencies {
checkstyle "io.spring.javaformat:spring-javaformat-checkstyle:${javaFormatVersion}"
implementation "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}"
implementation "org.jetbrains.kotlin:kotlin-compiler-embeddable:${kotlinVersion}"
implementation "org.gradle:test-retry-gradle-plugin:1.5.6"
implementation "me.champeau.gradle:japicmp-gradle-plugin:0.3.0"
implementation "org.gradle:test-retry-gradle-plugin:1.4.1"
implementation "io.spring.javaformat:spring-javaformat-gradle-plugin:${javaFormatVersion}"
implementation "io.spring.nohttp:nohttp-gradle:0.0.11"
}
gradlePlugin {
plugins {
apiDiffPlugin {
id = "org.springframework.build.api-diff"
implementationClass = "org.springframework.build.api.ApiDiffPlugin"
}
conventionsPlugin {
id = "org.springframework.build.conventions"
implementationClass = "org.springframework.build.ConventionsPlugin"
+1 -1
View File
@@ -1,2 +1,2 @@
org.gradle.caching=true
javaFormatVersion=0.0.41
javaFormatVersion=0.0.39
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -50,12 +50,12 @@ public class CheckstyleConventions {
project.getPlugins().apply(CheckstylePlugin.class);
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g"));
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
checkstyle.setToolVersion("10.15.0");
checkstyle.setToolVersion("10.12.5");
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
checkstyleDependencies.add(
project.getDependencies().create("io.spring.javaformat:spring-javaformat-checkstyle:" + version));
checkstyleDependencies
.add(project.getDependencies().create("io.spring.javaformat:spring-javaformat-checkstyle:" + version));
});
}
@@ -0,0 +1,142 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.build.api;
import java.io.File;
import java.net.URI;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
import me.champeau.gradle.japicmp.JapicmpPlugin;
import me.champeau.gradle.japicmp.JapicmpTask;
import org.gradle.api.GradleException;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.jvm.tasks.Jar;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* {@link Plugin} that applies the {@code "japicmp-gradle-plugin"}
* and create tasks for all subprojects named {@code "spring-*"}, diffing the public API one by one
* and creating the reports in {@code "build/reports/api-diff/$OLDVERSION_to_$NEWVERSION/"}.
* <p>{@code "./gradlew apiDiff -PbaselineVersion=5.1.0.RELEASE"} will output the
* reports for the API diff between the baseline version and the current one for all modules.
* You can limit the report to a single module with
* {@code "./gradlew :spring-core:apiDiff -PbaselineVersion=5.1.0.RELEASE"}.
*
* @author Brian Clozel
*/
public class ApiDiffPlugin implements Plugin<Project> {
private static final Logger logger = LoggerFactory.getLogger(ApiDiffPlugin.class);
public static final String TASK_NAME = "apiDiff";
private static final String BASELINE_VERSION_PROPERTY = "baselineVersion";
private static final List<String> PACKAGE_INCLUDES = Collections.singletonList("org.springframework.*");
private static final URI SPRING_MILESTONE_REPOSITORY = URI.create("https://repo.spring.io/milestone");
@Override
public void apply(Project project) {
if (project.hasProperty(BASELINE_VERSION_PROPERTY) && project.equals(project.getRootProject())) {
project.getPluginManager().apply(JapicmpPlugin.class);
project.getPlugins().withType(JapicmpPlugin.class,
plugin -> applyApiDiffConventions(project));
}
}
private void applyApiDiffConventions(Project project) {
String baselineVersion = project.property(BASELINE_VERSION_PROPERTY).toString();
project.subprojects(subProject -> {
if (subProject.getName().startsWith("spring-")) {
createApiDiffTask(baselineVersion, subProject);
}
});
}
private void createApiDiffTask(String baselineVersion, Project project) {
if (isProjectEligible(project)) {
// Add Spring Milestone repository for generating diffs against previous milestones
project.getRootProject()
.getRepositories()
.maven(mavenArtifactRepository -> mavenArtifactRepository.setUrl(SPRING_MILESTONE_REPOSITORY));
JapicmpTask apiDiff = project.getTasks().create(TASK_NAME, JapicmpTask.class);
apiDiff.setDescription("Generates an API diff report with japicmp");
apiDiff.setGroup(JavaBasePlugin.DOCUMENTATION_GROUP);
apiDiff.setOldClasspath(createBaselineConfiguration(baselineVersion, project));
TaskProvider<Jar> jar = project.getTasks().withType(Jar.class).named("jar");
apiDiff.setNewArchives(project.getLayout().files(jar.get().getArchiveFile().get().getAsFile()));
apiDiff.setNewClasspath(getRuntimeClassPath(project));
apiDiff.setPackageIncludes(PACKAGE_INCLUDES);
apiDiff.setOnlyModified(true);
apiDiff.setIgnoreMissingClasses(true);
// Ignore Kotlin metadata annotations since they contain
// illegal HTML characters and fail the report generation
apiDiff.setAnnotationExcludes(Collections.singletonList("@kotlin.Metadata"));
apiDiff.setHtmlOutputFile(getOutputFile(baselineVersion, project));
apiDiff.dependsOn(project.getTasks().getByName("jar"));
}
}
private boolean isProjectEligible(Project project) {
return project.getPlugins().hasPlugin(JavaPlugin.class)
&& project.getPlugins().hasPlugin(MavenPublishPlugin.class);
}
private Configuration createBaselineConfiguration(String baselineVersion, Project project) {
String baseline = String.join(":",
project.getGroup().toString(), project.getName(), baselineVersion);
Dependency baselineDependency = project.getDependencies().create(baseline + "@jar");
Configuration baselineConfiguration = project.getRootProject().getConfigurations().detachedConfiguration(baselineDependency);
try {
// eagerly resolve the baseline configuration to check whether this is a new Spring module
baselineConfiguration.resolve();
return baselineConfiguration;
}
catch (GradleException exception) {
logger.warn("Could not resolve {} - assuming this is a new Spring module.", baseline);
}
return project.getRootProject().getConfigurations().detachedConfiguration();
}
private Configuration getRuntimeClassPath(Project project) {
return project.getConfigurations().getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);
}
private File getOutputFile(String baseLineVersion, Project project) {
String buildDirectoryPath = project.getRootProject()
.getLayout().getBuildDirectory().getAsFile().get().getAbsolutePath();
Path outDir = Paths.get(buildDirectoryPath, "reports", "api-diff",
baseLineVersion + "_to_" + project.getRootProject().getVersion());
return project.file(outDir.resolve(project.getName() + ".html").toString());
}
}
+2 -4
View File
@@ -1,10 +1,8 @@
== Spring Framework Concourse pipeline
NOTE: CI is being migrated to GitHub Actions.
The Spring Framework uses https://concourse-ci.org/[Concourse] for its CI build and other automated tasks.
The Spring team has a dedicated Concourse instance available at https://ci.spring.io with a build pipeline
for https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-6.1.x[Spring Framework 6.1.x].
for https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-6.0.x[Spring Framework 6.0.x].
=== Setting up your development environment
@@ -53,7 +51,7 @@ The pipeline can be deployed using the following command:
[source]
----
$ fly -t spring set-pipeline -p spring-framework-6.1.x -c ci/pipeline.yml -l ci/parameters.yml
$ fly -t spring set-pipeline -p spring-framework-6.0.x -c ci/pipeline.yml -l ci/parameters.yml
----
NOTE: This assumes that you have credhub integration configured with the appropriate secrets.
+2 -2
View File
@@ -1,4 +1,4 @@
FROM ubuntu:jammy-20240125
FROM ubuntu:jammy-20231004
ADD setup.sh /setup.sh
ADD get-jdk-url.sh /get-jdk-url.sh
@@ -7,6 +7,6 @@ RUN ./setup.sh
ENV JAVA_HOME /opt/openjdk/java17
ENV JDK17 /opt/openjdk/java17
ENV JDK21 /opt/openjdk/java21
ENV JDK23 /opt/openjdk/java23
ENV JDK22 /opt/openjdk/java22
ENV PATH $JAVA_HOME/bin:$PATH
+4 -4
View File
@@ -3,13 +3,13 @@ set -e
case "$1" in
java17)
echo "https://github.com/bell-sw/Liberica/releases/download/17.0.10%2B13/bellsoft-jdk17.0.10+13-linux-amd64.tar.gz"
echo "https://download.bell-sw.com/java/17.0.9+11/bellsoft-jdk17.0.9+11-linux-amd64.tar.gz"
;;
java21)
echo "https://github.com/bell-sw/Liberica/releases/download/21.0.2%2B14/bellsoft-jdk21.0.2+14-linux-amd64.tar.gz"
echo "https://download.bell-sw.com/java/21.0.1+12/bellsoft-jdk21.0.1+12-linux-amd64.tar.gz"
;;
java23)
echo "https://download.java.net/java/early_access/jdk23/10/GPL/openjdk-23-ea+10_linux-x64_bin.tar.gz"
java22)
echo "https://download.java.net/java/early_access/jdk22/19/GPL/openjdk-22-ea+19_linux-x64_bin.tar.gz"
;;
*)
echo $"Unknown java version"
+2 -1
View File
@@ -3,8 +3,9 @@ github-repo-name: "spring-projects/spring-framework"
sonatype-staging-profile: "org.springframework"
docker-hub-organization: "springci"
artifactory-server: "https://repo.spring.io"
branch: "6.1.x"
branch: "main"
milestone: "6.1.x"
build-name: "spring-framework"
pipeline-name: "spring-framework"
concourse-url: "https://ci.spring.io"
task-timeout: 1h00m
+163 -25
View File
@@ -23,6 +23,14 @@ anchors:
docker-resource-source: &docker-resource-source
username: ((docker-hub-username))
password: ((docker-hub-password))
slack-fail-params: &slack-fail-params
text: >
:concourse-failed: <https://ci.spring.io/teams/${BUILD_TEAM_NAME}/pipelines/${BUILD_PIPELINE_NAME}/jobs/${BUILD_JOB_NAME}/builds/${BUILD_NAME}|${BUILD_PIPELINE_NAME} ${BUILD_JOB_NAME} failed!>
[$TEXT_FILE_CONTENT]
text_file: git-repo/build/build-scan-uri.txt
silent: true
icon_emoji: ":concourse:"
username: concourse-ci
changelog-task-params: &changelog-task-params
name: generated-changelog/tag
tag: generated-changelog/tag
@@ -56,6 +64,12 @@ resource_types:
<<: *docker-resource-source
repository: dpb587/github-status-resource
tag: master
- name: slack-notification
type: registry-image
source:
<<: *docker-resource-source
repository: cfcommunity/slack-notification-resource
tag: latest
resources:
- name: git-repo
type: git
@@ -76,6 +90,13 @@ resources:
<<: *docker-resource-source
repository: ((docker-hub-organization))/spring-framework-ci
tag: ((milestone))
- name: every-morning
type: time
icon: alarm
source:
start: 8:00 AM
stop: 9:00 AM
location: Europe/Vienna
- name: artifactory-repo
type: artifactory-resource
icon: package-variant
@@ -84,6 +105,35 @@ resources:
username: ((artifactory-username))
password: ((artifactory-password))
build_name: ((build-name))
- name: repo-status-build
type: github-status-resource
icon: eye-check-outline
source:
repository: ((github-repo-name))
access_token: ((github-ci-status-token))
branch: ((branch))
context: build
- name: repo-status-jdk21-build
type: github-status-resource
icon: eye-check-outline
source:
repository: ((github-repo-name))
access_token: ((github-ci-status-token))
branch: ((branch))
context: jdk21-build
- name: repo-status-jdk22-build
type: github-status-resource
icon: eye-check-outline
source:
repository: ((github-repo-name))
access_token: ((github-ci-status-token))
branch: ((branch))
context: jdk22-build
- name: slack-alert
type: slack-notification
icon: slack
source:
url: ((slack-webhook-url))
- name: github-pre-release
type: github-release
icon: briefcase-download-outline
@@ -118,6 +168,113 @@ jobs:
- put: ci-image
params:
image: ci-image/image.tar
- name: build
serial: true
public: true
plan:
- get: ci-image
- get: git-repo
trigger: true
- put: repo-status-build
params: { state: "pending", commit: "git-repo" }
- do:
- task: build-project
image: ci-image
file: git-repo/ci/tasks/build-project.yml
privileged: true
timeout: ((task-timeout))
params:
<<: *build-project-task-params
on_failure:
do:
- put: repo-status-build
params: { state: "failure", commit: "git-repo" }
- put: slack-alert
params:
<<: *slack-fail-params
- put: repo-status-build
params: { state: "success", commit: "git-repo" }
- put: artifactory-repo
params: &artifactory-params
signing_key: ((signing-key))
signing_passphrase: ((signing-passphrase))
repo: libs-snapshot-local
folder: distribution-repository
build_uri: "https://ci.spring.io/teams/${BUILD_TEAM_NAME}/pipelines/${BUILD_PIPELINE_NAME}/jobs/${BUILD_JOB_NAME}/builds/${BUILD_NAME}"
build_number: "${BUILD_PIPELINE_NAME}-${BUILD_JOB_NAME}-${BUILD_NAME}"
disable_checksum_uploads: true
threads: 8
artifact_set:
- include:
- "/**/framework-api-*.zip"
properties:
"zip.name": "spring-framework"
"zip.displayname": "Spring Framework"
"zip.deployed": "false"
- include:
- "/**/framework-api-*-docs.zip"
properties:
"zip.type": "docs"
- include:
- "/**/framework-api-*-schema.zip"
properties:
"zip.type": "schema"
get_params:
threads: 8
- name: jdk21-build
serial: true
public: true
plan:
- get: ci-image
- get: git-repo
- get: every-morning
trigger: true
- put: repo-status-jdk21-build
params: { state: "pending", commit: "git-repo" }
- do:
- task: check-project
image: ci-image
file: git-repo/ci/tasks/check-project.yml
privileged: true
timeout: ((task-timeout))
params:
TEST_TOOLCHAIN: 21
<<: *build-project-task-params
on_failure:
do:
- put: repo-status-jdk21-build
params: { state: "failure", commit: "git-repo" }
- put: slack-alert
params:
<<: *slack-fail-params
- put: repo-status-jdk21-build
params: { state: "success", commit: "git-repo" }
- name: jdk22-build
serial: true
public: true
plan:
- get: ci-image
- get: git-repo
- put: repo-status-jdk22-build
params: { state: "pending", commit: "git-repo" }
- do:
- task: check-project
image: ci-image
file: git-repo/ci/tasks/check-project.yml
privileged: true
timeout: ((task-timeout))
params:
TEST_TOOLCHAIN: 22
<<: *build-project-task-params
on_failure:
do:
- put: repo-status-jdk22-build
params: { state: "failure", commit: "git-repo" }
- put: slack-alert
params:
<<: *slack-fail-params
- put: repo-status-jdk22-build
params: { state: "success", commit: "git-repo" }
- name: stage-milestone
serial: true
plan:
@@ -131,32 +288,9 @@ jobs:
RELEASE_TYPE: M
<<: *gradle-enterprise-task-params
- put: artifactory-repo
params: &artifactory-params
signing_key: ((signing-key))
signing_passphrase: ((signing-passphrase))
params:
<<: *artifactory-params
repo: libs-staging-local
folder: distribution-repository
build_uri: "https://ci.spring.io/teams/${BUILD_TEAM_NAME}/pipelines/${BUILD_PIPELINE_NAME}/jobs/${BUILD_JOB_NAME}/builds/${BUILD_NAME}"
build_number: "${BUILD_PIPELINE_NAME}-${BUILD_JOB_NAME}-${BUILD_NAME}"
disable_checksum_uploads: true
threads: 8
artifact_set:
- include:
- "/**/framework-api-*.zip"
properties:
"zip.name": "spring-framework"
"zip.displayname": "Spring Framework"
"zip.deployed": "false"
- include:
- "/**/framework-api-*-docs.zip"
properties:
"zip.type": "docs"
- include:
- "/**/framework-api-*-schema.zip"
properties:
"zip.type": "schema"
get_params:
threads: 8
- put: git-repo
params:
repository: stage-git-repo
@@ -201,6 +335,7 @@ jobs:
- put: artifactory-repo
params:
<<: *artifactory-params
repo: libs-staging-local
- put: git-repo
params:
repository: stage-git-repo
@@ -245,6 +380,7 @@ jobs:
- put: artifactory-repo
params:
<<: *artifactory-params
repo: libs-staging-local
- put: git-repo
params:
repository: stage-git-repo
@@ -289,6 +425,8 @@ jobs:
<<: *changelog-task-params
groups:
- name: "builds"
jobs: ["build", "jdk21-build", "jdk22-build"]
- name: "releases"
jobs: ["stage-milestone", "stage-rc", "stage-release", "promote-milestone", "promote-rc", "promote-release", "create-github-release"]
- name: "ci-images"
+9
View File
@@ -0,0 +1,9 @@
#!/bin/bash
set -e
source $(dirname $0)/common.sh
pushd git-repo > /dev/null
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Porg.gradle.java.installations.fromEnv=JDK17,JDK21 \
--no-daemon --max-workers=4 check
popd > /dev/null
+10
View File
@@ -0,0 +1,10 @@
#!/bin/bash
set -e
source $(dirname $0)/common.sh
repository=$(pwd)/distribution-repository
pushd git-repo > /dev/null
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Porg.gradle.java.installations.fromEnv=JDK17,JDK21,JDK22 \
--no-daemon --max-workers=4 -PdeploymentRepository=${repository} build publishAllPublicationsToDeploymentRepository
popd > /dev/null
+9
View File
@@ -0,0 +1,9 @@
#!/bin/bash
set -e
source $(dirname $0)/common.sh
pushd git-repo > /dev/null
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Porg.gradle.java.installations.fromEnv=JDK17,JDK21 \
-PmainToolchain=${MAIN_TOOLCHAIN} -PtestToolchain=${TEST_TOOLCHAIN} --no-daemon --max-workers=4 check antora
popd > /dev/null
+19
View File
@@ -0,0 +1,19 @@
---
platform: linux
inputs:
- name: git-repo
caches:
- path: gradle
params:
BRANCH:
CI: true
GRADLE_ENTERPRISE_ACCESS_KEY:
GRADLE_ENTERPRISE_CACHE_USERNAME:
GRADLE_ENTERPRISE_CACHE_PASSWORD:
GRADLE_ENTERPRISE_URL: https://ge.spring.io
run:
path: bash
args:
- -ec
- |
${PWD}/git-repo/ci/scripts/build-pr.sh
+22
View File
@@ -0,0 +1,22 @@
---
platform: linux
inputs:
- name: git-repo
outputs:
- name: distribution-repository
- name: git-repo
caches:
- path: gradle
params:
BRANCH:
CI: true
GRADLE_ENTERPRISE_ACCESS_KEY:
GRADLE_ENTERPRISE_CACHE_USERNAME:
GRADLE_ENTERPRISE_CACHE_PASSWORD:
GRADLE_ENTERPRISE_URL: https://ge.spring.io
run:
path: bash
args:
- -ec
- |
${PWD}/git-repo/ci/scripts/build-project.sh
+24
View File
@@ -0,0 +1,24 @@
---
platform: linux
inputs:
- name: git-repo
outputs:
- name: distribution-repository
- name: git-repo
caches:
- path: gradle
params:
BRANCH:
CI: true
MAIN_TOOLCHAIN:
TEST_TOOLCHAIN:
GRADLE_ENTERPRISE_ACCESS_KEY:
GRADLE_ENTERPRISE_CACHE_USERNAME:
GRADLE_ENTERPRISE_CACHE_PASSWORD:
GRADLE_ENTERPRISE_URL: https://ge.spring.io
run:
path: bash
args:
- -ec
- |
${PWD}/git-repo/ci/scripts/check-project.sh
+3 -3
View File
@@ -27,8 +27,8 @@ javadoc {
author = true
header = rootProject.description
use = true
overview = project.relativePath("$rootProject.rootDir/framework-docs/src/docs/api/overview.html")
destinationDir = file("$project.docsDir/javadoc-api")
overview = "$rootProject.rootDir/framework-docs/src/docs/api/overview.html"
destinationDir = file("${project.buildDir}/docs/javadoc-api")
splitIndex = true
links(rootProject.ext.javadocLinks)
addBooleanOption('Xdoclint:syntax,reference', true) // only check syntax and reference with doclint
@@ -52,7 +52,7 @@ rootProject.tasks.dokkaHtmlMultiModule.configure {
tasks.named("javadoc")
}
moduleName.set("spring-framework")
outputDirectory.set(file("$docsDir/kdoc-api"))
outputDirectory.set(project.file("$buildDir/docs/kdoc-api"))
includes.from("$rootProject.rootDir/framework-docs/src/docs/api/dokka-overview.md")
}
+9 -67
View File
@@ -17,74 +17,16 @@ asciidoc:
# FIXME: the copyright is not removed
# FIXME: The package is not renamed
chomp: 'all'
fold: 'all'
table-stripes: 'odd'
include-java: 'example$docs-src/main/java/org/springframework/docs'
spring-site: 'https://spring.io'
spring-site-blog: '{spring-site}/blog'
spring-site-cve: "{spring-site}/security"
spring-site-guides: '{spring-site}/guides'
spring-site-projects: '{spring-site}/projects'
spring-site-tools: "{spring-site}/tools"
spring-org: 'spring-projects'
spring-github-org: "https://github.com/{spring-org}"
spring-framework-github: "https://github.com/{spring-org}/spring-framework"
spring-framework-code: '{spring-framework-github}/tree/main'
spring-framework-issues: '{spring-framework-github}/issues'
spring-framework-wiki: '{spring-framework-github}/wiki'
# Docs
spring-framework-main-code: 'https://github.com/spring-projects/spring-framework/tree/main'
docs-site: 'https://docs.spring.io'
spring-framework-docs-root: '{docs-site}/spring-framework/docs'
spring-framework-api: '{spring-framework-docs-root}/{spring-version}/javadoc-api/org/springframework'
spring-framework-api-kdoc: '{spring-framework-docs-root}/{spring-version}/kdoc-api'
spring-framework-reference: '{spring-framework-docs-root}/{spring-version}/reference'
#
# Other Spring portfolio projects
spring-boot-docs: '{docs-site}/spring-boot/docs/current/reference/html'
spring-boot-issues: '{spring-github-org}/spring-boot/issues'
# TODO add more projects / links or just build up on {docs-site}?
# TODO rename the below using new conventions
docs-spring: "{docs-site}/spring-framework/docs/{spring-version}"
docs-spring-framework: '{docs-site}/spring-framework/docs/{spring-version}'
api-spring-framework: '{docs-spring-framework}/javadoc-api/org/springframework'
docs-graalvm: 'https://www.graalvm.org/22.3/reference-manual'
docs-spring-boot: '{docs-site}/spring-boot/docs/current/reference'
docs-spring-gemfire: '{docs-site}/spring-gemfire/docs/current/reference'
docs-spring-security: '{docs-site}/spring-security/reference'
docs-spring-session: '{docs-site}/spring-session/reference'
#
# External projects URLs and related attributes
aspectj-site: 'https://www.eclipse.org/aspectj'
aspectj-docs: "{aspectj-site}/doc/released"
aspectj-api: "{aspectj-docs}/runtime-api"
aspectj-docs-devguide: "{aspectj-docs}/devguide"
aspectj-docs-progguide: "{aspectj-docs}/progguide"
assertj-docs: 'https://assertj.github.io/doc'
baeldung-blog: 'https://www.baeldung.com'
bean-validation-site: 'https://beanvalidation.org'
graalvm-docs: 'https://www.graalvm.org/22.3/reference-manual'
hibernate-validator-site: 'https://hibernate.org/validator/'
jackson-docs: 'https://fasterxml.github.io'
jackson-github-org: 'https://github.com/FasterXML'
java-api: 'https://docs.oracle.com/en/java/javase/17/docs/api'
java-tutorial: 'https://docs.oracle.com/javase/tutorial'
JSR: 'https://www.jcp.org/en/jsr/detail?id='
kotlin-site: 'https://kotlinlang.org'
kotlin-docs: '{kotlin-site}/docs'
kotlin-api: '{kotlin-site}/api/latest'
kotlin-coroutines-api: '{kotlin-site}/api/kotlinx.coroutines'
kotlin-github-org: 'https://github.com/Kotlin'
kotlin-issues: 'https://youtrack.jetbrains.com/issue'
reactive-streams-site: 'https://www.reactive-streams.org'
reactive-streams-spec: 'https://github.com/reactive-streams/reactive-streams-jvm/blob/master/README.md#specification'
reactor-github-org: 'https://github.com/reactor'
reactor-site: 'https://projectreactor.io'
rsocket-github-org: 'https://github.com/rsocket'
rsocket-java: '{rsocket-github-org}/rsocket-java'
rsocket-java-code: '{rsocket-java}/tree/master/'
rsocket-protocol-extensions: '{rsocket-github-org}/rsocket/tree/master/Extensions'
rsocket-site: 'https://rsocket.io'
rfc-site: 'https://datatracker.ietf.org/doc/html'
sockjs-client: 'https://github.com/sockjs/sockjs-client'
sockjs-protocol: 'https://github.com/sockjs/sockjs-protocol'
sockjs-protocol-site: "https://sockjs.github.io/sockjs-protocol"
stackoverflow-site: 'https://stackoverflow.com'
stackoverflow-questions: '{stackoverflow-site}/questions'
stackoverflow-spring-tag: "{stackoverflow-questions}/tagged/spring"
stackoverflow-spring-kotlin-tags: "{stackoverflow-spring-tag}+kotlin"
testcontainers-site: 'https://www.testcontainers.org'
gh-rsocket: 'https://github.com/rsocket'
gh-rsocket-extensions: '{gh-rsocket}/rsocket/blob/master/Extensions'
gh-rsocket-java: '{gh-rsocket}/rsocket-java{gh-rsocket}/rsocket-java'
+2 -2
View File
@@ -28,8 +28,8 @@ antora {
'@antora/atlas-extension': '1.0.0-alpha.1',
'@antora/collector-extension': '1.0.0-alpha.3',
'@asciidoctor/tabs': '1.0.0-beta.3',
'@opendevise/antora-release-line-extension': '1.0.0',
'@springio/antora-extensions': '1.8.2',
'@opendevise/antora-release-line-extension': '1.0.0-alpha.2',
'@springio/antora-extensions': '1.3.0',
'@springio/asciidoctor-extensions': '1.0.0-alpha.9'
]
}
+2 -4
View File
@@ -39,8 +39,8 @@
** xref:core/resources.adoc[]
** xref:core/validation.adoc[]
*** xref:core/validation/validator.adoc[]
*** xref:core/validation/beans-beans.adoc[]
*** xref:core/validation/conversion.adoc[]
*** xref:core/validation/beans-beans.adoc[]
*** xref:core/validation/convert.adoc[]
*** xref:core/validation/format.adoc[]
*** xref:core/validation/format-configuring-formatting-globaldatetimeformat.adoc[]
@@ -421,7 +421,6 @@
*** xref:integration/cache/specific-config.adoc[]
** xref:integration/observability.adoc[]
** xref:integration/checkpoint-restore.adoc[]
** xref:integration/cds.adoc[]
** xref:integration/appendix.adoc[]
* xref:languages.adoc[]
** xref:languages/kotlin.adoc[]
@@ -439,5 +438,4 @@
** xref:languages/groovy.adoc[]
** xref:languages/dynamic.adoc[]
* xref:appendix.adoc[]
* {spring-framework-wiki}[Wiki]
* https://github.com/spring-projects/spring-framework/wiki[Wiki]
@@ -8,7 +8,7 @@ within the core Spring Framework.
[[appendix-spring-properties]]
== Spring Properties
{spring-framework-api}/core/SpringProperties.html[`SpringProperties`] is a static holder
{api-spring-framework}/core/SpringProperties.html[`SpringProperties`] is a static holder
for properties that control certain low-level aspects of the Spring Framework. Users can
configure these properties via JVM system properties or programmatically via the
`SpringProperties.setProperty(String key, String value)` method. The latter may be
@@ -19,53 +19,15 @@ of the classpath -- for example, deployed within the application's JAR file.
The following table lists all currently supported Spring properties.
.Supported Spring Properties
[cols="1,1"]
|===
| Name | Description
| `spring.aot.enabled`
| Indicates the application should run with AOT generated artifacts. See
xref:core/aot.adoc[Ahead of Time Optimizations] and
{spring-framework-api}++/aot/AotDetector.html#AOT_ENABLED++[`AotDetector`]
for details.
| `spring.beaninfo.ignore`
| Instructs Spring to use the `Introspector.IGNORE_ALL_BEANINFO` mode when calling the
JavaBeans `Introspector`. See
{spring-framework-api}++/beans/StandardBeanInfoFactory.html#IGNORE_BEANINFO_PROPERTY_NAME++[`CachedIntrospectionResults`]
{api-spring-framework}++/beans/StandardBeanInfoFactory.html#IGNORE_BEANINFO_PROPERTY_NAME++[`CachedIntrospectionResults`]
for details.
| `spring.cache.reactivestreams.ignore`
| Instructs Spring's caching infrastructure to ignore the presence of Reactive Streams,
in particular Reactor's `Mono`/`Flux` in `@Cacheable` method return type declarations. See
{spring-framework-api}++/cache/interceptor/CacheAspectSupport.html#IGNORE_REACTIVESTREAMS_PROPERTY_NAME++[`CacheAspectSupport`]
for details.
| `spring.classformat.ignore`
| Instructs Spring to ignore class format exceptions during classpath scanning, in
particular for unsupported class file versions. See
{spring-framework-api}++/context/annotation/ClassPathScanningCandidateComponentProvider.html#IGNORE_CLASSFORMAT_PROPERTY_NAME++[`ClassPathScanningCandidateComponentProvider`]
for details.
| `spring.context.checkpoint`
| Property that specifies a common context checkpoint. See
xref:integration/checkpoint-restore.adoc#_automatic_checkpointrestore_at_startup[Automatic
checkpoint/restore at startup] and
{spring-framework-api}++/context/support/DefaultLifecycleProcessor.html#CHECKPOINT_PROPERTY_NAME++[`DefaultLifecycleProcessor`]
for details.
| `spring.context.exit`
| Property for terminating the JVM when the context reaches a specific phase. See
xref:integration/checkpoint-restore.adoc#_automatic_checkpointrestore_at_startup[Automatic
checkpoint/restore at startup] and
{spring-framework-api}++/context/support/DefaultLifecycleProcessor.html#EXIT_PROPERTY_NAME++[`DefaultLifecycleProcessor`]
for details.
| `spring.context.expression.maxLength`
| The maximum length for
xref:core/expressions/evaluation.adoc#expressions-parser-configuration[Spring Expression Language]
expressions used in XML bean definitions, `@Value`, etc.
| `spring.expression.compiler.mode`
| The mode to use when compiling expressions for the
xref:core/expressions/evaluation.adoc#expressions-compiler-configuration[Spring Expression Language].
@@ -74,7 +36,7 @@ xref:core/expressions/evaluation.adoc#expressions-compiler-configuration[Spring
| Instructs Spring to ignore operating system environment variables if a Spring
`Environment` property -- for example, a placeholder in a configuration String -- isn't
resolvable otherwise. See
{spring-framework-api}++/core/env/AbstractEnvironment.html#IGNORE_GETENV_PROPERTY_NAME++[`AbstractEnvironment`]
{api-spring-framework}++/core/env/AbstractEnvironment.html#IGNORE_GETENV_PROPERTY_NAME++[`AbstractEnvironment`]
for details.
| `spring.jdbc.getParameterType.ignore`
@@ -85,12 +47,12 @@ See the note in xref:data-access/jdbc/advanced.adoc#jdbc-batch-list[Batch Operat
| Instructs Spring to ignore a default JNDI environment, as an optimization for scenarios
where nothing is ever to be found for such JNDI fallback searches to begin with, avoiding
the repeated JNDI lookup overhead. See
{spring-framework-api}++/jndi/JndiLocatorDelegate.html#IGNORE_JNDI_PROPERTY_NAME++[`JndiLocatorDelegate`]
{api-spring-framework}++/jndi/JndiLocatorDelegate.html#IGNORE_JNDI_PROPERTY_NAME++[`JndiLocatorDelegate`]
for details.
| `spring.objenesis.ignore`
| Instructs Spring to ignore Objenesis, not even attempting to use it. See
{spring-framework-api}++/objenesis/SpringObjenesis.html#IGNORE_OBJENESIS_PROPERTY_NAME++[`SpringObjenesis`]
{api-spring-framework}++/objenesis/SpringObjenesis.html#IGNORE_OBJENESIS_PROPERTY_NAME++[`SpringObjenesis`]
for details.
| `spring.test.aot.processing.failOnError`
@@ -0,0 +1,20 @@
// Spring Portfolio
:docs-site: https://docs.spring.io
:docs-spring-boot: {docs-site}/spring-boot/docs/current/reference
:docs-spring-gemfire: {docs-site}/spring-gemfire/docs/current/reference
:docs-spring-security: {docs-site}/spring-security/reference
// spring-asciidoctor-backends Settings
:chomp: default headers packages
:fold: all
// Spring Framework
:docs-spring-framework: {docs-site}/spring-framework/docs/{spring-version}
:api-spring-framework: {docs-spring-framework}/javadoc-api/org/springframework
:docs-java: {docdir}/../../main/java/org/springframework/docs
:docs-kotlin: {docdir}/../../main/kotlin/org/springframework/docs
:docs-resources: {docdir}/../../main/resources
:spring-framework-main-code: https://github.com/spring-projects/spring-framework/tree/main
// Third-party Links
:docs-graalvm: https://www.graalvm.org/22.3/reference-manual
:gh-rsocket: https://github.com/rsocket
:gh-rsocket-extensions: {gh-rsocket}/rsocket/blob/master/Extensions
:gh-rsocket-java: {gh-rsocket}/rsocket-java
@@ -12,5 +12,5 @@ support for new custom advice types be added without changing the core framework
The only constraint on a custom `Advice` type is that it must implement the
`org.aopalliance.aop.Advice` marker interface.
See the {spring-framework-api}/aop/framework/adapter/package-summary.html[`org.springframework.aop.framework.adapter`]
See the {api-spring-framework}/aop/framework/adapter/package-summary.html[`org.springframework.aop.framework.adapter`]
javadoc for further information.
@@ -291,8 +291,6 @@ to consider:
* `final` classes cannot be proxied, because they cannot be extended.
* `final` methods cannot be advised, because they cannot be overridden.
* `private` methods cannot be advised, because they cannot be overridden.
* Methods that are not visible, typically package private methods in a parent class
from a different package, cannot be advised because they are effectively private.
NOTE: There is no need to add CGLIB to your classpath. CGLIB is repackaged and included
in the `spring-core` JAR. In other words, CGLIB-based AOP works "out of the box", as do
@@ -119,7 +119,7 @@ The following listing shows an example configuration:
Note that the target object (`businessObjectTarget` in the preceding example) must be a
prototype. This lets the `PoolingTargetSource` implementation create new instances
of the target to grow the pool as necessary. See the {spring-framework-api}/aop/target/AbstractPoolingTargetSource.html[javadoc of
of the target to grow the pool as necessary. See the {api-spring-framework}/aop/target/AbstractPoolingTargetSource.html[javadoc of
`AbstractPoolingTargetSource`] and the concrete subclass you wish to use for information
about its properties. `maxSize` is the most basic and is always guaranteed to be present.
@@ -168,7 +168,7 @@ Kotlin::
======
NOTE: Pooling stateless service objects is not usually necessary. We do not believe it should
be the default choice, as most stateless objects are naturally thread-safe, and instance
be the default choice, as most stateless objects are naturally thread safe, and instance
pooling is problematic if resources are cached.
Simpler pooling is available by using auto-proxying. You can set the `TargetSource` implementations
@@ -52,7 +52,7 @@ Kotlin::
----
======
See the {spring-framework-api}/aop/aspectj/annotation/AspectJProxyFactory.html[javadoc] for more information.
See the {api-spring-framework}/aop/aspectj/annotation/AspectJProxyFactory.html[javadoc] for more information.
@@ -4,7 +4,7 @@
@AspectJ refers to a style of declaring aspects as regular Java classes annotated with
annotations. The @AspectJ style was introduced by the
{aspectj-site}[AspectJ project] as part of the AspectJ 5 release. Spring
https://www.eclipse.org/aspectj[AspectJ project] as part of the AspectJ 5 release. Spring
interprets the same annotations as AspectJ 5, using a library supplied by AspectJ
for pointcut parsing and matching. The AOP runtime is still pure Spring AOP, though, and
there is no dependency on the AspectJ compiler or weaver.
@@ -482,7 +482,7 @@ The `JoinPoint` interface provides a number of useful methods:
* `getSignature()`: Returns a description of the method that is being advised.
* `toString()`: Prints a useful description of the method being advised.
See the {aspectj-api}/org/aspectj/lang/JoinPoint.html[javadoc] for more detail.
See the https://www.eclipse.org/aspectj/doc/released/runtime-api/org/aspectj/lang/JoinPoint.html[javadoc] for more detail.
[[aop-ataspectj-advice-params-passing]]
=== Passing Parameters to Advice
@@ -730,7 +730,7 @@ of determining parameter names, an exception will be thrown.
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]
{api-spring-framework}/aop/aspectj/AspectJAdviceParameterNameDiscoverer.html[javadoc]
for details on the algorithm used.
[[aop-ataspectj-advice-params-names-explicit]]
@@ -36,9 +36,9 @@ Kotlin::
The pointcut expression that forms the value of the `@Pointcut` annotation is a regular
AspectJ pointcut expression. For a full discussion of AspectJ's pointcut language, see
the {aspectj-docs-progguide}/index.html[AspectJ
the https://www.eclipse.org/aspectj/doc/released/progguide/index.html[AspectJ
Programming Guide] (and, for extensions, the
{aspectj-docs}/adk15notebook/index.html[AspectJ 5
https://www.eclipse.org/aspectj/doc/released/adk15notebook/index.html[AspectJ 5
Developer's Notebook]) or one of the books on AspectJ (such as _Eclipse AspectJ_, by Colyer
et al., or _AspectJ in Action_, by Ramnivas Laddad).
@@ -392,7 +392,7 @@ method that takes no parameters, whereas `(..)` matches any number (zero or more
The `({asterisk})` pattern matches a method that takes one parameter of any type.
`(*,String)` matches a method that takes two parameters. The first can be of any type, while the
second must be a `String`. Consult the
{aspectj-docs-progguide}/semantics-pointcuts.html[Language
https://www.eclipse.org/aspectj/doc/released/progguide/semantics-pointcuts.html[Language
Semantics] section of the AspectJ Programming Guide for more information.
The following examples show some common pointcut expressions:
@@ -2,7 +2,7 @@
= Further Resources
:page-section-summary-toc: 1
More information on AspectJ can be found on the {aspectj-site}[AspectJ website].
More information on AspectJ can be found on the https://www.eclipse.org/aspectj[AspectJ website].
_Eclipse AspectJ_ by Adrian Colyer et. al. (Addison-Wesley, 2005) provides a
comprehensive introduction and reference for the AspectJ language.
@@ -136,7 +136,7 @@ using Spring in accordance with the properties of the annotation". In this conte
"initialization" refers to newly instantiated objects (for example, objects instantiated
with the `new` operator) as well as to `Serializable` objects that are undergoing
deserialization (for example, through
{java-api}/java.base/java/io/Serializable.html[readResolve()]).
https://docs.oracle.com/javase/8/docs/api/java/io/Serializable.html[readResolve()]).
[NOTE]
=====
@@ -168,13 +168,14 @@ Kotlin::
You can find more information about the language semantics of the various pointcut
types in AspectJ
{aspectj-docs-progguide}/semantics-joinPoints.html[in this appendix] of the
{aspectj-docs-progguide}/index.html[AspectJ Programming Guide].
https://www.eclipse.org/aspectj/doc/next/progguide/semantics-joinPoints.html[in this
appendix] of the https://www.eclipse.org/aspectj/doc/next/progguide/index.html[AspectJ
Programming Guide].
=====
For this to work, the annotated types must be woven with the AspectJ weaver. You can
either use a build-time Ant or Maven task to do this (see, for example, the
{aspectj-docs-devguide}/antTasks.html[AspectJ Development
https://www.eclipse.org/aspectj/doc/released/devguide/antTasks.html[AspectJ Development
Environment Guide]) or load-time weaving (see xref:core/aop/using-aspectj.adoc#aop-aj-ltw[Load-time Weaving with AspectJ in the Spring Framework]). The
`AnnotationBeanConfigurerAspect` itself needs to be configured by Spring (in order to obtain
a reference to the bean factory that is to be used to configure new objects). If you
@@ -398,7 +399,7 @@ The focus of this section is on configuring and using LTW in the specific contex
Spring Framework. This section is not a general introduction to LTW. For full details on
the specifics of LTW and configuring LTW with only AspectJ (with Spring not being
involved at all), see the
{aspectj-docs-devguide}/ltw.html[LTW section of the AspectJ
https://www.eclipse.org/aspectj/doc/released/devguide/ltw.html[LTW section of the AspectJ
Development Environment Guide].
The value that the Spring Framework brings to AspectJ LTW is in enabling much
@@ -420,7 +421,7 @@ who typically are in charge of the deployment configuration, such as the launch
Now that the sales pitch is over, let us first walk through a quick example of AspectJ
LTW that uses Spring, followed by detailed specifics about elements introduced in the
example. For a complete example, see the
{spring-github-org}/spring-petclinic[Petclinic sample application].
https://github.com/spring-projects/spring-petclinic[Petclinic sample application].
[[aop-aj-ltw-first-example]]
@@ -521,8 +522,8 @@ standard AspectJ. The following example shows the `aop.xml` file:
<aspectj>
<weaver>
<!-- only weave classes in our application-specific packages and sub-packages -->
<include within="com.xyz..*"/>
<!-- only weave classes in our application-specific packages -->
<include within="com.xyz.*"/>
</weaver>
<aspects>
@@ -533,11 +534,6 @@ standard AspectJ. The following example shows the `aop.xml` file:
</aspectj>
----
NOTE: It is recommended to only weave specific classes (typically those in the
application packages, as shown in the `aop.xml` example above) in order
to avoid side effects such as AspectJ dump files and warnings.
This is also a best practice from an efficiency perspective.
Now we can move on to the Spring-specific portion of the configuration. We need
to configure a `LoadTimeWeaver` (explained later). This load-time weaver is the
essential component responsible for weaving the aspect configuration in one or
@@ -625,7 +621,7 @@ java -javaagent:C:/projects/xyz/lib/spring-instrument.jar com.xyz.Main
----
The `-javaagent` is a flag for specifying and enabling
{java-api}/java.instrument/java/lang/instrument/package-summary.html[agents
https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/package-summary.html[agents
to instrument programs that run on the JVM]. The Spring Framework ships with such an
agent, the `InstrumentationSavingAgent`, which is packaged in the
`spring-instrument.jar` that was supplied as the value of the `-javaagent` argument in
@@ -719,32 +715,13 @@ Furthermore, the compiled aspect classes need to be available on the classpath.
[[aop-aj-ltw-aop_dot_xml]]
=== `META-INF/aop.xml`
=== 'META-INF/aop.xml'
The AspectJ LTW infrastructure is configured by using one or more `META-INF/aop.xml`
files that are on the Java classpath (either directly or, more typically, in jar files).
For example:
[source,xml,indent=0,subs="verbatim"]
----
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver>
<!-- only weave classes in our application-specific packages and sub-packages -->
<include within="com.xyz..*"/>
</weaver>
</aspectj>
----
NOTE: It is recommended to only weave specific classes (typically those in the
application packages, as shown in the `aop.xml` example above) in order
to avoid side effects such as AspectJ dump files and warnings.
This is also a best practice from an efficiency perspective.
The structure and contents of this file is detailed in the LTW part of the
{aspectj-docs-devguide}/ltw-configuration.html[AspectJ reference
https://www.eclipse.org/aspectj/doc/released/devguide/ltw-configuration.html[AspectJ reference
documentation]. Because the `aop.xml` file is 100% AspectJ, we do not describe it further here.
+22 -85
View File
@@ -15,13 +15,10 @@ Applying such optimizations early implies the following restrictions:
* The classpath is fixed and fully defined at build time.
* The beans defined in your application cannot change at runtime, meaning:
** `@Profile`, in particular profile-specific configuration, needs to be chosen at build time and is automatically enabled at runtime when AOT is enabled.
** `@Profile`, in particular profile-specific configuration needs to be chosen at build time.
** `Environment` properties that impact the presence of a bean (`@Conditional`) are only considered at build time.
* Bean definitions with instance suppliers (lambdas or method references) cannot be transformed ahead-of-time.
* Beans registered as singletons (using `registerSingleton`, typically from
`ConfigurableListableBeanFactory`) cannot be transformed ahead-of-time either.
* As we cannot rely on the instance, make sure that the bean type is as precise as
possible.
* Bean definitions with instance suppliers (lambdas or method references) cannot be transformed ahead-of-time (see related https://github.com/spring-projects/spring-framework/issues/29555[spring-framework#29555] issue).
* Make sure that the bean type is as precise as possible.
TIP: See also the xref:core/aot.adoc#aot.bestpractices[] section.
@@ -30,7 +27,7 @@ A Spring AOT processed application typically generates:
* Java source code
* Bytecode (usually for dynamic proxies)
* {spring-framework-api}/aot/hint/RuntimeHints.html[`RuntimeHints`] for the use of reflection, resource loading, serialization, and JDK proxies
* {api-spring-framework}/aot/hint/RuntimeHints.html[`RuntimeHints`] for the use of reflection, resource loading, serialization, and JDK proxies.
NOTE: At the moment, AOT is focused on allowing Spring applications to be deployed as native images using GraalVM.
We intend to support more JVM-based use cases in future generations.
@@ -38,7 +35,7 @@ We intend to support more JVM-based use cases in future generations.
[[aot.basics]]
== AOT engine overview
The entry point of the AOT engine for processing an `ApplicationContext` is `ApplicationContextAotGenerator`. It takes care of the following steps, based on a `GenericApplicationContext` that represents the application to optimize and a {spring-framework-api}/aot/generate/GenerationContext.html[`GenerationContext`]:
The entry point of the AOT engine for processing an `ApplicationContext` arrangement is `ApplicationContextAotGenerator`. It takes care of the following steps, based on a `GenericApplicationContext` that represents the application to optimize and a {api-spring-framework}/aot/generate/GenerationContext.html[`GenerationContext`]:
* Refresh an `ApplicationContext` for AOT processing. Contrary to a traditional refresh, this version only creates bean definitions, not bean instances.
* Invoke the available `BeanFactoryInitializationAotProcessor` implementations and apply their contributions against the `GenerationContext`.
@@ -70,14 +67,7 @@ include-code::./AotProcessingSample[tag=aotcontext]
In this mode, xref:core/beans/factory-extension.adoc#beans-factory-extension-factory-postprocessors[`BeanFactoryPostProcessor` implementations] are invoked as usual.
This includes configuration class parsing, import selectors, classpath scanning, etc.
Such steps make sure that the `BeanRegistry` contains the relevant bean definitions for the application.
If bean definitions are guarded by conditions (such as `@Profile`), these are evaluated,
and bean definitions that don't match their conditions are discarded at this stage.
If custom code needs to register extra beans programmatically, make sure that custom
registration code uses `BeanDefinitionRegistry` instead of `BeanFactory` as only bean
definitions are taken into account. A good pattern is to implement
`ImportBeanDefinitionRegistrar` and register it via an `@Import` on one of your
configuration classes.
If bean definitions are guarded by conditions (such as `@Profile`), these are discarded at this stage.
Because this mode does not actually create bean instances, `BeanPostProcessor` implementations are not invoked, except for specific variants that are relevant for AOT processing.
These are:
@@ -91,15 +81,15 @@ Once this part completes, the `BeanFactory` contains the bean definitions that a
[[aot.bean-factory-initialization-contributions]]
== Bean Factory Initialization AOT Contributions
Components that want to participate in this step can implement the {spring-framework-api}/beans/factory/aot/BeanFactoryInitializationAotProcessor.html[`BeanFactoryInitializationAotProcessor`] interface.
Components that want to participate in this step can implement the {api-spring-framework}/beans/factory/aot/BeanFactoryInitializationAotProcessor.html[`BeanFactoryInitializationAotProcessor`] interface.
Each implementation can return an AOT contribution, based on the state of the bean factory.
An AOT contribution is a component that contributes generated code which reproduces a particular behavior.
An AOT contribution is a component that contributes generated code that reproduces a particular behavior.
It can also contribute `RuntimeHints` to indicate the need for reflection, resource loading, serialization, or JDK proxies.
A `BeanFactoryInitializationAotProcessor` implementation can be registered in `META-INF/spring/aot.factories` with a key equal to the fully-qualified name of the interface.
A `BeanFactoryInitializationAotProcessor` implementation can be registered in `META-INF/spring/aot.factories` with a key equal to the fully qualified name of the interface.
The `BeanFactoryInitializationAotProcessor` interface can also be implemented directly by a bean.
A `BeanFactoryInitializationAotProcessor` can also be implemented directly by a bean.
In this mode, the bean provides an AOT contribution equivalent to the feature it provides with a regular runtime.
Consequently, such a bean is automatically excluded from the AOT-optimized context.
@@ -121,7 +111,7 @@ This interface is used as follows:
* Implemented by a `BeanPostProcessor` bean, to replace its runtime behavior.
For instance xref:core/beans/factory-extension.adoc#beans-factory-extension-bpp-examples-aabpp[`AutowiredAnnotationBeanPostProcessor`] implements this interface to generate code that injects members annotated with `@Autowired`.
* Implemented by a type registered in `META-INF/spring/aot.factories` with a key equal to the fully-qualified name of the interface.
* Implemented by a type registered in `META-INF/spring/aot.factories` with a key equal to the fully qualified name of the interface.
Typically used when the bean definition needs to be tuned for specific features of the core framework.
[NOTE]
@@ -152,23 +142,8 @@ Java::
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
@Configuration(proxyBeanMethods = false)
class DataSourceConfiguration {
@Bean
fun dataSource() = SimpleDataSource()
}
----
======
WARNING: Kotlin class names with backticks that use invalid Java identifiers (not starting with a letter, containing spaces, etc.) are not supported.
Since there isn't any particular condition on this class, `dataSourceConfiguration` and `dataSource` are identified as candidates.
The AOT engine will convert the configuration class above to code similar to the following:
@@ -181,7 +156,6 @@ Java::
/**
* Bean definitions for {@link DataSourceConfiguration}
*/
@Generated
public class DataSourceConfiguration__BeanDefinitions {
/**
* Get the bean definition for 'dataSourceConfiguration'
@@ -216,25 +190,11 @@ Java::
NOTE: The exact code generated may differ depending on the exact nature of your bean definitions.
TIP: Each generated class is annotated with `org.springframework.aot.generate.Generated` to
identify them if they need to be excluded, for instance by static analysis tools.
The generated code above creates bean definitions equivalent to the `@Configuration` class, but in a direct way and without the use of reflection if at all possible.
There is a bean definition for `dataSourceConfiguration` and one for `dataSourceBean`.
When a `datasource` instance is required, a `BeanInstanceSupplier` is called.
This supplier invokes the `dataSource()` method on the `dataSourceConfiguration` bean.
[[aot.running]]
== Running with AOT optimizations
AOT is a mandatory step to transform a Spring application to a native executable, so it
is automatically enabled when running in this mode. It is possible to use those optimizations
on the JVM by setting the `spring.aot.enabled` System property to `true`.
NOTE: When AOT optimizations are included, some decisions that have been taken at build-time
are hard-coded in the application setup. For instance, profiles that have been enabled at
build-time are automatically enabled at runtime as well.
[[aot.bestpractices]]
== Best Practices
@@ -243,33 +203,11 @@ However, keep in mind that some optimizations are made at build time based on a
This section lists the best practices that make sure your application is ready for AOT.
[[aot.bestpractices.bean-registration]]
== Programmatic bean registration
The AOT engine takes care of the `@Configuration` model and any callback that might be
invoked as part of processing your configuration. If you need to register additional
beans programmatically, make sure to use a `BeanDefinitionRegistry` to register
bean definitions.
This can typically be done via a `BeanDefinitionRegistryPostProcessor`. Note that, if it
is registered itself as a bean, it will be invoked again at runtime unless you make
sure to implement `BeanFactoryInitializationAotProcessor` as well. A more idiomatic
way is to implement `ImportBeanDefinitionRegistrar` and register it using `@Import` on
one of your configuration classes. This invokes your custom code as part of configuration
class parsing.
If you declare additional beans programmatically using a different callback, they are
likely not going to be handled by the AOT engine, and therefore no hints are going to be
generated for them. Depending on the environment, those beans may not be registered at
all. For instance, classpath scanning does not work in a native image as there is no
notion of a classpath. For cases like this, it is crucial that the scanning happens at
build time.
[[aot.bestpractices.bean-type]]
=== Expose The Most Precise Bean Type
While your application may interact with an interface that a bean implements, it is still very important to declare the most precise type.
The AOT engine performs additional checks on the bean type, such as detecting the presence of `@Autowired` members or lifecycle callback methods.
The AOT engine performs additional checks on the bean type, such as detecting the presence of `@Autowired` members, or lifecycle callback methods.
For `@Configuration` classes, make sure that the return type of the factory `@Bean` method is as precise as possible.
Consider the following example:
@@ -320,11 +258,10 @@ If you are registering bean definitions programmatically, consider using `RootBe
[[aot.bestpractices.constructors]]
=== Avoid Multiple Constructors
The container is able to choose the most appropriate constructor to use based on several candidates.
However, this is not a best practice and flagging the preferred constructor with `@Autowired` if necessary is preferred.
In case you are working on a code base that you cannot modify, you can set the {spring-framework-api}/beans/factory/support/AbstractBeanDefinition.html#PREFERRED_CONSTRUCTORS_ATTRIBUTE[`preferredConstructors` attribute] on the related bean definition to indicate which constructor should be used.
In case you are working on a code base that you can't modify, you can set the {api-spring-framework}/beans/factory/support/AbstractBeanDefinition.html#PREFERRED_CONSTRUCTORS_ATTRIBUTE[`preferredConstructors` attribute] on the related bean definition to indicate which constructor should be used.
[[aot.bestpractices.factory-bean]]
=== FactoryBean
@@ -342,7 +279,7 @@ Java::
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
public class ClientFactoryBean<T extends AbstractClient> implements FactoryBean<T> {
// ...
}
----
======
@@ -439,10 +376,10 @@ Java::
Running an application as a native image requires additional information compared to a regular JVM runtime.
For instance, GraalVM needs to know ahead of time if a component uses reflection.
Similarly, classpath resources are not included in a native image unless specified explicitly.
Similarly, classpath resources are not shipped in a native image unless specified explicitly.
Consequently, if the application needs to load a resource, it must be referenced from the corresponding GraalVM native image configuration file.
The {spring-framework-api}/aot/hint/RuntimeHints.html[`RuntimeHints`] API collects the need for reflection, resource loading, serialization, and JDK proxies at runtime.
The {api-spring-framework}/aot/hint/RuntimeHints.html[`RuntimeHints`] API collects the need for reflection, resource loading, serialization, and JDK proxies at runtime.
The following example makes sure that `config/app.properties` can be loaded from the classpath at runtime within a native image:
[tabs]
@@ -474,16 +411,16 @@ include-code::./SpellCheckService[]
If at all possible, `@ImportRuntimeHints` should be used as close as possible to the component that requires the hints.
This way, if the component is not contributed to the `BeanFactory`, the hints won't be contributed either.
It is also possible to register an implementation statically by adding an entry in `META-INF/spring/aot.factories` with a key equal to the fully-qualified name of the `RuntimeHintsRegistrar` interface.
It is also possible to register an implementation statically by adding an entry in `META-INF/spring/aot.factories` with a key equal to the fully qualified name of the `RuntimeHintsRegistrar` interface.
[[aot.hints.reflective]]
=== `@Reflective`
{spring-framework-api}/aot/hint/annotation/Reflective.html[`@Reflective`] provides an idiomatic way to flag the need for reflection on an annotated element.
{api-spring-framework}/aot/hint/annotation/Reflective.html[`@Reflective`] provides an idiomatic way to flag the need for reflection on an annotated element.
For instance, `@EventListener` is meta-annotated with `@Reflective` since the underlying implementation invokes the annotated method using reflection.
By default, only Spring beans are considered, and an invocation hint is registered for the annotated element.
By default, only Spring beans are considered and an invocation hint is registered for the annotated element.
This can be tuned by specifying a custom `ReflectiveProcessor` implementation via the
`@Reflective` annotation.
@@ -494,7 +431,7 @@ If components other than Spring beans need to be processed, a `BeanFactoryInitia
[[aot.hints.register-reflection-for-binding]]
=== `@RegisterReflectionForBinding`
{spring-framework-api}/aot/hint/annotation/RegisterReflectionForBinding.html[`@RegisterReflectionForBinding`] is a specialization of `@Reflective` that registers the need for serializing arbitrary types.
{api-spring-framework}/aot/hint/annotation/RegisterReflectionForBinding.html[`@RegisterReflectionForBinding`] is a specialization of `@Reflective` that registers the need for serializing arbitrary types.
A typical use case is the use of DTOs that the container cannot infer, such as using a web client within a method body.
`@RegisterReflectionForBinding` can be applied to any Spring bean at the class level, but it can also be applied directly to a method, field, or constructor to better indicate where the hints are actually required.
@@ -530,7 +467,7 @@ include-code::./SpellCheckServiceTests[tag=hintspredicates]
With `RuntimeHintsPredicates`, we can check for reflection, resource, serialization, or proxy generation hints.
This approach works well for unit tests but implies that the runtime behavior of a component is well known.
You can learn more about the global runtime behavior of an application by running its test suite (or the app itself) with the {graalvm-docs}/native-image/metadata/AutomaticMetadataCollection/[GraalVM tracing agent].
You can learn more about the global runtime behavior of an application by running its test suite (or the app itself) with the {docs-graalvm}/native-image/metadata/AutomaticMetadataCollection/[GraalVM tracing agent].
This agent will record all relevant calls requiring GraalVM hints at runtime and write them out as JSON configuration files.
For more targeted discovery and testing, Spring Framework ships a dedicated module with core AOT testing utilities, `"org.springframework:spring-core-test"`.
@@ -562,4 +499,4 @@ io.spring.runtimehintstesting.SampleReflectionRuntimeHintsTests#lambda$shouldReg
There are various ways to configure this Java agent in your build, so please refer to the documentation of your build tool and test execution plugin.
The agent itself can be configured to instrument specific packages (by default, only `org.springframework` is instrumented).
You'll find more details in the {spring-framework-code}/buildSrc/README.md[Spring Framework `buildSrc` README] file.
You'll find more details in the {spring-framework-main-code}/buildSrc/README.md[Spring Framework `buildSrc` README] file.
@@ -765,7 +765,7 @@ want to add an additional attribute to the existing bean definition element.
By way of another example, suppose that you define a bean definition for a
service object that (unknown to it) accesses a clustered
{JSR}107[JCache], and you want to ensure that the
https://jcp.org/en/jsr/detail?id=107[JCache], and you want to ensure that the
named JCache instance is eagerly started within the surrounding cluster.
The following listing shows such a definition:
@@ -66,13 +66,13 @@ developer's intent ("`inject this constant value`"), and it reads better:
[[xsd-schemas-util-frfb]]
==== Setting a Bean Property or Constructor Argument from a Field Value
{spring-framework-api}/beans/factory/config/FieldRetrievingFactoryBean.html[`FieldRetrievingFactoryBean`]
{api-spring-framework}/beans/factory/config/FieldRetrievingFactoryBean.html[`FieldRetrievingFactoryBean`]
is a `FactoryBean` that retrieves a `static` or non-static field value. It is typically
used for retrieving `public` `static` `final` constants, which may then be used to set a
property value or constructor argument for another bean.
The following example shows how a `static` field is exposed, by using the
{spring-framework-api}/beans/factory/config/FieldRetrievingFactoryBean.html#setStaticField(java.lang.String)[`staticField`]
{api-spring-framework}/beans/factory/config/FieldRetrievingFactoryBean.html#setStaticField(java.lang.String)[`staticField`]
property:
[source,xml,indent=0,subs="verbatim,quotes"]
@@ -109,7 +109,7 @@ to be specified for the bean reference, as the following example shows:
You can also access a non-static (instance) field of another bean, as
described in the API documentation for the
{spring-framework-api}/beans/factory/config/FieldRetrievingFactoryBean.html[`FieldRetrievingFactoryBean`]
{api-spring-framework}/beans/factory/config/FieldRetrievingFactoryBean.html[`FieldRetrievingFactoryBean`]
class.
Injecting enumeration values into beans as either property or constructor arguments is
@@ -17,7 +17,7 @@ No matter the choice, Spring can accommodate both styles and even mix them toget
It is worth pointing out that through its xref:core/beans/java.adoc[JavaConfig] option, Spring lets
annotations be used in a non-invasive way, without touching the target components'
source code and that, in terms of tooling, all configuration styles are supported by
{spring-site-tools}[Spring Tools] for Eclipse, Visual Studio Code, and Theia.
https://spring.io/tools[Spring Tools] for Eclipse, Visual Studio Code, and Theia.
****
An alternative to XML setup is provided by annotation-based configuration, which relies
@@ -62,11 +62,11 @@ configuration (notice the inclusion of the `context` namespace):
The `<context:annotation-config/>` element implicitly registers the following post-processors:
* {spring-framework-api}/context/annotation/ConfigurationClassPostProcessor.html[`ConfigurationClassPostProcessor`]
* {spring-framework-api}/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.html[`AutowiredAnnotationBeanPostProcessor`]
* {spring-framework-api}/context/annotation/CommonAnnotationBeanPostProcessor.html[`CommonAnnotationBeanPostProcessor`]
* {spring-framework-api}/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.html[`PersistenceAnnotationBeanPostProcessor`]
* {spring-framework-api}/context/event/EventListenerMethodProcessor.html[`EventListenerMethodProcessor`]
* {api-spring-framework}/context/annotation/ConfigurationClassPostProcessor.html[`ConfigurationClassPostProcessor`]
* {api-spring-framework}/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.html[`AutowiredAnnotationBeanPostProcessor`]
* {api-spring-framework}/context/annotation/CommonAnnotationBeanPostProcessor.html[`CommonAnnotationBeanPostProcessor`]
* {api-spring-framework}/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.html[`PersistenceAnnotationBeanPostProcessor`]
* {api-spring-framework}/context/event/EventListenerMethodProcessor.html[`EventListenerMethodProcessor`]
[NOTE]
====
@@ -155,8 +155,6 @@ If there is no other resolution indicator (such as a qualifier or a primary mark
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.
Since version 6.1, this requires the `-parameters` Java compiler flag to be present.
====
That said, if you intend to express annotation-driven injection by name, do not
@@ -1,7 +1,7 @@
[[beans-custom-autowire-configurer]]
= Using `CustomAutowireConfigurer`
{spring-framework-api}/beans/factory/annotation/CustomAutowireConfigurer.html[`CustomAutowireConfigurer`]
{api-spring-framework}/beans/factory/annotation/CustomAutowireConfigurer.html[`CustomAutowireConfigurer`]
is a `BeanFactoryPostProcessor` that lets you register your own custom qualifier
annotation types, even if they are not annotated with Spring's `@Qualifier` annotation.
The following example shows how to use `CustomAutowireConfigurer`:
@@ -84,7 +84,7 @@ Kotlin::
NOTE: The name provided with the annotation is resolved as a bean name by the
`ApplicationContext` of which the `CommonAnnotationBeanPostProcessor` is aware.
The names can be resolved through JNDI if you configure Spring's
{spring-framework-api}/jndi/support/SimpleJndiBeanFactory.html[`SimpleJndiBeanFactory`]
{api-spring-framework}/jndi/support/SimpleJndiBeanFactory.html[`SimpleJndiBeanFactory`]
explicitly. However, we recommend that you rely on the default behavior and
use Spring's JNDI lookup capabilities to preserve the level of indirection.
@@ -12,8 +12,8 @@ between those objects.
Several implementations of the `ApplicationContext` interface are supplied
with Spring. In stand-alone applications, it is common to create an
instance of
{spring-framework-api}/context/support/ClassPathXmlApplicationContext.html[`ClassPathXmlApplicationContext`]
or {spring-framework-api}/context/support/FileSystemXmlApplicationContext.html[`FileSystemXmlApplicationContext`].
{api-spring-framework}/context/support/ClassPathXmlApplicationContext.html[`ClassPathXmlApplicationContext`]
or {api-spring-framework}/context/support/FileSystemXmlApplicationContext.html[`FileSystemXmlApplicationContext`].
While XML has been the traditional format for defining configuration metadata, you can
instruct the container to use Java annotations or code as the metadata format by
providing a small amount of XML configuration to declaratively enable support for these
@@ -24,7 +24,7 @@ more instances of a Spring IoC container. For example, in a web application scen
simple eight (or so) lines of boilerplate web descriptor XML in the `web.xml` file
of the application typically suffices (see
xref:core/beans/context-introduction.adoc#context-create[Convenient ApplicationContext Instantiation for Web Applications]).
If you use the {spring-site-tools}[Spring Tools for Eclipse] (an Eclipse-powered
If you use the https://spring.io/tools[Spring Tools for Eclipse] (an Eclipse-powered
development environment), you can easily create this boilerplate configuration with a
few mouse clicks or keystrokes.
@@ -61,10 +61,10 @@ For information about using other forms of metadata with the Spring container, s
annotation-based configuration metadata.
* xref:core/beans/java.adoc[Java-based configuration]: define beans external to your application
classes by using Java rather than XML files. To use these features, see the
{spring-framework-api}/context/annotation/Configuration.html[`@Configuration`],
{spring-framework-api}/context/annotation/Bean.html[`@Bean`],
{spring-framework-api}/context/annotation/Import.html[`@Import`],
and {spring-framework-api}/context/annotation/DependsOn.html[`@DependsOn`] annotations.
{api-spring-framework}/context/annotation/Configuration.html[`@Configuration`],
{api-spring-framework}/context/annotation/Bean.html[`@Bean`],
{api-spring-framework}/context/annotation/Import.html[`@Import`],
and {api-spring-framework}/context/annotation/DependsOn.html[`@DependsOn`] annotations.
Spring configuration consists of at least one and typically more than one bean
definition that the container must manage. XML-based configuration metadata configures these
@@ -141,7 +141,7 @@ Kotlin::
====
After you learn about Spring's IoC container, you may want to know more about Spring's
`Resource` abstraction (as described in
xref:core/resources.adoc[Resources])
xref:web/webflux-webclient/client-builder.adoc#webflux-client-builder-reactor-resources[Resources])
which provides a convenient mechanism for reading an InputStream from locations defined
in a URI syntax. In particular, `Resource` paths are used to construct applications contexts,
as described in xref:core/resources.adoc#resources-app-ctx[Application Contexts and Resource Paths].
@@ -191,7 +191,7 @@ Kotlin::
======
For further details, see the
{spring-framework-wiki}/Spring-Annotation-Programming-Model[Spring Annotation Programming Model]
https://github.com/spring-projects/spring-framework/wiki/Spring-Annotation-Programming-Model[Spring Annotation Programming Model]
wiki page.
@@ -315,7 +315,7 @@ entries in the classpath. When you build JARs with Ant, make sure that you do no
activate the files-only switch of the JAR task. Also, classpath directories may not be
exposed based on security policies in some environments -- for example, standalone apps on
JDK 1.7.0_45 and higher (which requires 'Trusted-Library' setup in your manifests -- see
{stackoverflow-questions}/19394570/java-jre-7u45-breaks-classloader-getresources).
https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources).
On JDK 9's module path (Jigsaw), Spring's classpath scanning generally works as expected.
However, make sure that your component classes are exported in your `module-info`
@@ -743,7 +743,7 @@ Kotlin::
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`]
{api-spring-framework}/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.
@@ -840,7 +840,7 @@ 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`]
{api-spring-framework}/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:
@@ -4,7 +4,7 @@
As discussed in the xref:web/webmvc-view/mvc-xslt.adoc#mvc-view-xslt-beandefs[chapter introduction], the `org.springframework.beans.factory`
package provides basic functionality for managing and manipulating beans, including in a
programmatic way. The `org.springframework.context` package adds the
{spring-framework-api}/context/ApplicationContext.html[`ApplicationContext`]
{api-spring-framework}/context/ApplicationContext.html[`ApplicationContext`]
interface, which extends the `BeanFactory` interface, in addition to extending other
interfaces to provide additional functionality in a more application
framework-oriented style. Many people use the `ApplicationContext` in a completely
@@ -269,7 +269,7 @@ file format but is more flexible than the standard JDK based
`ResourceBundleMessageSource` implementation. In particular, it allows for reading
files from any Spring resource location (not only from the classpath) and supports hot
reloading of bundle property files (while efficiently caching them in between).
See the {spring-framework-api}/context/support/ReloadableResourceBundleMessageSource.html[`ReloadableResourceBundleMessageSource`]
See the {api-spring-framework}/context/support/ReloadableResourceBundleMessageSource.html[`ReloadableResourceBundleMessageSource`]
javadoc for details.
@@ -485,8 +485,8 @@ This means that the `publishEvent()` method blocks until all listeners have fini
One advantage of this synchronous and single-threaded approach is that, when a listener receives an event,
it operates inside the transaction context of the publisher if a transaction context is available.
If another strategy for event publication becomes necessary, e.g. asynchronous event processing by default,
see the javadoc for Spring's {spring-framework-api}/context/event/ApplicationEventMulticaster.html[`ApplicationEventMulticaster`] interface
and {spring-framework-api}/context/event/SimpleApplicationEventMulticaster.html[`SimpleApplicationEventMulticaster`] implementation
see the javadoc for Spring's {api-spring-framework}/context/event/ApplicationEventMulticaster.html[`ApplicationEventMulticaster`] interface
and {api-spring-framework}/context/event/SimpleApplicationEventMulticaster.html[`SimpleApplicationEventMulticaster`] implementation
for configuration options which can be applied to a custom "applicationEventMulticaster" bean definition.
In these cases, ThreadLocals and logging context are not propagated for the event processing.
See xref:integration/observability.adoc#observability.application-events[the `@EventListener` Observability section]
@@ -529,7 +529,7 @@ notify appropriate parties.
NOTE: Spring's eventing mechanism is designed for simple communication between Spring beans
within the same application context. However, for more sophisticated enterprise
integration needs, the separately maintained
{spring-site-projects}/spring-integration/[Spring Integration] project provides
https://projects.spring.io/spring-integration/[Spring Integration] project provides
complete support for building lightweight,
https://www.enterpriseintegrationpatterns.com[pattern-oriented], event-driven
architectures that build upon the well-known Spring programming model.
@@ -742,11 +742,11 @@ Be aware of the following limitations when using asynchronous events:
* If an asynchronous event listener throws an `Exception`, it is not propagated to the
caller. See
{spring-framework-api}/aop/interceptor/AsyncUncaughtExceptionHandler.html[`AsyncUncaughtExceptionHandler`]
{api-spring-framework}/aop/interceptor/AsyncUncaughtExceptionHandler.html[`AsyncUncaughtExceptionHandler`]
for more details.
* Asynchronous event listener methods cannot publish a subsequent event by returning a
value. If you need to publish another event as the result of the processing, inject an
{spring-framework-api}/context/ApplicationEventPublisher.html[`ApplicationEventPublisher`]
{api-spring-framework}/context/ApplicationEventPublisher.html[`ApplicationEventPublisher`]
to publish the event manually.
* ThreadLocals and logging context are not propagated by default for the event processing.
See xref:integration/observability.adoc#observability.application-events[the `@EventListener` Observability section]
@@ -1040,7 +1040,7 @@ and JMX support facilities. Application components can also interact with the ap
server's JCA `WorkManager` through Spring's `TaskExecutor` abstraction.
See the javadoc of the
{spring-framework-api}/jca/context/SpringContextResourceAdapter.html[`SpringContextResourceAdapter`]
{api-spring-framework}/jca/context/SpringContextResourceAdapter.html[`SpringContextResourceAdapter`]
class for the configuration details involved in RAR deployment.
For a simple deployment of a Spring ApplicationContext as a Jakarta EE RAR file:
@@ -1050,7 +1050,7 @@ all application classes into a RAR file (which is a standard JAR file with a dif
file extension).
. Add all required library JARs into the root of the RAR archive.
. Add a
`META-INF/ra.xml` deployment descriptor (as shown in the {spring-framework-api}/jca/context/SpringContextResourceAdapter.html[javadoc for `SpringContextResourceAdapter`])
`META-INF/ra.xml` deployment descriptor (as shown in the {api-spring-framework}/jca/context/SpringContextResourceAdapter.html[javadoc for `SpringContextResourceAdapter`])
and the corresponding Spring XML bean definition file(s) (typically
`META-INF/applicationContext.xml`).
. Drop the resulting RAR file into your
@@ -44,7 +44,7 @@ weaver instance. This is particularly useful in combination with
xref:data-access/orm/jpa.adoc[Spring's JPA support] where load-time weaving may be
necessary for JPA class transformation.
Consult the
{spring-framework-api}/orm/jpa/LocalContainerEntityManagerFactoryBean.html[`LocalContainerEntityManagerFactoryBean`]
{api-spring-framework}/orm/jpa/LocalContainerEntityManagerFactoryBean.html[`LocalContainerEntityManagerFactoryBean`]
javadoc for more detail. For more on AspectJ load-time weaving, see xref:core/aop/using-aspectj.adoc#aop-aj-ltw[Load-time Weaving with AspectJ in the Spring Framework].
@@ -99,7 +99,7 @@ container, lets you handle this use case cleanly.
****
You can read more about the motivation for Method Injection in
{spring-site-blog}/2004/08/06/method-injection/[this blog entry].
https://spring.io/blog/2004/08/06/method-injection/[this blog entry].
****
@@ -51,7 +51,7 @@ 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 for Eclipse])
IDEA] or the https://spring.io/tools[Spring Tools for Eclipse])
that supports automatic property completion when you create bean definitions. Such IDE
assistance is highly recommended.
@@ -1,7 +1,7 @@
[[beans-environment]]
= Environment Abstraction
The {spring-framework-api}/core/env/Environment.html[`Environment`] interface
The {api-spring-framework}/core/env/Environment.html[`Environment`] interface
is an abstraction integrated in the container that models two key
aspects of the application environment: xref:core/beans/environment.adoc#beans-definition-profiles[profiles]
and xref:core/beans/environment.adoc#beans-property-source-abstraction[properties].
@@ -118,7 +118,7 @@ situation B. We start by updating our configuration to reflect this need.
[[beans-definition-profiles-java]]
=== Using `@Profile`
The {spring-framework-api}/context/annotation/Profile.html[`@Profile`]
The {api-spring-framework}/context/annotation/Profile.html[`@Profile`]
annotation lets you indicate that a component is eligible for registration
when one or more specified profiles are active. Using our preceding example, we
can rewrite the `dataSource` configuration as follows:
@@ -516,8 +516,8 @@ as the following example shows:
[[beans-definition-profiles-default]]
=== Default Profile
The default profile represents the profile that is enabled if no profile is active. Consider
the following example:
The default profile represents the profile that is enabled by default. Consider the
following example:
[tabs]
======
@@ -558,9 +558,9 @@ Kotlin::
----
======
If xref:#beans-definition-profiles-enable[no profile is active], the `dataSource` is
created. You can see this as a way to provide a default definition for one or more
beans. If any profile is enabled, the default profile does not apply.
If no profile is active, the `dataSource` is created. You can see this
as a way to provide a default definition for one or more beans. If any
profile is enabled, the default profile does not apply.
The name of the default profile is `default`. You can change the name of
the default profile by using `setDefaultProfiles()` on the `Environment` or,
@@ -599,17 +599,17 @@ Kotlin::
In the preceding snippet, we see a high-level way of asking Spring whether the `my-property` property is
defined for the current environment. To answer this question, the `Environment` object performs
a search over a set of {spring-framework-api}/core/env/PropertySource.html[`PropertySource`]
a search over a set of {api-spring-framework}/core/env/PropertySource.html[`PropertySource`]
objects. A `PropertySource` is a simple abstraction over any source of key-value pairs, and
Spring's {spring-framework-api}/core/env/StandardEnvironment.html[`StandardEnvironment`]
Spring's {api-spring-framework}/core/env/StandardEnvironment.html[`StandardEnvironment`]
is configured with two PropertySource objects -- one representing the set of JVM system properties
(`System.getProperties()`) and one representing the set of system environment variables
(`System.getenv()`).
NOTE: These default property sources are present for `StandardEnvironment`, for use in standalone
applications. {spring-framework-api}/web/context/support/StandardServletEnvironment.html[`StandardServletEnvironment`]
applications. {api-spring-framework}/web/context/support/StandardServletEnvironment.html[`StandardServletEnvironment`]
is populated with additional default property sources including servlet config, servlet
context parameters, and a {spring-framework-api}/jndi/JndiPropertySource.html[`JndiPropertySource`]
context parameters, and a {api-spring-framework}/jndi/JndiPropertySource.html[`JndiPropertySource`]
if JNDI is available.
Concretely, when you use the `StandardEnvironment`, the call to `env.containsProperty("my-property")`
@@ -663,7 +663,7 @@ Kotlin::
In the preceding code, `MyPropertySource` has been added with highest precedence in the
search. If it contains a `my-property` property, the property is detected and returned, in favor of
any `my-property` property in any other `PropertySource`. The
{spring-framework-api}/core/env/MutablePropertySources.html[`MutablePropertySources`]
{api-spring-framework}/core/env/MutablePropertySources.html[`MutablePropertySources`]
API exposes a number of methods that allow for precise manipulation of the set of
property sources.
@@ -672,7 +672,7 @@ property sources.
[[beans-using-propertysource]]
== Using `@PropertySource`
The {spring-framework-api}/context/annotation/PropertySource.html[`@PropertySource`]
The {api-spring-framework}/context/annotation/PropertySource.html[`@PropertySource`]
annotation provides a convenient and declarative mechanism for adding a `PropertySource`
to Spring's `Environment`.
@@ -22,8 +22,8 @@ in which these `BeanPostProcessor` instances run by setting the `order` property
You can set this property only if the `BeanPostProcessor` implements the `Ordered`
interface. If you write your own `BeanPostProcessor`, you should consider implementing
the `Ordered` interface, too. For further details, see the javadoc of the
{spring-framework-api}/beans/factory/config/BeanPostProcessor.html[`BeanPostProcessor`]
and {spring-framework-api}/core/Ordered.html[`Ordered`] interfaces. See also the note on
{api-spring-framework}/beans/factory/config/BeanPostProcessor.html[`BeanPostProcessor`]
and {api-spring-framework}/core/Ordered.html[`Ordered`] interfaces. See also the note on
xref:core/beans/factory-extension.adoc#beans-factory-programmatically-registering-beanpostprocessors[programmatic registration of `BeanPostProcessor` instances].
[NOTE]
@@ -272,8 +272,8 @@ which these `BeanFactoryPostProcessor` instances run by setting the `order` prop
However, you can only set this property if the `BeanFactoryPostProcessor` implements the
`Ordered` interface. If you write your own `BeanFactoryPostProcessor`, you should
consider implementing the `Ordered` interface, too. See the javadoc of the
{spring-framework-api}/beans/factory/config/BeanFactoryPostProcessor.html[`BeanFactoryPostProcessor`]
and {spring-framework-api}/core/Ordered.html[`Ordered`] interfaces for more details.
{api-spring-framework}/beans/factory/config/BeanFactoryPostProcessor.html[`BeanFactoryPostProcessor`]
and {api-spring-framework}/core/Ordered.html[`Ordered`] interfaces for more details.
[NOTE]
====
@@ -592,40 +592,6 @@ Kotlin::
[[beans-factory-thread-safety]]
=== Thread Safety and Visibility
The Spring core container publishes created singleton instances in a thread-safe manner,
guarding access through a singleton lock and guaranteeing visibility in other threads.
As a consequence, application-provided bean classes do not have to be concerned about the
visibility of their initialization state. Regular configuration fields do not have to be
marked as `volatile` as long as they are only mutated during the initialization phase,
providing visibility guarantees similar to `final` even for setter-based configuration
state that is mutable during that initial phase. If such fields get changed after the
bean creation phase and its subsequent initial publication, they need to be declared as
`volatile` or guarded by a common lock whenever accessed.
Note that concurrent access to such configuration state in singleton bean instances,
e.g. for controller instances or repository instances, is perfectly thread-safe after
such safe initial publication from the container side. This includes common singleton
`FactoryBean` instances which are processed within the general singleton lock as well.
For destruction callbacks, the configuration state remains thread-safe but any runtime
state accumulated between initialization and destruction should be kept in thread-safe
structures (or in `volatile` fields for simple cases) as per common Java guidelines.
Deeper `Lifecycle` integration as shown above involves runtime-mutable state such as
a `runnable` field which will have to be declared as `volatile`. While the common
lifecycle callbacks follow a certain order, e.g. a start callback is guaranteed to
only happen after full initialization and a stop callback only after an initial start,
there is a special case with the common stop before destroy arrangement: It is strongly
recommended that the internal state in any such bean also allows for an immediate
destroy callback without a preceding stop since this may happen during an extraordinary
shutdown after a cancelled bootstrap or in case of a stop timeout caused by another bean.
[[beans-factory-aware]]
== `ApplicationContextAware` and `BeanNameAware`
@@ -51,7 +51,7 @@ The following table describes the supported scopes:
NOTE: A thread scope is available but is not registered by default. For more information,
see the documentation for
{spring-framework-api}/context/support/SimpleThreadScope.html[`SimpleThreadScope`].
{api-spring-framework}/context/support/SimpleThreadScope.html[`SimpleThreadScope`].
For instructions on how to register this or any other custom scope, see
xref:core/beans/factory-scopes.adoc#beans-factory-scopes-custom-using[Using a Custom Scope].
@@ -324,6 +324,7 @@ Kotlin::
[[beans-factory-scopes-application]]
=== Application Scope
@@ -373,6 +374,7 @@ Kotlin::
[[beans-factory-scopes-websocket]]
=== WebSocket Scope
@@ -382,6 +384,7 @@ xref:web/websocket/stomp/scope.adoc[WebSocket scope] for more details.
[[beans-factory-scopes-other-injection]]
=== Scoped Beans as Dependencies
@@ -541,19 +544,6 @@ see xref:core/aop/proxying.adoc[Proxying Mechanisms].
[[beans-factory-scopes-injection]]
=== Injecting Request/Session References Directly
As an alternative to factory scopes, a Spring `WebApplicationContext` also supports
the injection of `HttpServletRequest`, `HttpServletResponse`, `HttpSession`,
`WebRequest` and (if JSF is present) `FacesContext` and `ExternalContext` into
Spring-managed beans, simply through type-based autowiring next to regular injection
points for other beans. Spring generally injects proxies for such request and session
objects which has the advantage of working in singleton beans and serializable beans
as well, similar to scoped proxies for factory-scoped beans.
[[beans-factory-scopes-custom]]
== Custom Scopes
@@ -569,7 +559,7 @@ To integrate your custom scopes into the Spring container, you need to implement
`org.springframework.beans.factory.config.Scope` interface, which is described in this
section. For an idea of how to implement your own scopes, see the `Scope`
implementations that are supplied with the Spring Framework itself and the
{spring-framework-api}/beans/factory/config/Scope.html[`Scope`] javadoc,
{api-spring-framework}/beans/factory/config/Scope.html[`Scope`] javadoc,
which explains the methods you need to implement in more detail.
The `Scope` interface has four methods to get objects from the scope, remove them from
@@ -639,7 +629,7 @@ Kotlin::
----
======
See the {spring-framework-api}/beans/factory/config/Scope.html#registerDestructionCallback[javadoc]
See the {api-spring-framework}/beans/factory/config/Scope.html#registerDestructionCallback[javadoc]
or a Spring scope implementation for more information on destruction callbacks.
The following method obtains the conversation identifier for the underlying scope:
@@ -1,22 +1,22 @@
[[beans-introduction]]
= Introduction to the Spring IoC Container and Beans
This chapter covers the Spring Framework implementation of the Inversion of Control (IoC)
principle. Dependency injection (DI) is a specialized form of IoC, whereby objects define
their dependencies (that is, the other objects they work with) only through constructor
arguments, arguments to a factory method, or properties that are set on the object
instance after it is constructed or returned from a factory method. The IoC container
This chapter covers the Spring Framework implementation of the Inversion of Control
(IoC) principle. IoC is also known as dependency injection (DI). It is a process whereby
objects define their dependencies (that is, the other objects they work with) only through
constructor arguments, arguments to a factory method, or properties that are set on the
object instance after it is constructed or returned from a factory method. The container
then injects those dependencies when it creates the bean. This process is fundamentally
the inverse (hence the name, Inversion of Control) of the bean itself controlling the
instantiation or location of its dependencies by using direct construction of classes or
a mechanism such as the Service Locator pattern.
the inverse (hence the name, Inversion of Control) of the bean itself
controlling the instantiation or location of its dependencies by using direct
construction of classes or a mechanism such as the Service Locator pattern.
The `org.springframework.beans` and `org.springframework.context` packages are the basis
for Spring Framework's IoC container. The
{spring-framework-api}/beans/factory/BeanFactory.html[`BeanFactory`]
{api-spring-framework}/beans/factory/BeanFactory.html[`BeanFactory`]
interface provides an advanced configuration mechanism capable of managing any type of
object.
{spring-framework-api}/context/ApplicationContext.html[`ApplicationContext`]
{api-spring-framework}/context/ApplicationContext.html[`ApplicationContext`]
is a sub-interface of `BeanFactory`. It adds:
* Easier integration with Spring's AOP features
@@ -3,5 +3,17 @@
:page-section-summary-toc: 1
This section covers how to use annotations in your Java code to configure the Spring
container.
container. It includes the following topics:
* xref:core/beans/java/basic-concepts.adoc[Basic Concepts: `@Bean` and `@Configuration`]
* xref:core/beans/java/instantiating-container.adoc[Instantiating the Spring Container by Using `AnnotationConfigApplicationContext`]
* xref:core/beans/java/bean-annotation.adoc[Using the `@Bean` Annotation]
* xref:core/beans/java/configuration-annotation.adoc[Using the `@Configuration` annotation]
* xref:core/beans/java/composing-configuration-classes.adoc[Composing Java-based Configurations]
* xref:core/beans/environment.adoc#beans-definition-profiles[Bean Definition Profiles]
* xref:core/beans/environment.adoc#beans-property-source-abstraction[`PropertySource` Abstraction]
* xref:core/beans/environment.adoc#beans-using-propertysource[Using `@PropertySource`]
* xref:core/beans/environment.adoc#beans-placeholder-resolution-in-statements[Placeholder Resolution in Statements]
@@ -552,7 +552,7 @@ Sometimes, it is helpful to provide a more detailed textual description of a bea
be particularly useful when beans are exposed (perhaps through JMX) for monitoring purposes.
To add a description to a `@Bean`, you can use the
{spring-framework-api}/context/annotation/Description.html[`@Description`]
{api-spring-framework}/context/annotation/Description.html[`@Description`]
annotation, as the following example shows:
[tabs]
@@ -225,7 +225,7 @@ Also, be particularly careful with `BeanPostProcessor` and `BeanFactoryPostProce
through `@Bean`. Those should usually be declared as `static @Bean` methods, not triggering the
instantiation of their containing configuration class. Otherwise, `@Autowired` and `@Value` may not
work on the configuration class itself, since it is possible to create it as a bean instance earlier than
{spring-framework-api}/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.html[`AutowiredAnnotationBeanPostProcessor`].
{api-spring-framework}/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.html[`AutowiredAnnotationBeanPostProcessor`].
====
The following example shows how one bean can be autowired to another bean:
@@ -338,7 +338,7 @@ modularity, but determining exactly where the autowired bean definitions are dec
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. Remember that the
{spring-site-tools}[Spring Tools for Eclipse] provides tooling that
https://spring.io/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.
@@ -519,7 +519,7 @@ profile has been enabled in the Spring `Environment` (see xref:core/beans/enviro
for details).
The `@Profile` annotation is actually implemented by using a much more flexible annotation
called {spring-framework-api}/context/annotation/Conditional.html[`@Conditional`].
called {api-spring-framework}/context/annotation/Conditional.html[`@Conditional`].
The `@Conditional` annotation indicates specific
`org.springframework.context.annotation.Condition` implementations that should be
consulted before a `@Bean` is registered.
@@ -570,7 +570,7 @@ Kotlin::
----
======
See the {spring-framework-api}/context/annotation/Conditional.html[`@Conditional`]
See the {api-spring-framework}/context/annotation/Conditional.html[`@Conditional`]
javadoc for more detail.
@@ -278,7 +278,7 @@ init-param):
NOTE: For programmatic use cases, a `GenericWebApplicationContext` can be used as an
alternative to `AnnotationConfigWebApplicationContext`. See the
{spring-framework-api}/web/context/support/GenericWebApplicationContext.html[`GenericWebApplicationContext`]
{api-spring-framework}/web/context/support/GenericWebApplicationContext.html[`GenericWebApplicationContext`]
javadoc for details.
@@ -56,7 +56,7 @@ alternate between read and write.
== `PooledDataBuffer`
As explained in the Javadoc for
{java-api}/java.base/java/nio/ByteBuffer.html[ByteBuffer],
https://docs.oracle.com/javase/8/docs/api/java/nio/ByteBuffer.html[ByteBuffer],
byte buffers can be direct or non-direct. Direct buffers may reside outside the Java heap
which eliminates the need for copying for native I/O operations. That makes direct buffers
particularly useful for receiving and sending data over a socket, but they're also more
@@ -1,18 +1,17 @@
[[expressions]]
= Spring Expression Language (SpEL)
The Spring Expression Language ("SpEL" for short) is a powerful expression language that
The Spring Expression Language ("`SpEL`" for short) is a powerful expression language that
supports querying and manipulating an object graph at runtime. The language syntax is
similar to the https://jakarta.ee/specifications/expression-language/[Jakarta Expression
Language] but offers additional features, most notably method invocation and basic string
templating functionality.
similar to Unified EL 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 support within the {spring-site-tools}[Spring Tools for Eclipse].
for code completion support within the https://spring.io/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.
@@ -34,24 +33,26 @@ populate them are listed at the end of the chapter.
The expression language supports the following functionality:
* Literal expressions
* Boolean and relational operators
* Regular expressions
* Class expressions
* Accessing properties, arrays, lists, and maps
* Method invocation
* Assignment
* Calling constructors
* Bean references
* Array construction
* Inline lists
* Inline maps
* Array construction
* Relational operators
* Regular expressions
* Logical operators
* String operators
* Mathematical operators
* Assignment
* Type expressions
* Method invocation
* Constructor invocation
* Ternary operator
* Variables
* User-defined functions
* Bean references
* Ternary, Elvis, and safe-navigation operators
* User-defined functions added to the context
* reflective invocation of `Method`
* various cases of `MethodHandle`
* Collection projection
* Collection selection
* Templated expressions
@@ -1,12 +1,12 @@
[[expressions-evaluation]]
= Evaluation
This section introduces programmatic use of SpEL's interfaces and its expression language.
The complete language reference can be found in the
This section introduces the simple use of SpEL interfaces and its expression language.
The complete language reference can be found in
xref:core/expressions/language-ref.adoc[Language Reference].
The following code demonstrates how to use the SpEL API to evaluate the literal string
expression, `Hello World`.
The following code introduces the SpEL API to evaluate the literal string expression,
`Hello World`.
[tabs]
======
@@ -18,7 +18,7 @@ Java::
Expression exp = parser.parseExpression("'Hello World'"); // <1>
String message = (String) exp.getValue();
----
<1> The value of the message variable is `"Hello World"`.
<1> The value of the message variable is `'Hello World'`.
Kotlin::
+
@@ -28,24 +28,24 @@ Kotlin::
val exp = parser.parseExpression("'Hello World'") // <1>
val message = exp.value as String
----
<1> The value of the message variable is `"Hello World"`.
<1> The value of the message variable is `'Hello World'`.
======
The SpEL classes and interfaces you are most likely to use are located in the
`org.springframework.expression` package and its sub-packages, such as `spel.support`.
The `ExpressionParser` interface is responsible for parsing an expression string. In the
preceding example, the expression string is a string literal denoted by the surrounding
single quotation marks. The `Expression` interface is responsible for evaluating the
defined expression string. The two types of exceptions that can be thrown when calling
`parser.parseExpression(...)` and `exp.getValue(...)` are `ParseException` and
`EvaluationException`, respectively.
The `ExpressionParser` interface is responsible for parsing an expression string. In
the preceding example, the expression string is a string literal denoted by the surrounding single
quotation marks. The `Expression` interface is responsible for evaluating the previously defined
expression string. Two exceptions that can be thrown, `ParseException` and
`EvaluationException`, when calling `parser.parseExpression` and `exp.getValue`,
respectively.
SpEL supports a wide range of features such as calling methods, accessing properties,
SpEL supports a wide range of features, such as calling methods, accessing properties,
and calling constructors.
In the following method invocation example, we call the `concat` method on the string
literal, `Hello World`.
In the following example of method invocation, we call the `concat` method on the string literal:
[tabs]
======
@@ -57,7 +57,7 @@ Java::
Expression exp = parser.parseExpression("'Hello World'.concat('!')"); // <1>
String message = (String) exp.getValue();
----
<1> The value of `message` is now `"Hello World!"`.
<1> The value of `message` is now 'Hello World!'.
Kotlin::
+
@@ -67,11 +67,10 @@ Kotlin::
val exp = parser.parseExpression("'Hello World'.concat('!')") // <1>
val message = exp.value as String
----
<1> The value of `message` is now `"Hello World!"`.
<1> The value of `message` is now 'Hello World!'.
======
The following example demonstrates how to access the `Bytes` JavaBean property of the
string literal, `Hello World`.
The following example of calling a JavaBean property calls the `String` property `Bytes`:
[tabs]
======
@@ -101,10 +100,10 @@ Kotlin::
======
SpEL also supports nested properties by using the standard dot notation (such as
`prop1.prop2.prop3`) as well as the corresponding setting of property values.
`prop1.prop2.prop3`) and also the corresponding setting of property values.
Public fields may also be accessed.
The following example shows how to use dot notation to get the length of a string literal.
The following example shows how to use dot notation to get the length of a literal:
[tabs]
======
@@ -134,7 +133,7 @@ Kotlin::
======
The String's constructor can be called instead of using a string literal, as the following
example shows.
example shows:
[tabs]
======
@@ -146,7 +145,7 @@ Java::
Expression exp = parser.parseExpression("new String('hello world').toUpperCase()"); // <1>
String message = exp.getValue(String.class);
----
<1> Construct a new `String` from the literal and convert it to upper case.
<1> Construct a new `String` from the literal and make it be upper case.
Kotlin::
+
@@ -156,9 +155,10 @@ Kotlin::
val exp = parser.parseExpression("new String('hello world').toUpperCase()") // <1>
val message = exp.getValue(String::class.java)
----
<1> Construct a new `String` from the literal and convert it to upper case.
<1> Construct a new `String` from the literal and make it be upper case.
======
Note the use of the generic method: `public <T> T getValue(Class<T> desiredResultType)`.
Using this method removes the need to cast the value of the expression to the desired
result type. An `EvaluationException` is thrown if the value cannot be cast to the
@@ -166,8 +166,8 @@ type `T` or converted by using the registered type converter.
The more common usage of SpEL is to provide an expression string that is evaluated
against a specific object instance (called the root object). The following example shows
how to retrieve the `name` property from an instance of the `Inventor` class and how to
reference the `name` property in a boolean expression.
how to retrieve the `name` property from an instance of the `Inventor` class or
create a boolean condition:
[tabs]
======
@@ -240,7 +240,7 @@ It excludes Java type references, constructors, and bean references. It also req
you to explicitly choose the level of support for properties and methods in expressions.
By default, the `create()` static factory method enables only read access to properties.
You can also obtain a builder to configure the exact level of support needed, targeting
one or some combination of the following.
one or some combination of the following:
* Custom `PropertyAccessor` only (no reflection)
* Data binding properties for read-only access
@@ -252,15 +252,16 @@ one or some combination of the following.
By default, SpEL uses the conversion service available in Spring core
(`org.springframework.core.convert.ConversionService`). This conversion service comes
with many built-in converters for common conversions, but is also fully extensible so
that you can add custom conversions between types. Additionally, it is generics-aware.
This means that, when you work with generic types in expressions, SpEL attempts
conversions to maintain type correctness for any objects it encounters.
with many built-in converters for common conversions but is also fully extensible so that
you can add custom conversions between types. Additionally, it is
generics-aware. This means that, when you work with generic types in
expressions, SpEL attempts conversions to maintain type correctness for any objects
it encounters.
What does this mean in practice? Suppose assignment, using `setValue()`, is being used
to set a `List` property. The type of the property is actually `List<Boolean>`. SpEL
recognizes that the elements of the list need to be converted to `Boolean` before
being placed in it. The following example shows how to do so.
being placed in it. The following example shows how to do so:
[tabs]
======
@@ -324,7 +325,7 @@ constructor before setting the specified value. If the element type does not hav
default constructor, `null` will be added to the array or list. If there is no built-in
or custom converter that knows how to set the value, `null` will remain in the array or
list at the specified index. The following example demonstrates how to automatically grow
the list.
the list:
[tabs]
======
@@ -379,25 +380,16 @@ Kotlin::
----
======
By default, a SpEL expression cannot contain more than 10,000 characters; however, the
`maxExpressionLength` is configurable. If you create a `SpelExpressionParser`
programmatically, you can specify a custom `maxExpressionLength` when creating the
`SpelParserConfiguration` that you provide to the `SpelExpressionParser`. If you wish to
set the `maxExpressionLength` used for parsing SpEL expressions within an
`ApplicationContext` -- for example, in XML bean definitions, `@Value`, etc. -- you can
set a JVM system property or Spring property named `spring.context.expression.maxLength`
to the maximum expression length needed by your application (see
xref:appendix.adoc#appendix-spring-properties[Supported Spring Properties]).
[[expressions-spel-compilation]]
== SpEL Compilation
Spring provides a basic compiler for SpEL expressions. Expressions are usually
interpreted, which provides a lot of dynamic flexibility during evaluation but does not
provide optimum performance. For occasional expression usage, this is fine, but, when
used by other components such as Spring Integration, performance can be very important,
and there is no real need for the dynamism.
Spring Framework 4.1 includes a basic expression compiler. Expressions are usually
interpreted, which provides a lot of dynamic flexibility during evaluation but
does not provide optimum performance. For occasional expression usage,
this is fine, but, when used by other components such as Spring Integration,
performance can be very important, and there is no real need for the dynamism.
The SpEL compiler is intended to address this need. During evaluation, the compiler
generates a Java class that embodies the expression behavior at runtime and uses that
@@ -410,17 +402,16 @@ information can cause trouble later if the types of the various expression eleme
change over time. For this reason, compilation is best suited to expressions whose
type information is not going to change on repeated evaluations.
Consider the following basic expression.
Consider the following basic expression:
[source,java,indent=0,subs="verbatim,quotes"]
----
someArray[0].someProperty.someOtherProperty < 0.1
someArray[0].someProperty.someOtherProperty < 0.1
----
Because the preceding expression involves array access, some property de-referencing, and
numeric operations, the performance gain can be very noticeable. In an example micro
benchmark run of 50,000 iterations, it took 75ms to evaluate by using the interpreter and
only 3ms using the compiled version of the expression.
Because the preceding expression involves array access, some property de-referencing,
and numeric operations, the performance gain can be very noticeable. In an example
micro benchmark run of 50000 iterations, it took 75ms to evaluate by using the
interpreter and only 3ms using the compiled version of the expression.
[[expressions-compiler-configuration]]
@@ -428,34 +419,33 @@ only 3ms using the compiled version of the expression.
The compiler is not turned on by default, but you can turn it on in either of two
different ways. You can turn it on by using the parser configuration process
(xref:core/expressions/evaluation.adoc#expressions-parser-configuration[discussed
earlier]) or by using a Spring property when SpEL usage is embedded inside another
component. This section discusses both of these options.
(xref:core/expressions/evaluation.adoc#expressions-parser-configuration[discussed earlier]) or by using a Spring property
when SpEL usage is embedded inside another component. This section discusses both of
these options.
The compiler can operate in one of three modes, which are captured in the
`org.springframework.expression.spel.SpelCompilerMode` enum. The modes are as follows.
`org.springframework.expression.spel.SpelCompilerMode` enum. The modes are as follows:
* `OFF` (default): The compiler is switched off.
* `IMMEDIATE`: In immediate mode, the expressions are compiled as soon as possible. This
is typically after the first interpreted evaluation. If the compiled expression fails
(typically due to a type changing, as described earlier), the caller of the expression
evaluation receives an exception.
* `MIXED`: In mixed mode, the expressions silently switch between interpreted and
compiled mode over time. After some number of interpreted runs, they switch to compiled
form and, if something goes wrong with the compiled form (such as a type changing, as
described earlier), the expression automatically switches back to interpreted form
again. Sometime later, it may generate another compiled form and switch to it.
Basically, the exception that the user gets in `IMMEDIATE` mode is instead handled
internally.
is typically after the first interpreted evaluation. If the compiled expression fails
(typically due to a type changing, as described earlier), the caller of the expression
evaluation receives an exception.
* `MIXED`: In mixed mode, the expressions silently switch between interpreted and compiled
mode over time. After some number of interpreted runs, they switch to compiled
form and, if something goes wrong with the compiled form (such as a type changing, as
described earlier), the expression automatically switches back to interpreted form
again. Sometime later, it may generate another compiled form and switch to it. Basically,
the exception that the user gets in `IMMEDIATE` mode is instead handled internally.
`IMMEDIATE` mode exists because `MIXED` mode could cause issues for expressions that
have side effects. If a compiled expression blows up after partially succeeding, it
may have already done something that has affected the state of the system. If this
has happened, the caller may not want it to silently re-run in interpreted mode,
since part of the expression may be run twice.
since part of the expression may be running twice.
After selecting a mode, use the `SpelParserConfiguration` to configure the parser. The
following example shows how to do so.
following example shows how to do so:
[tabs]
======
@@ -492,16 +482,15 @@ Kotlin::
----
======
When you specify the compiler mode, you can also specify a `ClassLoader` (passing `null`
is allowed). Compiled expressions are defined in a child `ClassLoader` created under any
that is supplied. It is important to ensure that, if a `ClassLoader` is specified, it can
see all the types involved in the expression evaluation process. If you do not specify a
`ClassLoader`, a default `ClassLoader` is used (typically the context `ClassLoader` for
the thread that is running during expression evaluation).
When you specify the compiler mode, you can also specify a classloader (passing null is allowed).
Compiled expressions are defined in a child classloader created under any that is supplied.
It is important to ensure that, if a classloader is specified, it can see all the types involved in
the expression evaluation process. If you do not specify a classloader, a default classloader is used
(typically the context classloader for the thread that is running during expression evaluation).
The second way to configure the compiler is for use when SpEL is embedded inside some
other component and it may not be possible to configure it through a configuration
object. In such cases, it is possible to set the `spring.expression.compiler.mode`
object. In these cases, it is possible to set the `spring.expression.compiler.mode`
property via a JVM system property (or via the
xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism) to one of the
`SpelCompilerMode` enum values (`off`, `immediate`, or `mixed`).
@@ -510,16 +499,18 @@ xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism) to
[[expressions-compiler-limitations]]
=== Compiler Limitations
Spring does not support compiling every kind of expression. The primary focus is on
common expressions that are likely to be used in performance-critical contexts. The
following kinds of expressions cannot be compiled.
Since Spring Framework 4.1, the basic compilation framework is in place. However, the framework
does not yet support compiling every kind of expression. The initial focus has been on the
common expressions that are likely to be used in performance-critical contexts. The following
kinds of expression cannot be compiled at the moment:
* Expressions involving assignment
* Expressions relying on the conversion service
* Expressions using custom resolvers or accessors
* Expressions using overloaded operators
* Expressions using array construction syntax
* Expressions using selection or projection
Compilation of additional kinds of expressions may be supported in the future.
More types of expressions will be compilable in the future.
@@ -3,11 +3,9 @@
This section lists the classes used in the examples throughout this chapter.
== `Inventor`
[tabs]
======
Java::
Inventor.Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
----
@@ -82,7 +80,7 @@ Java::
}
----
Kotlin::
Inventor.kt::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
----
@@ -97,11 +95,9 @@ Kotlin::
----
======
== `PlaceOfBirth`
[tabs]
======
Java::
PlaceOfBirth.java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
----
@@ -139,7 +135,7 @@ Java::
}
----
Kotlin::
PlaceOfBirth.kt::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
----
@@ -149,11 +145,9 @@ Kotlin::
----
======
== `Society`
[tabs]
======
Java::
Society.java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
----
@@ -198,7 +192,7 @@ Java::
}
----
Kotlin::
Society.kt::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
----
@@ -2,4 +2,24 @@
= Language Reference
:page-section-summary-toc: 1
This section describes how the Spring Expression Language works.
This section describes how the Spring Expression Language works. It covers the following
topics:
* xref:core/expressions/language-ref/literal.adoc[Literal Expressions]
* xref:core/expressions/language-ref/properties-arrays.adoc[Properties, Arrays, Lists, Maps, and Indexers]
* xref:core/expressions/language-ref/inline-lists.adoc[Inline Lists]
* xref:core/expressions/language-ref/inline-maps.adoc[Inline Maps]
* xref:core/expressions/language-ref/array-construction.adoc[Array Construction]
* xref:core/expressions/language-ref/methods.adoc[Methods]
* xref:core/expressions/language-ref/operators.adoc[Operators]
* xref:core/expressions/language-ref/types.adoc[Types]
* xref:core/expressions/language-ref/constructors.adoc[Constructors]
* xref:core/expressions/language-ref/variables.adoc[Variables]
* xref:core/expressions/language-ref/functions.adoc[User-Defined Functions]
* xref:core/expressions/language-ref/bean-references.adoc[Bean References]
* xref:core/expressions/language-ref/operator-ternary.adoc[Ternary Operator (If-Then-Else)]
* xref:core/expressions/language-ref/operator-elvis.adoc[The Elvis Operator]
* xref:core/expressions/language-ref/operator-safe-navigation.adoc[Safe Navigation Operator]
@@ -13,7 +13,7 @@ Java::
int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context);
// Array with initializer
int[] numbers2 = (int[]) parser.parseExpression("new int[] {1, 2, 3}").getValue(context);
int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context);
// Multi dimensional array
int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);
@@ -26,22 +26,14 @@ Kotlin::
val numbers1 = parser.parseExpression("new int[4]").getValue(context) as IntArray
// Array with initializer
val numbers2 = parser.parseExpression("new int[] {1, 2, 3}").getValue(context) as IntArray
val numbers2 = parser.parseExpression("new int[]{1,2,3}").getValue(context) as IntArray
// Multi dimensional array
val numbers3 = parser.parseExpression("new int[4][5]").getValue(context) as Array<IntArray>
----
======
[NOTE]
====
You cannot currently supply an initializer when you construct a multi-dimensional array.
====
[CAUTION]
====
Any expression that constructs an array for example, via `new int[4]` or
`new int[] {1, 2, 3}` cannot be compiled. See
xref:core/expressions/evaluation.adoc#expressions-compiler-limitations[Compiler Limitations]
for details.
====
@@ -4,7 +4,7 @@
Projection lets a collection drive the evaluation of a sub-expression, and the result is
a new collection. The syntax for projection is `.![projectionExpression]`. For example,
suppose we have a list of inventors but want the list of cities where they were born.
Effectively, we want to evaluate `placeOfBirth.city` for every entry in the inventor
Effectively, we want to evaluate 'placeOfBirth.city' for every entry in the inventor
list. The following example uses projection to do so:
[tabs]
@@ -13,18 +13,16 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
// evaluates to ["Smiljan", "Idvor"]
List placesOfBirth = parser.parseExpression("members.![placeOfBirth.city]")
.getValue(societyContext, List.class);
// returns ['Smiljan', 'Idvor' ]
List placesOfBirth = (List)parser.parseExpression("members.![placeOfBirth.city]");
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
// evaluates to ["Smiljan", "Idvor"]
val placesOfBirth = parser.parseExpression("members.![placeOfBirth.city]")
.getValue(societyContext) as List<*>
// returns ['Smiljan', 'Idvor' ]
val placesOfBirth = parser.parseExpression("members.![placeOfBirth.city]") as List<*>
----
======
@@ -34,12 +32,5 @@ evaluated against each entry in the map (represented as a Java `Map.Entry`). The
of a projection across a map is a list that consists of the evaluation of the projection
expression against each map entry.
[NOTE]
====
The Spring Expression Language also supports safe navigation for collection projection.
See
xref:core/expressions/language-ref/operator-safe-navigation.adoc#expressions-operator-safe-navigation-selection-and-projection[Safe Collection Selection and Projection]
for details.
====
@@ -28,14 +28,13 @@ Kotlin::
======
Selection is supported for arrays and anything that implements `java.lang.Iterable` or
`java.util.Map`. For an array or `Iterable`, the selection expression is evaluated
against each individual element. Against a map, the selection expression is evaluated
against each map entry (objects of the Java type `Map.Entry`). Each map entry has its
`key` and `value` accessible as properties for use in the selection.
`java.util.Map`. For a list or array, the selection criteria is evaluated against each
individual element. Against a map, the selection criteria is evaluated against each map
entry (objects of the Java type `Map.Entry`). Each map entry has its `key` and `value`
accessible as properties for use in the selection.
Given a `Map` stored in a variable named `#map`, the following expression returns a new
map that consists of those elements of the original map where the entry's value is less
than 27:
The following expression returns a new map that consists of those elements of the
original map where the entry's value is less than 27:
[tabs]
======
@@ -43,28 +42,21 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
Map newMap = parser.parseExpression("#map.?[value < 27]").getValue(Map.class);
Map newMap = parser.parseExpression("map.?[value<27]").getValue();
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val newMap = parser.parseExpression("#map.?[value < 27]").getValue() as Map
val newMap = parser.parseExpression("map.?[value<27]").getValue()
----
======
In addition to returning all the selected elements, you can retrieve only the first or
the last element. To obtain the first element matching the selection expression, the
syntax is `.^[selectionExpression]`. To obtain the last element matching the selection
expression, the syntax is `.$[selectionExpression]`.
the last element. To obtain the first element matching the selection, the syntax is
`.^[selectionExpression]`. To obtain the last matching selection, the syntax is
`.$[selectionExpression]`.
[NOTE]
====
The Spring Expression Language also supports safe navigation for collection selection.
See
xref:core/expressions/language-ref/operator-safe-navigation.adoc#expressions-operator-safe-navigation-selection-and-projection[Safe Collection Selection and Projection]
for details.
====
@@ -1,26 +1,10 @@
[[expressions-ref-functions]]
= Functions
You can extend SpEL by registering user-defined functions that can be called within
expressions by using the `#functionName(...)` syntax. Functions can be registered as
variables in `EvaluationContext` implementations via the `setVariable()` method.
[TIP]
====
`StandardEvaluationContext` also defines `registerFunction(...)` methods that provide a
convenient way to register a function as a `java.lang.reflect.Method` or a
`java.lang.invoke.MethodHandle`.
====
[WARNING]
====
Since functions share a common namespace with
xref:core/expressions/language-ref/variables.adoc[variables] in the evaluation context,
care must be taken to ensure that function names and variable names do not overlap.
====
The following example shows how to register a user-defined function to be invoked via
reflection using a `java.lang.reflect.Method`:
You can extend SpEL by registering user-defined functions that can be called within the
expression string. The function is registered through the `EvaluationContext`. The
following example shows how to register a user-defined function to be invoked via reflection
(i.e. a `Method`):
[tabs]
======
@@ -56,7 +40,11 @@ Java::
public abstract class StringUtils {
public static String reverseString(String input) {
return new StringBuilder(input).reverse().toString();
StringBuilder backwards = new StringBuilder(input.length());
for (int i = 0; i < input.length(); i++) {
backwards.append(input.charAt(input.length() - 1 - i));
}
return backwards.toString();
}
}
----
@@ -66,12 +54,16 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
fun reverseString(input: String): String {
return StringBuilder(input).reverse().toString()
val backwards = StringBuilder(input.length)
for (i in 0 until input.length) {
backwards.append(input[input.length - 1 - i])
}
return backwards.toString()
}
----
======
You can register and use the preceding method, as the following example shows:
You can then register and use the preceding method, as the following example shows:
[tabs]
======
@@ -83,9 +75,8 @@ Java::
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("reverseString",
StringUtils.class.getMethod("reverseString", String.class));
StringUtils.class.getDeclaredMethod("reverseString", String.class));
// evaluates to "olleh"
String helloWorldReversed = parser.parseExpression(
"#reverseString('hello')").getValue(context, String.class);
----
@@ -97,18 +88,16 @@ Kotlin::
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
context.setVariable("reverseString", ::reverseString.javaMethod)
context.setVariable("reverseString", ::reverseString::javaMethod)
// evaluates to "olleh"
val helloWorldReversed = parser.parseExpression(
"#reverseString('hello')").getValue(context, String::class.java)
----
======
A function can also be registered as a `java.lang.invoke.MethodHandle`. This enables
potentially more efficient use cases if the `MethodHandle` target and parameters have
been fully bound prior to registration; however, partially bound handles are also
supported.
The use of `MethodHandle` is also supported. This enables potentially more efficient use
cases if the `MethodHandle` target and parameters have been fully bound prior to
registration, but partially bound handles are also supported.
Consider the `String#formatted(String, Object...)` instance method, which produces a
message according to a template and a variable number of arguments.
@@ -129,9 +118,9 @@ Java::
MethodType.methodType(String.class, Object[].class));
context.setVariable("message", mh);
// evaluates to "Simple message: <Hello World>"
String message = parser.parseExpression("#message('Simple message: <%s>', 'Hello World', 'ignored')")
.getValue(context, String.class);
//returns "Simple message: <Hello World>"
----
Kotlin::
@@ -145,7 +134,6 @@ Kotlin::
MethodType.methodType(String::class.java, Array<Any>::class.java))
context.setVariable("message", mh)
// evaluates to "Simple message: <Hello World>"
val message = parser.parseExpression("#message('Simple message: <%s>', 'Hello World', 'ignored')")
.getValue(context, String::class.java)
----
@@ -173,9 +161,9 @@ Java::
.bindTo(varargs); //here we have to provide arguments in a single array binding
context.setVariable("message", mh);
// evaluates to "This is a prerecorded message with 3 words: <Oh Hello World!>"
String message = parser.parseExpression("#message()")
.getValue(context, String.class);
//returns "This is a prerecorded message with 3 words: <Oh Hello World!>"
----
Kotlin::
@@ -194,7 +182,6 @@ Kotlin::
.bindTo(varargs) //here we have to provide arguments in a single array binding
context.setVariable("message", mh)
// evaluates to "This is a prerecorded message with 3 words: <Oh Hello World!>"
val message = parser.parseExpression("#message()")
.getValue(context, String::class.java)
----
@@ -3,46 +3,20 @@
SpEL supports the following types of literal expressions.
String ::
Strings can be delimited by single quotation marks (`'`) or double quotation marks
(`"`). To include a single quotation mark within a string literal enclosed in single
quotation marks, use two adjacent single quotation mark characters. Similarly, to
include a double quotation mark within a string literal enclosed in double quotation
marks, use two adjacent double quotation mark characters.
Number ::
Numbers support the use of the negative sign, exponential notation, and decimal points.
* Integer: `int` or `long`
* Hexadecimal: `int` or `long`
* Real: `float` or `double`
** By default, real numbers are parsed using `Double.parseDouble()`.
Boolean ::
`true` or `false`
Null ::
`null`
- strings
- numeric values: integer (`int` or `long`), hexadecimal (`int` or `long`), real (`float`
or `double`)
- boolean values: `true` or `false`
- null
[NOTE]
====
Due to the design and implementation of the Spring Expression Language, literal numbers
are always stored internally as positive numbers.
Strings can delimited by single quotation marks (`'`) or double quotation marks (`"`). To
include a single quotation mark within a string literal enclosed in single quotation
marks, use two adjacent single quotation mark characters. Similarly, to include a double
quotation mark within a string literal enclosed in double quotation marks, use two
adjacent double quotation mark characters.
For example, `-2` is stored internally as a positive `2` which is then negated while
evaluating the expression (by calculating the value of `0 - 2`).
This means that it is not possible to represent a negative literal number equal to the
minimum value of that type of number in Java. For example, the minimum supported value
for an `int` in Java is `Integer.MIN_VALUE` which has a value of `-2147483648`. However,
if you include `-2147483648` in a SpEL expression, an exception will be thrown informing
you that the value `2147483648` cannot be parsed as an `int` (because it exceeds the
value of `Integer.MAX_VALUE` which is `2147483647`).
If you need to use the minimum value for a particular type of number within a SpEL
expression, you can either reference the `MIN_VALUE` constant for the respective wrapper
type (such as `Integer.MIN_VALUE`, `Long.MIN_VALUE`, etc.) or calculate the minimum
value. For example, to use the minimum integer value:
- `T(Integer).MIN_VALUE` -- requires a `StandardEvaluationContext`
- `-2^31` -- can be used with any type of `EvaluationContext`
====
Numbers support the use of the negative sign, exponential notation, and decimal points.
By default, real numbers are parsed by using `Double.parseDouble()`.
The following listing shows simple usage of literals. Typically, they are not used in
isolation like this but, rather, as part of a more complex expression -- for example,
@@ -1,27 +1,12 @@
[[expressions-operator-safe-navigation]]
= Safe Navigation Operator
The safe navigation operator (`?`) is used to avoid a `NullPointerException` and comes
from the https://www.groovy-lang.org/operators.html#_safe_navigation_operator[Groovy]
language. Typically, when you have a reference to an object, you might need to verify
that it is not `null` before accessing methods or properties of the object. To avoid
this, the safe navigation operator returns `null` for the particular null-safe operation
instead of throwing an exception.
[WARNING]
====
When the safe navigation operator evaluates to `null` for a particular null-safe
operation within a compound expression, the remainder of the compound expression will
still be evaluated.
See <<expressions-operator-safe-navigation-compound-expressions>> for details.
====
[[expressions-operator-safe-navigation-property-access]]
== Safe Property and Method Access
The following example shows how to use the safe navigation operator for property access
(`?.`).
The safe navigation operator is used to avoid a `NullPointerException` and comes from
the https://www.groovy-lang.org/operators.html#_safe_navigation_operator[Groovy]
language. Typically, when you have a reference to an object, you might need to verify that
it is not null before accessing methods or properties of the object. To avoid this, the
safe navigation operator returns null instead of throwing an exception. The following
example shows how to use the safe navigation operator:
[tabs]
======
@@ -35,18 +20,13 @@ Java::
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan"));
// evaluates to "Smiljan"
String city = parser.parseExpression("placeOfBirth?.city") // <1>
.getValue(context, tesla, String.class);
String city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String.class);
System.out.println(city); // Smiljan
tesla.setPlaceOfBirth(null);
// evaluates to null - does not throw NullPointerException
city = parser.parseExpression("placeOfBirth?.city") // <2>
.getValue(context, tesla, String.class);
city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String.class);
System.out.println(city); // null - does not throw NullPointerException!!!
----
<1> Use safe navigation operator on non-null `placeOfBirth` property
<2> Use safe navigation operator on null `placeOfBirth` property
Kotlin::
+
@@ -58,306 +38,14 @@ Kotlin::
val tesla = Inventor("Nikola Tesla", "Serbian")
tesla.setPlaceOfBirth(PlaceOfBirth("Smiljan"))
// evaluates to "Smiljan"
var city = parser.parseExpression("placeOfBirth?.city") // <1>
.getValue(context, tesla, String::class.java)
var city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String::class.java)
println(city) // Smiljan
tesla.setPlaceOfBirth(null)
// evaluates to null - does not throw NullPointerException
city = parser.parseExpression("placeOfBirth?.city") // <2>
.getValue(context, tesla, String::class.java)
city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String::class.java)
println(city) // null - does not throw NullPointerException!!!
----
<1> Use safe navigation operator on non-null `placeOfBirth` property
<2> Use safe navigation operator on null `placeOfBirth` property
======
[NOTE]
====
The safe navigation operator also applies to method invocations on an object.
For example, the expression `#calculator?.max(4, 2)` evaluates to `null` if the
`#calculator` variable has not been configured in the context. Otherwise, the
`max(int, int)` method will be invoked on the `#calculator`.
====
[[expressions-operator-safe-navigation-selection-and-projection]]
== Safe Collection Selection and Projection
The Spring Expression Language supports safe navigation for
xref:core/expressions/language-ref/collection-selection.adoc[collection selection] and
xref:core/expressions/language-ref/collection-projection.adoc[collection projection] via
the following operators.
* null-safe selection: `?.?`
* null-safe select first: `?.^`
* null-safe select last: `?.$`
* null-safe projection: `?.!`
The following example shows how to use the safe navigation operator for collection
selection (`?.?`).
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
ExpressionParser parser = new SpelExpressionParser();
IEEE society = new IEEE();
StandardEvaluationContext context = new StandardEvaluationContext(society);
String expression = "members?.?[nationality == 'Serbian']"; // <1>
// evaluates to [Inventor("Nikola Tesla")]
List<Inventor> list = (List<Inventor>) parser.parseExpression(expression)
.getValue(context);
society.members = null;
// evaluates to null - does not throw a NullPointerException
list = (List<Inventor>) parser.parseExpression(expression)
.getValue(context);
----
<1> Use null-safe selection operator on potentially null `members` list
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val parser = SpelExpressionParser()
val society = IEEE()
val context = StandardEvaluationContext(society)
val expression = "members?.?[nationality == 'Serbian']" // <1>
// evaluates to [Inventor("Nikola Tesla")]
var list = parser.parseExpression(expression)
.getValue(context) as List<Inventor>
society.members = null
// evaluates to null - does not throw a NullPointerException
list = parser.parseExpression(expression)
.getValue(context) as List<Inventor>
----
<1> Use null-safe selection operator on potentially null `members` list
======
The following example shows how to use the "null-safe select first" operator for
collections (`?.^`).
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
ExpressionParser parser = new SpelExpressionParser();
IEEE society = new IEEE();
StandardEvaluationContext context = new StandardEvaluationContext(society);
String expression =
"members?.^[nationality == 'Serbian' || nationality == 'Idvor']"; // <1>
// evaluates to Inventor("Nikola Tesla")
Inventor inventor = parser.parseExpression(expression)
.getValue(context, Inventor.class);
society.members = null;
// evaluates to null - does not throw a NullPointerException
inventor = parser.parseExpression(expression)
.getValue(context, Inventor.class);
----
<1> Use "null-safe select first" operator on potentially null `members` list
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val parser = SpelExpressionParser()
val society = IEEE()
val context = StandardEvaluationContext(society)
val expression =
"members?.^[nationality == 'Serbian' || nationality == 'Idvor']" // <1>
// evaluates to Inventor("Nikola Tesla")
var inventor = parser.parseExpression(expression)
.getValue(context, Inventor::class.java)
society.members = null
// evaluates to null - does not throw a NullPointerException
inventor = parser.parseExpression(expression)
.getValue(context, Inventor::class.java)
----
<1> Use "null-safe select first" operator on potentially null `members` list
======
The following example shows how to use the "null-safe select last" operator for
collections (`?.$`).
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
ExpressionParser parser = new SpelExpressionParser();
IEEE society = new IEEE();
StandardEvaluationContext context = new StandardEvaluationContext(society);
String expression =
"members?.$[nationality == 'Serbian' || nationality == 'Idvor']"; // <1>
// evaluates to Inventor("Pupin")
Inventor inventor = parser.parseExpression(expression)
.getValue(context, Inventor.class);
society.members = null;
// evaluates to null - does not throw a NullPointerException
inventor = parser.parseExpression(expression)
.getValue(context, Inventor.class);
----
<1> Use "null-safe select last" operator on potentially null `members` list
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val parser = SpelExpressionParser()
val society = IEEE()
val context = StandardEvaluationContext(society)
val expression =
"members?.$[nationality == 'Serbian' || nationality == 'Idvor']" // <1>
// evaluates to Inventor("Pupin")
var inventor = parser.parseExpression(expression)
.getValue(context, Inventor::class.java)
society.members = null
// evaluates to null - does not throw a NullPointerException
inventor = parser.parseExpression(expression)
.getValue(context, Inventor::class.java)
----
<1> Use "null-safe select last" operator on potentially null `members` list
======
The following example shows how to use the safe navigation operator for collection
projection (`?.!`).
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
ExpressionParser parser = new SpelExpressionParser();
IEEE society = new IEEE();
StandardEvaluationContext context = new StandardEvaluationContext(society);
// evaluates to ["Smiljan", "Idvor"]
List placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]") // <1>
.getValue(context, List.class);
society.members = null;
// evaluates to null - does not throw a NullPointerException
placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]") // <2>
.getValue(context, List.class);
----
<1> Use null-safe projection operator on non-null `members` list
<2> Use null-safe projection operator on null `members` list
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val parser = SpelExpressionParser()
val society = IEEE()
val context = StandardEvaluationContext(society)
// evaluates to ["Smiljan", "Idvor"]
var placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]") // <1>
.getValue(context, List::class.java)
society.members = null
// evaluates to null - does not throw a NullPointerException
placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]") // <2>
.getValue(context, List::class.java)
----
<1> Use null-safe projection operator on non-null `members` list
<2> Use null-safe projection operator on null `members` list
======
[[expressions-operator-safe-navigation-compound-expressions]]
== Null-safe Operations in Compound Expressions
As mentioned at the beginning of this section, when the safe navigation operator
evaluates to `null` for a particular null-safe operation within a compound expression,
the remainder of the compound expression will still be evaluated. This means that the
safe navigation operator must be applied throughout a compound expression in order to
avoid any unwanted `NullPointerException`.
Given the expression `#person?.address.city`, if `#person` is `null` the safe navigation
operator (`?.`) ensures that no exception will be thrown when attempting to access the
`address` property of `#person`. However, since `#person?.address` evaluates to `null`, a
`NullPointerException` will be thrown when attempting to access the `city` property of
`null`. To address that, you can apply null-safe navigation throughout the compound
expression as in `#person?.address?.city`. That expression will safely evaluate to `null`
if either `#person` or `#person?.address` evaluates to `null`.
The following example demonstrates how to use the "null-safe select first" operator
(`?.^`) on a collection combined with null-safe property access (`?.`) within a compound
expression. If `members` is `null`, the result of the "null-safe select first" operator
(`members?.^[nationality == 'Serbian']`) evaluates to `null`, and the additional use of
the safe navigation operator (`?.name`) ensures that the entire compound expression
evaluates to `null` instead of throwing an exception.
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
ExpressionParser parser = new SpelExpressionParser();
IEEE society = new IEEE();
StandardEvaluationContext context = new StandardEvaluationContext(society);
String expression = "members?.^[nationality == 'Serbian']?.name"; // <1>
// evaluates to "Nikola Tesla"
String name = parser.parseExpression(expression)
.getValue(context, String.class);
society.members = null;
// evaluates to null - does not throw a NullPointerException
name = parser.parseExpression(expression)
.getValue(context, String.class);
----
<1> Use "null-safe select first" and null-safe property access operators within compound expression.
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val parser = SpelExpressionParser()
val society = IEEE()
val context = StandardEvaluationContext(society)
val expression = "members?.^[nationality == 'Serbian']?.name" // <1>
// evaluates to "Nikola Tesla"
String name = parser.parseExpression(expression)
.getValue(context, String::class.java)
society.members = null
// evaluates to null - does not throw a NullPointerException
name = parser.parseExpression(expression)
.getValue(context, String::class.java)
----
<1> Use "null-safe select first" and null-safe property access operators within compound expression.
======
@@ -5,11 +5,8 @@ The Spring Expression Language supports the following kinds of operators:
* xref:core/expressions/language-ref/operators.adoc#expressions-operators-relational[Relational Operators]
* xref:core/expressions/language-ref/operators.adoc#expressions-operators-logical[Logical Operators]
* xref:core/expressions/language-ref/operators.adoc#expressions-operators-string[String Operators]
* xref:core/expressions/language-ref/operators.adoc#expressions-operators-mathematical[Mathematical Operators]
* xref:core/expressions/language-ref/operators.adoc#expressions-assignment[The Assignment Operator]
* xref:core/expressions/language-ref/operators.adoc#expressions-operators-overloaded[Overloaded Operators]
[[expressions-operators-relational]]
@@ -18,7 +15,7 @@ The Spring Expression Language supports the following kinds of operators:
The relational operators (equal, not equal, less than, less than or equal, greater than,
and greater than or equal) are supported by using standard operator notation.
These operators work on `Number` types as well as types implementing `Comparable`.
The following listing shows a few examples of relational operators:
The following listing shows a few examples of operators:
[tabs]
======
@@ -68,22 +65,8 @@ If you prefer numeric comparisons instead, avoid number-based `null` comparisons
in favor of comparisons against zero (for example, `X > 0` or `X < 0`).
====
Each symbolic operator can also be specified as a purely textual equivalent. This avoids
problems where the symbols used have special meaning for the document type in which the
expression is embedded (such as in an XML document). The textual equivalents are:
* `lt` (`<`)
* `gt` (`>`)
* `le` (`\<=`)
* `ge` (`>=`)
* `eq` (`==`)
* `ne` (`!=`)
All of the textual operators are case-insensitive.
In addition to the standard relational operators, SpEL supports the `between`,
`instanceof`, and regular expression-based `matches` operators. The following listing
shows examples of all three:
In addition to the standard relational operators, SpEL supports the `instanceof` and regular
expression-based `matches` operator. The following listing shows examples of both:
[tabs]
======
@@ -91,38 +74,16 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
boolean result;
// evaluates to true
result = parser.parseExpression(
"1 between {1, 5}").getValue(Boolean.class);
// evaluates to false
result = parser.parseExpression(
"1 between {10, 15}").getValue(Boolean.class);
// evaluates to true
result = parser.parseExpression(
"'elephant' between {'aardvark', 'zebra'}").getValue(Boolean.class);
// evaluates to false
result = parser.parseExpression(
"'elephant' between {'aardvark', 'cobra'}").getValue(Boolean.class);
// evaluates to true
result = parser.parseExpression(
"123 instanceof T(Integer)").getValue(Boolean.class);
// evaluates to false
result = parser.parseExpression(
boolean falseValue = parser.parseExpression(
"'xyz' instanceof T(Integer)").getValue(Boolean.class);
// evaluates to true
result = parser.parseExpression(
boolean trueValue = parser.parseExpression(
"'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
// evaluates to false
result = parser.parseExpression(
boolean falseValue = parser.parseExpression(
"'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
----
@@ -130,65 +91,50 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
// evaluates to true
var result = parser.parseExpression(
"1 between {1, 5}").getValue(Boolean::class.java)
// evaluates to false
result = parser.parseExpression(
"1 between {10, 15}").getValue(Boolean::class.java)
// evaluates to true
result = parser.parseExpression(
"'elephant' between {'aardvark', 'zebra'}").getValue(Boolean::class.java)
// evaluates to false
result = parser.parseExpression(
"'elephant' between {'aardvark', 'cobra'}").getValue(Boolean::class.java)
// evaluates to true
result = parser.parseExpression(
"123 instanceof T(Integer)").getValue(Boolean::class.java)
// evaluates to false
result = parser.parseExpression(
val falseValue = parser.parseExpression(
"'xyz' instanceof T(Integer)").getValue(Boolean::class.java)
// evaluates to true
result = parser.parseExpression(
val trueValue = parser.parseExpression(
"'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java)
// evaluates to false
result = parser.parseExpression(
val falseValue = parser.parseExpression(
"'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java)
----
======
[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>}`.
Consequently, `1 between {1, 5}` evaluates to `true`, while `1 between {5, 1}` evaluates
to `false`.
====
CAUTION: Be careful with primitive types, as they are immediately boxed up to their
wrapper types. For example, `1 instanceof T(int)` evaluates to `false`, while
`1 instanceof T(Integer)` evaluates to `true`.
`1 instanceof T(Integer)` evaluates to `true`, as expected.
Each symbolic operator can also be specified as a purely alphabetic equivalent. This
avoids problems where the symbols used have special meaning for the document type in
which the expression is embedded (such as in an XML document). The textual equivalents are:
* `lt` (`<`)
* `gt` (`>`)
* `le` (`\<=`)
* `ge` (`>=`)
* `eq` (`==`)
* `ne` (`!=`)
* `div` (`/`)
* `mod` (`%`)
* `not` (`!`).
All of the textual operators are case-insensitive.
[[expressions-operators-logical]]
== Logical Operators
SpEL supports the following logical (`boolean`) operators:
SpEL supports the following logical operators:
* `and` (`&&`)
* `or` (`||`)
* `not` (`!`)
All of the textual operators are case-insensitive.
The following example shows how to use the logical operators:
[tabs]
@@ -221,7 +167,6 @@ Java::
boolean falseValue = parser.parseExpression("!true").getValue(Boolean.class);
// -- AND and NOT --
String expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')";
boolean falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
----
@@ -254,105 +199,20 @@ Kotlin::
val falseValue = parser.parseExpression("!true").getValue(Boolean::class.java)
// -- AND and NOT --
val expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')"
val falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean::class.java)
----
======
[[expressions-operators-string]]
== String Operators
You can use the following operators on strings.
* concatenation (`+`)
* subtraction (`-`)
- for use with a string containing a single character
* repeat (`*`)
The following example shows the `String` operators in use:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
// -- Concatenation --
// evaluates to "hello world"
String helloWorld = parser.parseExpression("'hello' + ' ' + 'world'")
.getValue(String.class);
// -- Character Subtraction --
// evaluates to 'a'
char ch = parser.parseExpression("'d' - 3")
.getValue(char.class);
// -- Repeat --
// evaluates to "abcabc"
String repeated = parser.parseExpression("'abc' * 2")
.getValue(String.class);
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
// -- Concatenation --
// evaluates to "hello world"
val helloWorld = parser.parseExpression("'hello' + ' ' + 'world'")
.getValue(String::class.java)
// -- Character Subtraction --
// evaluates to 'a'
val ch = parser.parseExpression("'d' - 3")
.getValue(Character::class.java);
// -- Repeat --
// evaluates to "abcabc"
val repeated = parser.parseExpression("'abc' * 2")
.getValue(String::class.java);
----
======
[[expressions-operators-mathematical]]
== Mathematical Operators
You can use the following operators on numbers, and standard operator precedence is enforced.
* addition (`+`)
* subtraction (`-`)
* increment (`{pp}`)
* decrement (`--`)
* multiplication (`*`)
* division (`/`)
* modulus (`%`)
* exponential power (`^`)
The division and modulus operators can also be specified as a purely textual equivalent.
This avoids problems where the symbols used have special meaning for the document type in
which the expression is embedded (such as in an XML document). The textual equivalents
are:
* `div` (`/`)
* `mod` (`%`)
All of the textual operators are case-insensitive.
[NOTE]
====
The increment and decrement operators can be used with either prefix (`{pp}A`, `--A`) or
postfix (`A{pp}`, `A--`) notation with variables or properties that can be written to.
====
The following example shows the mathematical operators in use:
You can use the addition operator (`+`) on both numbers and strings. You can use the
subtraction (`-`), multiplication (`*`), and division (`/`) operators only on numbers.
You can also use the modulus (`%`) and exponential power (`^`) operators on numbers.
Standard operator precedence is enforced. The following example shows the mathematical
operators in use:
[tabs]
======
@@ -360,131 +220,67 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
Inventor inventor = new Inventor();
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
// Addition
int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2
// -- Addition --
String testString = parser.parseExpression(
"'test' + ' ' + 'string'").getValue(String.class); // 'test string'
int two = parser.parseExpression("1 + 1").getValue(int.class); // 2
// Subtraction
int four = parser.parseExpression("1 - -3").getValue(Integer.class); // 4
// -- Subtraction --
double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class); // -9000
int four = parser.parseExpression("1 - -3").getValue(int.class); // 4
// Multiplication
int six = parser.parseExpression("-2 * -3").getValue(Integer.class); // 6
double d = parser.parseExpression("1000.00 - 1e4").getValue(double.class); // -9000
double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); // 24.0
// -- Increment --
// Division
int minusTwo = parser.parseExpression("6 / -3").getValue(Integer.class); // -2
// The counter property in Inventor has an initial value of 0.
double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class); // 1.0
// evaluates to 2; counter is now 1
two = parser.parseExpression("counter++ + 2").getValue(context, inventor, int.class);
// Modulus
int three = parser.parseExpression("7 % 4").getValue(Integer.class); // 3
// evaluates to 5; counter is now 2
int five = parser.parseExpression("3 + ++counter").getValue(context, inventor, int.class);
int one = parser.parseExpression("8 / 5 % 2").getValue(Integer.class); // 1
// -- Decrement --
// The counter property in Inventor has a value of 2.
// evaluates to 6; counter is now 1
int six = parser.parseExpression("counter-- + 4").getValue(context, inventor, int.class);
// evaluates to 5; counter is now 0
five = parser.parseExpression("5 + --counter").getValue(context, inventor, int.class);
// -- Multiplication --
six = parser.parseExpression("-2 * -3").getValue(int.class); // 6
double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(double.class); // 24.0
// -- Division --
int minusTwo = parser.parseExpression("6 / -3").getValue(int.class); // -2
double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(double.class); // 1.0
// -- Modulus --
int three = parser.parseExpression("7 % 4").getValue(int.class); // 3
int oneInt = parser.parseExpression("8 / 5 % 2").getValue(int.class); // 1
// -- Exponential power --
int maxInt = parser.parseExpression("(2^31) - 1").getValue(int.class); // Integer.MAX_VALUE
int minInt = parser.parseExpression("-2^31").getValue(int.class); // Integer.MIN_VALUE
// -- Operator precedence --
int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(int.class); // -21
// Operator precedence
int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class); // -21
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val inventor = Inventor()
val context = SimpleEvaluationContext.forReadWriteDataBinding().build()
// Addition
val two = parser.parseExpression("1 + 1").getValue(Int::class.java) // 2
// -- Addition --
var two = parser.parseExpression("1 + 1").getValue(Int::class.java) // 2
// -- Subtraction --
val testString = parser.parseExpression(
"'test' + ' ' + 'string'").getValue(String::class.java) // 'test string'
// Subtraction
val four = parser.parseExpression("1 - -3").getValue(Int::class.java) // 4
val d = parser.parseExpression("1000.00 - 1e4").getValue(Double::class.java) // -9000
// -- Increment --
// The counter property in Inventor has an initial value of 0.
// evaluates to 2; counter is now 1
two = parser.parseExpression("counter++ + 2").getValue(context, inventor, Int::class.java)
// evaluates to 5; counter is now 2
var five = parser.parseExpression("3 + ++counter").getValue(context, inventor, Int::class.java)
// -- Decrement --
// The counter property in Inventor has a value of 2.
// evaluates to 6; counter is now 1
var six = parser.parseExpression("counter-- + 4").getValue(context, inventor, Int::class.java)
// evaluates to 5; counter is now 0
five = parser.parseExpression("5 + --counter").getValue(context, inventor, Int::class.java)
// -- Multiplication --
six = parser.parseExpression("-2 * -3").getValue(Int::class.java) // 6
// Multiplication
val six = parser.parseExpression("-2 * -3").getValue(Int::class.java) // 6
val twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double::class.java) // 24.0
// -- Division --
// Division
val minusTwo = parser.parseExpression("6 / -3").getValue(Int::class.java) // -2
val one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double::class.java) // 1.0
// -- Modulus --
// Modulus
val three = parser.parseExpression("7 % 4").getValue(Int::class.java) // 3
val oneInt = parser.parseExpression("8 / 5 % 2").getValue(Int::class.java) // 1
// -- Exponential power --
val maxInt = parser.parseExpression("(2^31) - 1").getValue(Int::class.java) // Integer.MAX_VALUE
val minInt = parser.parseExpression("-2^31").getValue(Int::class.java) // Integer.MIN_VALUE
// -- Operator precedence --
val one = parser.parseExpression("8 / 5 % 2").getValue(Int::class.java) // 1
// Operator precedence
val minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Int::class.java) // -21
----
======
@@ -529,83 +325,3 @@ Kotlin::
======
[[expressions-operators-overloaded]]
== Overloaded Operators
By default, the mathematical operations defined in SpEL's `Operation` enum (`ADD`,
`SUBTRACT`, `DIVIDE`, `MULTIPLY`, `MODULUS`, and `POWER`) support simple types like
numbers. By providing an implementation of `OperatorOverloader`, the expression language
can support these operations on other types.
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.
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
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}`
as demonstrated in the following example.
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
StandardEvaluationContext context = new StandardEvaluationContext();
context.setOperatorOverloader(new ListConcatenation());
// evaluates to a new list: [1, 2, 3, 4, 5]
parser.parseExpression("{1, 2, 3} + {2 + 2, 5}").getValue(context, List.class);
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
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)
----
======
[NOTE]
====
An `OperatorOverloader` does not change the default semantics for an operator. For
example, `2 + 2` in the above example still evaluates to `4`.
====
[CAUTION]
====
Any expression that uses an overloaded operator cannot be compiled. See
xref:core/expressions/evaluation.adoc#expressions-compiler-limitations[Compiler Limitations]
for details.
====
@@ -1,5 +1,5 @@
[[expressions-templating]]
= Expression Templating
= Expression templating
Expression templates allow mixing literal text with one or more evaluation blocks.
Each evaluation block is delimited with prefix and suffix characters that you can
@@ -32,13 +32,53 @@ Kotlin::
======
The string is evaluated by concatenating the literal text `'random number is '` with the
result of evaluating the expression inside the `#{ }` delimiters (in this case, the
result of calling that `random()` method). The second argument to the `parseExpression()`
method is of the type `ParserContext`. The `ParserContext` interface is used to influence
how the expression is parsed in order to support the expression templating functionality.
The `TemplateParserContext` used in the previous example resides in the
`org.springframework.expression.common` package and is an implementation of the
`ParserContext` which by default configures the prefix and suffix to `#{` and `}`,
respectively.
result of evaluating the expression inside the `#{ }` delimiter (in this case, the result
of calling that `random()` method). The second argument to the `parseExpression()` method
is of the type `ParserContext`. The `ParserContext` interface is used to influence how
the expression is parsed in order to support the expression templating functionality.
The definition of `TemplateParserContext` follows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
public class TemplateParserContext implements ParserContext {
public String getExpressionPrefix() {
return "#{";
}
public String getExpressionSuffix() {
return "}";
}
public boolean isTemplate() {
return true;
}
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
class TemplateParserContext : ParserContext {
override fun getExpressionPrefix(): String {
return "#{"
}
override fun getExpressionSuffix(): String {
return "}"
}
override fun isTemplate(): Boolean {
return true
}
}
----
======
@@ -1,41 +1,20 @@
[[expressions-ref-variables]]
= Variables
You can reference variables in an expression by using the `#variableName` syntax. Variables
are set by using the `setVariable()` method in `EvaluationContext` implementations.
You can reference variables in the expression by using the `#variableName` syntax. Variables
are set by using the `setVariable` method on `EvaluationContext` implementations.
[NOTE]
====
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
Valid variable names must be composed of one or more of the following supported
characters.
* letter: any character for which `java.lang.Character.isLetter(char)` returns `true`
- This includes letters such as `A` to `Z`, `a` to `z`, `ü`, `ñ`, and `é` as well as
letters from other character sets such as Chinese, Japanese, Cyrillic, etc.
* digit: `0` to `9`
* letters: `A` to `Z` and `a` to `z`
* digits: `0` to `9`
* underscore: `_`
* dollar sign: `$`
====
[TIP]
====
When setting a variable or root context object in the `EvaluationContext`, it is advised
that the type of the variable or root context object be `public`.
Otherwise, certain types of SpEL expressions involving a variable or root context object
with a non-public type may fail to evaluate or compile.
====
[WARNING]
====
Since variables share a common namespace with
xref:core/expressions/language-ref/functions.adoc[functions] in the evaluation context,
care must be taken to ensure that variable names and functions names do not overlap.
====
The following example shows how to use variables.
[tabs]
@@ -50,7 +29,7 @@ Java::
context.setVariable("newName", "Mike Tesla");
parser.parseExpression("name = #newName").getValue(context, tesla);
System.out.println(tesla.getName()); // "Mike Tesla"
System.out.println(tesla.getName()) // "Mike Tesla"
----
Kotlin::
@@ -74,10 +53,8 @@ Kotlin::
The `#this` variable is always defined and refers to the current evaluation object
(against which unqualified references are resolved). The `#root` variable is always
defined and refers to the root context object. Although `#this` may vary as components of
an expression are evaluated, `#root` always refers to the root.
The following example shows how to use the `#this` variable in conjunction with
xref:core/expressions/language-ref/collection-selection.adoc[collection selection].
an expression are evaluated, `#root` always refers to the root. The following examples
show how to use the `#this` and `#root` variables:
[tabs]
======
@@ -85,95 +62,40 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
// Create a list of prime integers.
List<Integer> primes = List.of(2, 3, 5, 7, 11, 13, 17);
// create an array of integers
List<Integer> primes = new ArrayList<>();
primes.addAll(Arrays.asList(2,3,5,7,11,13,17));
// Create parser and set variable 'primes' as the list of integers.
// create parser and set variable 'primes' as the array of integers
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataAccess();
context.setVariable("primes", primes);
// Select all prime numbers > 10 from the list (using selection ?{...}).
String expression = "#primes.?[#this > 10]";
// Evaluates to a list containing [11, 13, 17].
List<Integer> primesGreaterThanTen =
parser.parseExpression(expression).getValue(context, List.class);
// all prime numbers > 10 from the list (using selection ?{...})
// evaluates to [11, 13, 17]
List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression(
"#primes.?[#this>10]").getValue(context);
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
// Create a list of prime integers.
val primes = listOf(2, 3, 5, 7, 11, 13, 17)
// create an array of integers
val primes = ArrayList<Int>()
primes.addAll(listOf(2, 3, 5, 7, 11, 13, 17))
// Create parser and set variable 'primes' as the list of integers.
// create parser and set variable 'primes' as the array of integers
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadWriteDataBinding().build()
val context = SimpleEvaluationContext.forReadOnlyDataAccess()
context.setVariable("primes", primes)
// Select all prime numbers > 10 from the list (using selection ?{...}).
val expression = "#primes.?[#this > 10]"
// Evaluates to a list containing [11, 13, 17].
val primesGreaterThanTen = parser.parseExpression(expression)
.getValue(context) as List<Int>
// all prime numbers > 10 from the list (using selection ?{...})
// evaluates to [11, 13, 17]
val primesGreaterThanTen = parser.parseExpression(
"#primes.?[#this>10]").getValue(context) as List<Int>
----
======
The following example shows how to use the `#this` and `#root` variables together in
conjunction with
xref:core/expressions/language-ref/collection-projection.adoc[collection projection].
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
// Create parser and evaluation context.
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
// Create an inventor to use as the root context object.
Inventor tesla = new Inventor("Nikola Tesla");
tesla.setInventions("Telephone repeater", "Tesla coil transformer");
// Iterate over all inventions of the Inventor referenced as the #root
// object, and generate a list of strings whose contents take the form
// "<inventor's name> invented the <invention>." (using projection !{...}).
String expression = "#root.inventions.![#root.name + ' invented the ' + #this + '.']";
// Evaluates to a list containing:
// "Nikola Tesla invented the Telephone repeater."
// "Nikola Tesla invented the Tesla coil transformer."
List<String> results = parser.parseExpression(expression)
.getValue(context, tesla, List.class);
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
// Create parser and evaluation context.
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadWriteDataBinding().build()
// Create an inventor to use as the root context object.
val tesla = Inventor("Nikola Tesla")
tesla.setInventions("Telephone repeater", "Tesla coil transformer")
// Iterate over all inventions of the Inventor referenced as the #root
// object, and generate a list of strings whose contents take the form
// "<inventor's name> invented the <invention>." (using projection !{...}).
val expression = "#root.inventions.![#root.name + ' invented the ' + #this + '.']"
// Evaluates to a list containing:
// "Nikola Tesla invented the Telephone repeater."
// "Nikola Tesla invented the Tesla coil transformer."
val results = parser.parseExpression(expression)
.getValue(context, tesla, List::class.java)
----
======
@@ -5,14 +5,14 @@ Although Java does not let you express null-safety with its type system, the Spr
provides the following annotations in the `org.springframework.lang` package to let you
declare nullability of APIs and fields:
* {spring-framework-api}/lang/Nullable.html[`@Nullable`]: Annotation to indicate that a
* {api-spring-framework}/lang/Nullable.html[`@Nullable`]: Annotation to indicate that a
specific parameter, return value, or field can be `null`.
* {spring-framework-api}/lang/NonNull.html[`@NonNull`]: Annotation to indicate that a specific
* {api-spring-framework}/lang/NonNull.html[`@NonNull`]: Annotation to indicate that a specific
parameter, return value, or field cannot be `null` (not needed on parameters, return values,
and fields where `@NonNullApi` and `@NonNullFields` apply, respectively).
* {spring-framework-api}/lang/NonNullApi.html[`@NonNullApi`]: Annotation at the package level
* {api-spring-framework}/lang/NonNullApi.html[`@NonNullApi`]: Annotation at the package level
that declares non-null as the default semantics for parameters and return values.
* {spring-framework-api}/lang/NonNullFields.html[`@NonNullFields`]: Annotation at the package
* {api-spring-framework}/lang/NonNullFields.html[`@NonNullFields`]: Annotation at the package
level that declares non-null as the default semantics for fields.
The Spring Framework itself leverages these annotations, but they can also be used in any
@@ -37,7 +37,7 @@ these annotations can be used by an IDE (such as IDEA or Eclipse) to provide use
warnings related to null-safety in order to avoid `NullPointerException` at runtime.
They are also used to make Spring APIs null-safe in Kotlin projects, since Kotlin natively
supports {kotlin-docs}/null-safety.html[null-safety]. More details
supports https://kotlinlang.org/docs/null-safety.html[null-safety]. More details
are available in the xref:languages/kotlin/null-safety.adoc[Kotlin support documentation].
@@ -46,7 +46,7 @@ are available in the xref:languages/kotlin/null-safety.adoc[Kotlin support docum
[[jsr-305-meta-annotations]]
== JSR-305 meta-annotations
Spring annotations are meta-annotated with {JSR}305[JSR 305]
Spring annotations are meta-annotated with https://jcp.org/en/jsr/detail?id=305[JSR 305]
annotations (a dormant but widespread JSR). JSR-305 meta-annotations let tooling vendors
like IDEA or Kotlin provide null-safety support in a generic way, without having to
hard-code support for Spring annotations.
@@ -37,7 +37,7 @@ such as a method to check for the existence of the resource being pointed to.
Spring's `Resource` interface located in the `org.springframework.core.io.` package is
meant to be a more capable interface for abstracting access to low-level resources. The
following listing provides an overview of the `Resource` interface. See the
{spring-framework-api}/core/io/Resource.html[`Resource`] javadoc for further details.
{api-spring-framework}/core/io/Resource.html[`Resource`] javadoc for further details.
[source,java,indent=0,subs="verbatim,quotes"]
@@ -104,7 +104,7 @@ resource (if the underlying implementation is compatible and supports that
functionality).
Some implementations of the `Resource` interface also implement the extended
{spring-framework-api}/core/io/WritableResource.html[`WritableResource`] interface
{api-spring-framework}/core/io/WritableResource.html[`WritableResource`] interface
for a resource that supports writing to it.
Spring itself uses the `Resource` abstraction extensively, as an argument type in
@@ -143,7 +143,7 @@ Spring includes several built-in `Resource` implementations:
For a complete list of `Resource` implementations available in Spring, consult the
"All Known Implementing Classes" section of the
{spring-framework-api}/core/io/Resource.html[`Resource`] javadoc.
{api-spring-framework}/core/io/Resource.html[`Resource`] javadoc.
@@ -763,7 +763,7 @@ Kotlin::
----
======
See the {spring-framework-api}/context/support/ClassPathXmlApplicationContext.html[`ClassPathXmlApplicationContext`]
See the {api-spring-framework}/context/support/ClassPathXmlApplicationContext.html[`ClassPathXmlApplicationContext`]
javadoc for details on the various constructors.
@@ -903,7 +903,7 @@ entries in the classpath. When you build JARs with Ant, do not activate the `fil
switch of the JAR task. Also, classpath directories may not get exposed based on security
policies in some environments -- for example, stand-alone applications on JDK 1.7.0_45
and higher (which requires 'Trusted-Library' to be set up in your manifests. See
{stackoverflow-questions}/19394570/java-jre-7u45-breaks-classloader-getresources).
https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources).
On JDK 9's module path (Jigsaw), Spring's classpath scanning generally works as expected.
Putting resources into a dedicated directory is highly recommendable here as well,
@@ -9,7 +9,7 @@ known as _JUL_ or `java.util.logging`) if neither Log4j 2.x nor SLF4J is availab
Put Log4j 2.x or Logback (or another SLF4J provider) in your classpath, without any extra
bridges, and let the framework auto-adapt to your choice. For further information see the
{spring-boot-docs}/features.html#features.logging[Spring
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-logging[Spring
Boot Logging Reference Documentation].
[NOTE]
@@ -51,7 +51,7 @@ A JavaBean is a class with a default no-argument constructor and that follows
a naming convention where (for example) a property named `bingoMadness` would
have a setter method `setBingoMadness(..)` and a getter method `getBingoMadness()`. For
more information about JavaBeans and the specification, see
{java-api}/java.desktop/java/beans/package-summary.html[javabeans].
https://docs.oracle.com/javase/8/docs/api/java/beans/package-summary.html[javabeans].
One quite important class in the beans package is the `BeanWrapper` interface and its
corresponding implementation (`BeanWrapperImpl`). As quoted from the javadoc, the
@@ -249,7 +249,7 @@ behavior can be achieved by registering custom editors of type
`java.beans.PropertyEditor`. Registering custom editors on a `BeanWrapper` or,
alternatively, in a specific IoC container (as mentioned in the previous chapter), gives it
the knowledge of how to convert properties to the desired type. For more about
`PropertyEditor`, see {java-api}/java.desktop/java/beans/package-summary.html[the javadoc of the `java.beans` package from Oracle].
`PropertyEditor`, see https://docs.oracle.com/javase/8/docs/api/java/beans/package-summary.html[the javadoc of the `java.beans` package from Oracle].
A couple of examples where property editing is used in Spring:
@@ -355,7 +355,7 @@ com
Note that you can also use the standard `BeanInfo` JavaBeans mechanism here as well
(described to some extent
{java-tutorial}/javabeans/advanced/customization.html[here]). The
https://docs.oracle.com/javase/tutorial/javabeans/advanced/customization.html[here]). The
following example uses the `BeanInfo` mechanism to explicitly register one or more
`PropertyEditor` instances with the properties of an associated class:
@@ -2,7 +2,7 @@
= Java Bean Validation
The Spring Framework provides support for the
{bean-validation-site}[Java Bean Validation] API.
https://beanvalidation.org/[Java Bean Validation] API.
@@ -72,7 +72,7 @@ Kotlin::
======
A Bean Validation validator then validates instances of this class based on the declared
constraints. See {bean-validation-site}[Bean Validation] for general information about
constraints. See https://beanvalidation.org/[Bean Validation] for general information about
the API. See the https://hibernate.org/validator/[Hibernate Validator] documentation for
specific constraints. To learn how to set up a bean validation provider as a Spring
bean, keep reading.
@@ -317,7 +317,7 @@ XML::
To be eligible for Spring-driven method validation, target classes need to be annotated
with Spring's `@Validated` annotation, which can optionally also declare the validation
groups to use. See
{spring-framework-api}/validation/beanvalidation/MethodValidationPostProcessor.html[`MethodValidationPostProcessor`]
{api-spring-framework}/validation/beanvalidation/MethodValidationPostProcessor.html[`MethodValidationPostProcessor`]
for setup details with the Hibernate Validator and Bean Validation providers.
[TIP]
@@ -429,7 +429,7 @@ Kotlin::
----
======
A `ConstraintViolation` on `Person.name()` is adapted to a `FieldError` with the following:
A `ConstraintViolation` on `Person.name()` is adapted to a `FieldErrro` with the following:
- Error codes `"Size.student.name"`, `"Size.name"`, `"Size.java.lang.String"`, and `"Size"`
- Message arguments `"name"`, `10`, and `1` (the field name and the constraint attributes)
@@ -472,7 +472,7 @@ Max.degrees=You cannot provide more than {1} {0}
The default `LocalValidatorFactoryBean` configuration suffices for most
cases. There are a number of configuration options for various Bean Validation
constructs, from message interpolation to traversal resolution. See the
{spring-framework-api}/validation/beanvalidation/LocalValidatorFactoryBean.html[`LocalValidatorFactoryBean`]
{api-spring-framework}/validation/beanvalidation/LocalValidatorFactoryBean.html[`LocalValidatorFactoryBean`]
javadoc for more information on these options.
@@ -19,8 +19,8 @@ of the field). This is done as a convenience to aid developers when targeting er
More information on the `MessageCodesResolver` and the default strategy can be found
in the javadoc of
{spring-framework-api}/validation/MessageCodesResolver.html[`MessageCodesResolver`] and
{spring-framework-api}/validation/DefaultMessageCodesResolver.html[`DefaultMessageCodesResolver`],
{api-spring-framework}/validation/MessageCodesResolver.html[`MessageCodesResolver`] and
{api-spring-framework}/validation/DefaultMessageCodesResolver.html[`DefaultMessageCodesResolver`],
respectively.
@@ -139,7 +139,7 @@ Kotlin::
======
The Spring team welcomes community-driven `Formatter` contributions. See
{spring-framework-issues}[GitHub Issues] to contribute.
https://github.com/spring-projects/spring-framework/issues[GitHub Issues] to contribute.
@@ -96,7 +96,7 @@ Kotlin::
The `static` `rejectIfEmpty(..)` method on the `ValidationUtils` class is used to
reject the `name` property if it is `null` or the empty string. Have a look at the
{spring-framework-api}/validation/ValidationUtils.html[`ValidationUtils`] javadoc
{api-spring-framework}/validation/ValidationUtils.html[`ValidationUtils`] javadoc
to see what functionality it provides besides the example shown previously.
While it is certainly possible to implement a single `Validator` class to validate each
@@ -193,7 +193,7 @@ Kotlin::
Validation errors are reported to the `Errors` object passed to the validator. In the case
of Spring Web MVC, you can use the `<spring:bind/>` tag to inspect the error messages, but
you can also inspect the `Errors` object yourself. More information about the
methods it offers can be found in the {spring-framework-api}/validation/Errors.html[javadoc].
methods it offers can be found in the {api-spring-framework}/validation/Errors.html[javadoc].
Validators may also get locally invoked for the immediate validation of a given object,
not involving a binding process. As of 6.1, this has been simplified through a new
@@ -199,7 +199,7 @@ participating in Spring managed transactions. It is generally preferable to writ
own new code by using the higher level abstractions for resource management, such as
`JdbcTemplate` or `DataSourceUtils`.
See the {spring-framework-api}/jdbc/datasource/TransactionAwareDataSourceProxy.html[`TransactionAwareDataSourceProxy`]
See the {api-spring-framework}/jdbc/datasource/TransactionAwareDataSourceProxy.html[`TransactionAwareDataSourceProxy`]
javadoc for more details.
@@ -230,19 +230,6 @@ provided you stick to the required connection lookup pattern. Note that JTA does
savepoints or custom isolation levels and has a different timeout mechanism but otherwise
exposes similar behavior in terms of JDBC resources and JDBC commit/rollback management.
For JTA-style lazy retrieval of actual resource connections, Spring provides a
corresponding `DataSource` proxy class for the target connection pool: see
{spring-framework-api}/jdbc/datasource/LazyConnectionDataSourceProxy.html[`LazyConnectionDataSourceProxy`].
This is particularly useful for potentially empty transactions without actual statement
execution (never fetching an actual resource in such a scenario), and also in front of
a routing `DataSource` which means to take the transaction-synchronized read-only flag
and/or isolation level into account (e.g. `IsolationLevelDataSourceRouter`).
`LazyConnectionDataSourceProxy` also provides special support for a read-only connection
pool to use during a read-only transaction, avoiding the overhead of switching the JDBC
Connection's read-only flag at the beginning and end of every transaction when fetching
it from the primary connection pool (which may be costly depending on the JDBC driver).
NOTE: As of 5.3, Spring provides an extended `JdbcTransactionManager` variant which adds
exception translation capabilities on commit/rollback (aligned with `JdbcTemplate`).
Where `DataSourceTransactionManager` will only ever throw `TransactionSystemException`
@@ -51,7 +51,7 @@ corresponding to the fully qualified class name of the template instance (typica
The following sections provide some examples of `JdbcTemplate` usage. These examples
are not an exhaustive list of all of the functionality exposed by the `JdbcTemplate`.
See the attendant {spring-framework-api}/jdbc/core/JdbcTemplate.html[javadoc] for that.
See the attendant {api-spring-framework}/jdbc/core/JdbcTemplate.html[javadoc] for that.
[[jdbc-JdbcTemplate-examples-query]]
=== Querying (`SELECT`)
@@ -717,7 +717,7 @@ For example, with positional parameters:
public int countOfActorsByFirstName(String firstName) {
return this.jdbcClient.sql("select count(*) from t_actor where first_name = ?")
.param(firstName)
.param(firstName);
.query(Integer.class).single();
}
----
@@ -730,7 +730,7 @@ For example, with named parameters:
public int countOfActorsByFirstName(String firstName) {
return this.jdbcClient.sql("select count(*) from t_actor where first_name = :firstName")
.param("firstName", firstName)
.param("firstName", firstName);
.query(Integer.class).single();
}
----
@@ -759,8 +759,8 @@ With a required single object result:
[source,java,indent=0,subs="verbatim,quotes"]
----
Actor actor = this.jdbcClient.sql("select first_name, last_name from t_actor where id = ?")
.param(1212L)
Actor actor = this.jdbcClient.sql("select first_name, last_name from t_actor where id = ?",
.param(1212L);
.query(Actor.class)
.single();
----
@@ -769,8 +769,8 @@ With a `java.util.Optional` result:
[source,java,indent=0,subs="verbatim,quotes"]
----
Optional<Actor> actor = this.jdbcClient.sql("select first_name, last_name from t_actor where id = ?")
.param(1212L)
Optional<Actor> actor = this.jdbcClient.sql("select first_name, last_name from t_actor where id = ?",
.param(1212L);
.query(Actor.class)
.optional();
----
@@ -780,7 +780,7 @@ And for an update statement:
[source,java,indent=0,subs="verbatim,quotes"]
----
this.jdbcClient.sql("insert into t_actor (first_name, last_name) values (?, ?)")
.param("Leonor").param("Watling")
.param("Leonor").param("Watling");
.update();
----
@@ -789,7 +789,7 @@ Or an update statement with named parameters:
[source,java,indent=0,subs="verbatim,quotes"]
----
this.jdbcClient.sql("insert into t_actor (first_name, last_name) values (:firstName, :lastName)")
.param("firstName", "Leonor").param("lastName", "Watling")
.param("firstName", "Leonor").param("lastName", "Watling");
.update();
----
@@ -800,7 +800,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();
----
@@ -83,7 +83,7 @@ Kotlin::
----
======
See the {spring-framework-api}/jdbc/datasource/embedded/EmbeddedDatabaseBuilder.html[javadoc for `EmbeddedDatabaseBuilder`]
See the {api-spring-framework}/jdbc/datasource/embedded/EmbeddedDatabaseBuilder.html[javadoc for `EmbeddedDatabaseBuilder`]
for further details on all supported options.
You can also use the `EmbeddedDatabaseBuilder` to create an embedded database by using Java
@@ -288,7 +288,7 @@ You can extend Spring JDBC embedded database support in two ways:
connection pool to manage embedded database connections.
We encourage you to contribute extensions to the Spring community at
{spring-framework-issues}[GitHub Issues].
https://github.com/spring-projects/spring-framework/issues[GitHub Issues].
@@ -407,12 +407,6 @@ exposes the Hibernate transaction as a JDBC transaction if you have set up the p
`DataSource` for which the transactions are supposed to be exposed through the
`dataSource` property of the `HibernateTransactionManager` class.
For JTA-style lazy retrieval of actual resource connections, Spring provides a
corresponding `DataSource` proxy class for the target connection pool: see
{spring-framework-api}/jdbc/datasource/LazyConnectionDataSourceProxy.html[`LazyConnectionDataSourceProxy`].
This is particularly useful for Hibernate read-only transactions which can often
be processed from a local cache rather than hitting the database.
[[orm-hibernate-resources]]
== Comparing Container-managed and Locally Defined Resources
@@ -58,8 +58,8 @@ The benefits of using the Spring Framework to create your ORM DAOs include:
TIP: For more comprehensive ORM support, including support for alternative database
technologies such as MongoDB, you might want to check out the
{spring-site-projects}/spring-data/[Spring Data] suite of projects. If you are
a JPA user, the {spring-site-guides}/gs/accessing-data-jpa/[Getting Started Accessing
https://projects.spring.io/spring-data/[Spring Data] suite of projects. If you are
a JPA user, the https://spring.io/guides/gs/accessing-data-jpa/[Getting Started Accessing
Data with JPA] guide from https://spring.io provides a great introduction.
@@ -157,7 +157,7 @@ The `LoadTimeWeaver` interface is a Spring-provided class that lets JPA
`ClassTransformer` instances be plugged in a specific manner, depending on whether the
environment is a web container or application server. Hooking `ClassTransformers`
through an
{java-api}/java.instrument/java/lang/instrument/package-summary.html[agent]
https://docs.oracle.com/javase/6/docs/api/java/lang/instrument/package-summary.html[agent]
is typically not efficient. The agents work against the entire virtual machine and
inspect every class that is loaded, which is usually undesirable in a production
server environment.
@@ -268,8 +268,8 @@ The actual JPA provider bootstrapping is handed off to the specified executor an
running in parallel, to the application bootstrap thread. The exposed `EntityManagerFactory`
proxy can be injected into other application components and is even able to respond to
`EntityManagerFactoryInfo` configuration inspection. However, once the actual JPA provider
is being accessed by other components (for example, calling `createEntityManager`), those
calls block until the background bootstrapping has completed. In particular, when you use
is being accessed by other components (for example, calling `createEntityManager`), those calls
block until the background bootstrapping has completed. In particular, when you use
Spring Data JPA, make sure to set up deferred bootstrapping for its repositories as well.
@@ -284,9 +284,9 @@ to a newly created `EntityManager` per operation, in effect making its usage thr
It is possible to write code against the plain JPA without any Spring dependencies, by
using an injected `EntityManagerFactory` or `EntityManager`. Spring can understand the
`@PersistenceUnit` and `@PersistenceContext` annotations both at the field and the method
level if a `PersistenceAnnotationBeanPostProcessor` is enabled. The following example
shows a plain JPA DAO implementation that uses the `@PersistenceUnit` annotation:
`@PersistenceUnit` and `@PersistenceContext` annotations both at the field and the method level
if a `PersistenceAnnotationBeanPostProcessor` is enabled. The following example shows a plain
JPA DAO implementation that uses the `@PersistenceUnit` annotation:
[tabs]
======
@@ -506,20 +506,13 @@ if you have not already done so, to get more detailed coverage of Spring's decla
The recommended strategy for JPA is local transactions through JPA's native transaction
support. Spring's `JpaTransactionManager` provides many capabilities known from local
JDBC transactions (such as transaction-specific isolation levels and resource-level
read-only optimizations) against any regular JDBC connection pool, without requiring
a JTA transaction coordinator and XA-capable resources.
read-only optimizations) against any regular JDBC connection pool (no XA requirement).
Spring JPA also lets a configured `JpaTransactionManager` expose a JPA transaction
to JDBC access code that accesses the same `DataSource`, provided that the registered
`JpaDialect` supports retrieval of the underlying JDBC `Connection`. Spring provides
dialects for the EclipseLink and Hibernate JPA implementations. See the
xref:data-access/orm/jpa.adoc#orm-jpa-dialect[next section] for details on `JpaDialect`.
For JTA-style lazy retrieval of actual resource connections, Spring provides a
corresponding `DataSource` proxy class for the target connection pool: see
{spring-framework-api}/jdbc/datasource/LazyConnectionDataSourceProxy.html[`LazyConnectionDataSourceProxy`].
This is particularly useful for JPA read-only transactions which can often
be processed from a local cache rather than hitting the database.
`JpaDialect` supports retrieval of the underlying JDBC `Connection`.
Spring provides dialects for the EclipseLink and Hibernate JPA implementations.
See the xref:data-access/orm/jpa.adoc#orm-jpa-dialect[next section] for details on the `JpaDialect` mechanism.
[[orm-jpa-dialect]]
@@ -548,8 +541,8 @@ way of auto-configuring an `EntityManagerFactory` setup for Hibernate or Eclipse
respectively. Note that those provider adapters are primarily designed for use with
Spring-driven transaction management (that is, for use with `JpaTransactionManager`).
See the {spring-framework-api}/orm/jpa/JpaDialect.html[`JpaDialect`] and
{spring-framework-api}/orm/jpa/JpaVendorAdapter.html[`JpaVendorAdapter`] javadoc for
See the {api-spring-framework}/orm/jpa/JpaDialect.html[`JpaDialect`] and
{api-spring-framework}/orm/jpa/JpaVendorAdapter.html[`JpaVendorAdapter`] javadoc for
more details of its operations and how they are used within Spring's JPA support.
@@ -36,8 +36,8 @@ simpler.
[[oxm-consistent-interfaces]]
=== Consistent Interfaces
Spring's O-X mapping operates through two global interfaces: {spring-framework-api}/oxm/Marshaller.html[`Marshaller`] and
{spring-framework-api}/oxm/Unmarshaller.html[`Unmarshaller`]. These abstractions let you switch O-X mapping frameworks
Spring's O-X mapping operates through two global interfaces: {api-spring-framework}/oxm/Marshaller.html[`Marshaller`] and
{api-spring-framework}/oxm/Unmarshaller.html[`Unmarshaller`]. These abstractions let you switch O-X mapping frameworks
with relative ease, with little or no change required on the classes that do the
marshalling. This approach has the additional benefit of making it possible to do XML
marshalling with a mix-and-match approach (for example, some marshalling performed using JAXB
@@ -557,7 +557,7 @@ set the `supportedClasses` property on the `XStreamMarshaller`, as the following
Doing so ensures that only the registered classes are eligible for unmarshalling.
Additionally, you can register
{spring-framework-api}/oxm/xstream/XStreamMarshaller.html#setConverters(com.thoughtworks.xstream.converters.ConverterMatcher...)[custom
{api-spring-framework}/oxm/xstream/XStreamMarshaller.html#setConverters(com.thoughtworks.xstream.converters.ConverterMatcher...)[custom
converters] to make sure that only your supported classes can be unmarshalled. You might
want to add a `CatchAllConverter` as the last converter in the list, in addition to
converters that explicitly support the domain classes that should be supported. As a
@@ -95,7 +95,7 @@ parameter to database bind marker translation.
run.
* `….namedParameters(false)`: Disable named parameter expansion. Enabled by default.
TIP: Dialects are resolved by {spring-framework-api}/r2dbc/core/binding/BindMarkersFactoryResolver.html[`BindMarkersFactoryResolver`]
TIP: Dialects are resolved by {api-spring-framework}/r2dbc/core/binding/BindMarkersFactoryResolver.html[`BindMarkersFactoryResolver`]
from a `ConnectionFactory`, typically by inspecting `ConnectionFactoryMetadata`.
+
You can let Spring auto-discover your `BindMarkersFactory` by registering a
@@ -120,7 +120,7 @@ the reactive sequence to aid debugging.
The following sections provide some examples of `DatabaseClient` usage. These examples
are not an exhaustive list of all of the functionality exposed by the `DatabaseClient`.
See the attendant {spring-framework-api}/r2dbc/core/DatabaseClient.html[javadoc] for that.
See the attendant {api-spring-framework}/r2dbc/core/DatabaseClient.html[javadoc] for that.
[[r2dbc-DatabaseClient-examples-statement]]
==== Executing Statements
@@ -440,8 +440,12 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val tuples: MutableList<Array<Any>> = ArrayList()
tuples.add(arrayOf("John", 35))
tuples.add(arrayOf("Ann", 50))
client.sql("SELECT id, name, state FROM table WHERE age IN (:ages)")
.bind("ages", arrayOf(35, 50))
.bind("tuples", arrayOf(35, 50))
----
======
@@ -748,7 +752,7 @@ the same time, have this client participating in Spring managed transactions. It
preferable to integrate a R2DBC client with proper access to `ConnectionFactoryUtils`
for resource management.
See the {spring-framework-api}/r2dbc/connection/TransactionAwareConnectionFactoryProxy.html[`TransactionAwareConnectionFactoryProxy`]
See the {api-spring-framework}/r2dbc/connection/TransactionAwareConnectionFactoryProxy.html[`TransactionAwareConnectionFactoryProxy`]
javadoc for more details.
@@ -7,7 +7,7 @@ the JTA `UserTransaction` and `TransactionManager` objects) autodetects the loca
the latter object, which varies by application server. Having access to the JTA
`TransactionManager` allows for enhanced transaction semantics -- in particular,
supporting transaction suspension. See the
{spring-framework-api}/transaction/jta/JtaTransactionManager.html[`JtaTransactionManager`]
{api-spring-framework}/transaction/jta/JtaTransactionManager.html[`JtaTransactionManager`]
javadoc for details.
Spring's `JtaTransactionManager` is the standard choice to run on Jakarta EE application
@@ -85,7 +85,7 @@ subclass-level annotation.
When a POJO class such as the one above is defined as a bean in a Spring context,
you can make the bean instance transactional through an `@EnableTransactionManagement`
annotation in a `@Configuration` class. See the
{spring-framework-api}/transaction/annotation/EnableTransactionManagement.html[javadoc]
{api-spring-framework}/transaction/annotation/EnableTransactionManagement.html[javadoc]
for full details.
In XML configuration, the `<tx:annotation-driven/>` tag provides similar convenience:
@@ -262,7 +262,7 @@ is modified) to support `@Transactional` runtime behavior on any kind of method.
| XML Attribute| Annotation Attribute| Default| Description
| `transaction-manager`
| N/A (see {spring-framework-api}/transaction/annotation/TransactionManagementConfigurer.html[`TransactionManagementConfigurer`] javadoc)
| N/A (see {api-spring-framework}/transaction/annotation/TransactionManagementConfigurer.html[`TransactionManagementConfigurer`] javadoc)
| `transactionManager`
| Name of the transaction manager to use. Required only if the name of the transaction
manager is not `transactionManager`, as in the preceding example.
@@ -57,7 +57,8 @@ transaction semantics given by the class annotation (if present). You can annota
regardless of visibility.
To weave your applications with the `AnnotationTransactionAspect`, you must either build
your application with AspectJ (see the {aspectj-docs-devguide}/index.html[AspectJ Development
your application with AspectJ (see the
https://www.eclipse.org/aspectj/doc/released/devguide/index.html[AspectJ Development
Guide]) or use load-time weaving. See xref:core/aop/using-aspectj.adoc#aop-aj-ltw[Load-time weaving with AspectJ in the Spring Framework]
for a discussion of load-time weaving with AspectJ.
@@ -23,7 +23,7 @@ As of Spring Framework 5.2, the default configuration also provides support for
Vavr's `Try` method to trigger transaction rollbacks when it returns a 'Failure'.
This allows you to handle functional-style errors using Try and have the transaction
automatically rolled back in case of a failure. For more information on Vavr's Try,
refer to the https://docs.vavr.io/#_try[official Vavr documentation].
refer to the [official Vavr documentation](https://www.vavr.io/vavr-docs/#_try).
Here's an example of how to use Vavr's Try with a transactional method:
[tabs]
@@ -38,11 +38,6 @@ within the method.
A reactive transaction managed by `ReactiveTransactionManager` uses the Reactor context
instead of thread-local attributes. As a consequence, all participating data access
operations need to execute within the same Reactor context in the same reactive pipeline.
When configured with a `ReactiveTransactionManager`, all transaction-demarcated methods
are expected to return a reactive pipeline. Void methods or regular return types need
to be associated with a regular `PlatformTransactionManager`, e.g. through the
`transactionManager` attribute of the corresponding `@Transactional` declarations.
====
The following image shows a conceptual view of calling a method on a transactional proxy:
@@ -75,6 +75,6 @@ that it can roll back to. Such partial rollbacks let an inner transaction scope
trigger a rollback for its scope, with the outer transaction being able to continue
the physical transaction despite some operations having been rolled back. This setting
is typically mapped onto JDBC savepoints, so it works only with JDBC resource
transactions. See Spring's {spring-framework-api}/jdbc/datasource/DataSourceTransactionManager.html[`DataSourceTransactionManager`].
transactions. See Spring's {api-spring-framework}/jdbc/datasource/DataSourceTransactionManager.html[`DataSourceTransactionManager`].
@@ -63,7 +63,7 @@ For the former, listeners are guaranteed to see the current thread-bound transac
Since the latter uses the Reactor context instead of thread-local variables, the transaction
context needs to be included in the published event instance as the event source.
See the
{spring-framework-api}/transaction/reactive/TransactionalEventPublisher.html[`TransactionalEventPublisher`]
{api-spring-framework}/transaction/reactive/TransactionalEventPublisher.html[`TransactionalEventPublisher`]
javadoc for details.
====

Some files were not shown because too many files have changed in this diff Show More