mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
205 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 14fa75c98a | |||
| f2fd2f1226 | |||
| 072ebb6ffc | |||
| 08e7f7efa4 | |||
| b1fafbf7e1 | |||
| a3647a8c5e | |||
| 5c8d9cd0b2 | |||
| 40d5196243 | |||
| ec2c9b5d0e | |||
| 36539bdaa9 | |||
| 67ba7dd1da | |||
| 7b3fcf2647 | |||
| fd76c33589 | |||
| 1a7a6f421f | |||
| 9ac1feceb5 | |||
| 7029042e44 | |||
| 1a5661d426 | |||
| b208c63414 | |||
| 814c003b43 | |||
| 5c34e1d11a | |||
| d57775bbb2 | |||
| b598ad3f33 | |||
| b44ef70bc3 | |||
| d1b3107398 | |||
| 629c560316 | |||
| 7bf07ef393 | |||
| ca602ef874 | |||
| e9bf5f5569 | |||
| 9a6f636e17 | |||
| 7686f5467e | |||
| 429c477f6a | |||
| 5187281b50 | |||
| 24a4487050 | |||
| 8fba4a448a | |||
| 8f286de773 | |||
| ee100cfba7 | |||
| aaf90f5a96 | |||
| 70be04ba81 | |||
| b976ee3f67 | |||
| b11ff96652 | |||
| 667e4e7531 | |||
| 5fd9fab0ce | |||
| 95a8646309 | |||
| 5434edd726 | |||
| 5f13ea95fb | |||
| 74bb42b78f | |||
| 55717adf88 | |||
| 72835f10b9 | |||
| 125ebd029c | |||
| d8d4fa0e24 | |||
| bfd3b3ad88 | |||
| 0909161ba1 | |||
| 4910c217dc | |||
| f262046ef9 | |||
| d50d4a9092 | |||
| b06305e64f | |||
| c749a14326 | |||
| 3817936ca5 | |||
| 9bd2be80b9 | |||
| c6e9cd0c62 | |||
| e2a5cfb459 | |||
| b484ab116f | |||
| c668473186 | |||
| 38595c6a99 | |||
| d756c2b128 | |||
| 3be4322f71 | |||
| 328e444db3 | |||
| 94bdd4380f | |||
| 3f9d479583 | |||
| 338922f03d | |||
| fad857e06a | |||
| b8395a2321 | |||
| 6df8be8be3 | |||
| 4a599d0b22 | |||
| a1463c2bf2 | |||
| 23eff5c650 | |||
| c44bb29aa5 | |||
| 0c22866b72 | |||
| 0c6957e395 | |||
| 8d51fc0444 | |||
| bad01011da | |||
| 867a199507 | |||
| 1b4a4ac51f | |||
| dd0d26b4ba | |||
| d074f660a1 | |||
| 1b8baffbd7 | |||
| 56c63c779f | |||
| 198cf063fd | |||
| b1b6b544a2 | |||
| 9d13ea290f | |||
| c9163b77df | |||
| 033c8df53f | |||
| b272f4e615 | |||
| bafff5079f | |||
| ccaecab500 | |||
| 67e03105b5 | |||
| 76bc9cf325 | |||
| db52c77cca | |||
| 1e742aae34 | |||
| 20dd585c93 | |||
| 707eb701dc | |||
| 2c97996796 | |||
| 3a068b807b | |||
| f54b19ff90 | |||
| 494d2ab727 | |||
| 627d9cf8be | |||
| dd3a67c7ab | |||
| 85cc229063 | |||
| fa95f12be0 | |||
| 035cc72fc8 | |||
| 29a39b617e | |||
| a1471a9266 | |||
| 21793b4f93 | |||
| 2d255c4d5e | |||
| 10391586d1 | |||
| edadc79835 | |||
| 3783d31c09 | |||
| 87730f76b1 | |||
| 6fae3e150e | |||
| ad44e8ab0a | |||
| aadf96ba92 | |||
| 2baf064d04 | |||
| 6121e2f526 | |||
| 8e33805d29 | |||
| 1f19bb2311 | |||
| 2784410cc6 | |||
| f4ac323409 | |||
| 6db00e63c7 | |||
| ed613e767a | |||
| 65781046cf | |||
| 0ee36095e7 | |||
| 0fc38117df | |||
| 86b8a70ce6 | |||
| 4bf759d872 | |||
| 770cbd2fb5 | |||
| e5f04e5ddf | |||
| c18784678d | |||
| 51cdff591c | |||
| d93114df9a | |||
| 5ac4c3bd76 | |||
| 05c3ffb2fb | |||
| 19e8ed130c | |||
| 4464251754 | |||
| 8868fe2ea5 | |||
| c373f496f3 | |||
| eb1883bdc4 | |||
| df6f66110f | |||
| ac235a0c43 | |||
| a4e3af5cbe | |||
| e71117dcdf | |||
| d5874ab99e | |||
| 99327b7db1 | |||
| 3e06441d97 | |||
| 01f2925048 | |||
| 44a37000ec | |||
| 53fe5fafed | |||
| b9bd98fc5b | |||
| 7714110940 | |||
| 75d1278bde | |||
| 620f558547 | |||
| 1e78cc35e5 | |||
| 11fdb5ba17 | |||
| 9957bb6918 | |||
| 6a7a0bddb7 | |||
| 5c012bbb0c | |||
| 5df6e8825d | |||
| 654e822676 | |||
| e943058b18 | |||
| cafb38ad1d | |||
| e778d2e908 | |||
| c5bcfc7682 | |||
| 5752e03d97 | |||
| dc26d3b0ec | |||
| 7f94c64b72 | |||
| 50e55d5219 | |||
| fcd4ba2f1f | |||
| e73107341c | |||
| f8a33cd66e | |||
| f088d4a05b | |||
| 84c28995fb | |||
| 925fa0272b | |||
| 09aa59f9e7 | |||
| 7874a59771 | |||
| 4dab35205d | |||
| 5c6b9be3a1 | |||
| 33fba8ea0c | |||
| 9aa707ec4b | |||
| ea30c8fb5b | |||
| 6bdf7ad36a | |||
| d2108d2db6 | |||
| f9be717602 | |||
| bb446a3905 | |||
| f3dce4bb9a | |||
| 70cb96c1d8 | |||
| 6ec264252b | |||
| 187b4e5ea6 | |||
| 3bc607df53 | |||
| 69c92f9ac7 | |||
| 875eeabb6f | |||
| 7a60e2024b | |||
| da95542d8f | |||
| 6669ab1ae7 | |||
| d05ac097dd | |||
| 35103b0cd1 | |||
| 3b50f992fe |
@@ -1,36 +0,0 @@
|
||||
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>
|
||||
+2
-3
@@ -75,7 +75,7 @@ configure([rootProject] + javaProjects) { project ->
|
||||
}
|
||||
|
||||
checkstyle {
|
||||
toolVersion = "10.12.4"
|
||||
toolVersion = "10.12.7"
|
||||
configDirectory.set(rootProject.file("src/checkstyle"))
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ configure([rootProject] + javaProjects) { project ->
|
||||
// JSR-305 only used for non-required meta-annotations
|
||||
compileOnly("com.google.code.findbugs:jsr305")
|
||||
testCompileOnly("com.google.code.findbugs:jsr305")
|
||||
checkstyle("io.spring.javaformat:spring-javaformat-checkstyle:0.0.39")
|
||||
checkstyle("io.spring.javaformat:spring-javaformat-checkstyle:0.0.41")
|
||||
}
|
||||
|
||||
ext.javadocLinks = [
|
||||
@@ -146,7 +146,6 @@ configure(rootProject) {
|
||||
description = "Spring Framework"
|
||||
|
||||
apply plugin: "io.spring.nohttp"
|
||||
apply plugin: 'org.springframework.build.api-diff'
|
||||
|
||||
nohttp {
|
||||
source.exclude "**/test-output/**"
|
||||
|
||||
@@ -22,21 +22,6 @@ 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
|
||||
|
||||
|
||||
@@ -19,16 +19,11 @@ ext {
|
||||
dependencies {
|
||||
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
|
||||
implementation("org.jetbrains.kotlin:kotlin-compiler-embeddable:${kotlinVersion}")
|
||||
implementation "me.champeau.gradle:japicmp-gradle-plugin:0.3.0"
|
||||
implementation "org.gradle:test-retry-gradle-plugin:1.4.1"
|
||||
implementation "org.gradle:test-retry-gradle-plugin:1.5.6"
|
||||
}
|
||||
|
||||
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,140 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2019 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) {
|
||||
Path outDir = Paths.get(project.getRootProject().getBuildDir().getAbsolutePath(),
|
||||
"reports", "api-diff",
|
||||
baseLineVersion + "_to_" + project.getRootProject().getVersion());
|
||||
return project.file(outDir.resolve(project.getName() + ".html").toString());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ubuntu:jammy-20230816
|
||||
FROM ubuntu:jammy-20240111
|
||||
|
||||
ADD setup.sh /setup.sh
|
||||
ADD get-jdk-url.sh /get-jdk-url.sh
|
||||
@@ -6,6 +6,5 @@ RUN ./setup.sh
|
||||
|
||||
ENV JAVA_HOME /opt/openjdk/java17
|
||||
ENV JDK17 /opt/openjdk/java17
|
||||
ENV JDK20 /opt/openjdk/java20
|
||||
|
||||
ENV PATH $JAVA_HOME/bin:$PATH
|
||||
|
||||
@@ -3,10 +3,7 @@ set -e
|
||||
|
||||
case "$1" in
|
||||
java17)
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/17.0.7+7/bellsoft-jdk17.0.7+7-linux-amd64.tar.gz"
|
||||
;;
|
||||
java20)
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/20.0.1+10/bellsoft-jdk20.0.1+10-linux-amd64.tar.gz"
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/17.0.10%2B13/bellsoft-jdk17.0.10+13-linux-amd64.tar.gz"
|
||||
;;
|
||||
*)
|
||||
echo $"Unknown java version"
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ curl https://raw.githubusercontent.com/spring-io/concourse-java-scripts/v0.0.4/c
|
||||
|
||||
mkdir -p /opt/openjdk
|
||||
pushd /opt/openjdk > /dev/null
|
||||
for jdk in java17 java20
|
||||
for jdk in java17
|
||||
do
|
||||
JDK_URL=$( /get-jdk-url.sh $jdk )
|
||||
mkdir $jdk
|
||||
|
||||
+1
-44
@@ -90,13 +90,6 @@ 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
|
||||
@@ -113,14 +106,6 @@ resources:
|
||||
access_token: ((github-ci-status-token))
|
||||
branch: ((branch))
|
||||
context: build
|
||||
- name: repo-status-jdk20-build
|
||||
type: github-status-resource
|
||||
icon: eye-check-outline
|
||||
source:
|
||||
repository: ((github-repo-name))
|
||||
access_token: ((github-ci-status-token))
|
||||
branch: ((branch))
|
||||
context: jdk20-build
|
||||
- name: slack-alert
|
||||
type: slack-notification
|
||||
icon: slack
|
||||
@@ -217,34 +202,6 @@ jobs:
|
||||
"zip.type": "schema"
|
||||
get_params:
|
||||
threads: 8
|
||||
- name: jdk20-build
|
||||
serial: true
|
||||
public: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
- get: every-morning
|
||||
trigger: true
|
||||
- put: repo-status-jdk20-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: 20
|
||||
<<: *build-project-task-params
|
||||
on_failure:
|
||||
do:
|
||||
- put: repo-status-jdk20-build
|
||||
params: { state: "failure", commit: "git-repo" }
|
||||
- put: slack-alert
|
||||
params:
|
||||
<<: *slack-fail-params
|
||||
- put: repo-status-jdk20-build
|
||||
params: { state: "success", commit: "git-repo" }
|
||||
- name: stage-milestone
|
||||
serial: true
|
||||
plan:
|
||||
@@ -396,7 +353,7 @@ jobs:
|
||||
|
||||
groups:
|
||||
- name: "builds"
|
||||
jobs: ["build", "jdk20-build"]
|
||||
jobs: ["build"]
|
||||
- name: "releases"
|
||||
jobs: ["stage-milestone", "stage-rc", "stage-release", "promote-milestone", "promote-rc", "promote-release", "create-github-release"]
|
||||
- name: "ci-images"
|
||||
|
||||
@@ -4,6 +4,6 @@ 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,JDK20 \
|
||||
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Porg.gradle.java.installations.fromEnv=JDK17 \
|
||||
-PmainToolchain=${MAIN_TOOLCHAIN} -PtestToolchain=${TEST_TOOLCHAIN} --no-daemon --max-workers=4 check antora
|
||||
popd > /dev/null
|
||||
|
||||
@@ -39,8 +39,8 @@
|
||||
** xref:core/resources.adoc[]
|
||||
** xref:core/validation.adoc[]
|
||||
*** xref:core/validation/validator.adoc[]
|
||||
*** xref:core/validation/conversion.adoc[]
|
||||
*** xref:core/validation/beans-beans.adoc[]
|
||||
*** xref:core/validation/conversion.adoc[]
|
||||
*** xref:core/validation/convert.adoc[]
|
||||
*** xref:core/validation/format.adoc[]
|
||||
*** xref:core/validation/format-configuring-formatting-globaldatetimeformat.adoc[]
|
||||
@@ -431,4 +431,4 @@
|
||||
** xref:languages/groovy.adoc[]
|
||||
** xref:languages/dynamic.adoc[]
|
||||
* xref:appendix.adoc[]
|
||||
* https://github.com/spring-projects/spring-framework/wiki[Wiki]
|
||||
* https://github.com/spring-projects/spring-framework/wiki[Wiki]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -6,8 +6,8 @@ it until later.
|
||||
|
||||
By default, there is a single instance of each aspect within the application
|
||||
context. AspectJ calls this the singleton instantiation model. It is possible to define
|
||||
aspects with alternate lifecycles. Spring supports AspectJ's `perthis` and `pertarget`
|
||||
instantiation models; `percflow`, `percflowbelow`, and `pertypewithin` are not currently
|
||||
aspects with alternate lifecycles. Spring supports AspectJ's `perthis`, `pertarget`, and
|
||||
`pertypewithin` instantiation models; `percflow` and `percflowbelow` are not currently
|
||||
supported.
|
||||
|
||||
You can declare a `perthis` aspect by specifying a `perthis` clause in the `@Aspect`
|
||||
|
||||
@@ -565,6 +565,17 @@ is a convenience mechanism that sets up a xref:core/beans/factory-extension.adoc
|
||||
for you. If you need more control over the specific
|
||||
`PropertySourcesPlaceholderConfigurer` setup, you can explicitly define it as a bean yourself.
|
||||
|
||||
[WARNING]
|
||||
=====
|
||||
Only one such element should be defined for a given application with the properties
|
||||
that it needs. Several property placeholders can be configured as long as they have distinct
|
||||
placeholder syntax (`${...}`).
|
||||
|
||||
If you need to modularize the source of properties used for the replacement, you should
|
||||
not create multiple properties placeholders. Rather, each module should contribute a
|
||||
`PropertySource` to the `Environment`. Alternatively, you can create your own
|
||||
`PropertySourcesPlaceholderConfigurer` bean that gathers the properties to use.
|
||||
=====
|
||||
|
||||
[[xsd-schemas-context-ac]]
|
||||
=== Using `<annotation-config/>`
|
||||
|
||||
@@ -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:web/webflux-webclient/client-builder.adoc#webflux-client-builder-reactor-resources[Resources])
|
||||
xref:core/resources.adoc[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].
|
||||
|
||||
@@ -562,8 +562,9 @@ 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.
|
||||
|
||||
You can change the name of the default profile by using `setDefaultProfiles()` on
|
||||
the `Environment` or, declaratively, by using the `spring.profiles.default` property.
|
||||
The name of the default profile is `default`. You can change the name of
|
||||
the default profile by using `setDefaultProfiles()` on the `Environment` or,
|
||||
declaratively, by using the `spring.profiles.default` property.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -372,6 +372,17 @@ The `PropertySourcesPlaceholderConfigurer` not only looks for properties in the
|
||||
file you specify. By default, if it cannot find a property in the specified properties files,
|
||||
it checks against Spring `Environment` properties and regular Java `System` properties.
|
||||
|
||||
[WARNING]
|
||||
=====
|
||||
Only one such element should be defined for a given application with the properties
|
||||
that it needs. Several property placeholders can be configured as long as they have distinct
|
||||
placeholder syntax (`${...}`).
|
||||
|
||||
If you need to modularize the source of properties used for the replacement, you should
|
||||
not create multiple properties placeholders. Rather, you should create your own
|
||||
`PropertySourcesPlaceholderConfigurer` bean that gathers the properties to use.
|
||||
=====
|
||||
|
||||
[TIP]
|
||||
=====
|
||||
You can use the `PropertySourcesPlaceholderConfigurer` to substitute class names, which
|
||||
|
||||
@@ -324,7 +324,6 @@ Kotlin::
|
||||
|
||||
|
||||
|
||||
|
||||
[[beans-factory-scopes-application]]
|
||||
=== Application Scope
|
||||
|
||||
@@ -374,7 +373,6 @@ Kotlin::
|
||||
|
||||
|
||||
|
||||
|
||||
[[beans-factory-scopes-websocket]]
|
||||
=== WebSocket Scope
|
||||
|
||||
@@ -384,7 +382,6 @@ xref:web/websocket/stomp/scope.adoc[WebSocket scope] for more details.
|
||||
|
||||
|
||||
|
||||
|
||||
[[beans-factory-scopes-other-injection]]
|
||||
=== Scoped Beans as Dependencies
|
||||
|
||||
|
||||
+7
-5
@@ -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,16 +13,18 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// returns ['Smiljan', 'Idvor' ]
|
||||
List placesOfBirth = (List)parser.parseExpression("members.![placeOfBirth.city]");
|
||||
// evaluates to ["SmilJan", "Idvor"]
|
||||
List placesOfBirth = parser.parseExpression("members.![placeOfBirth.city]")
|
||||
.getValue(societyContext, List.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// returns ['Smiljan', 'Idvor' ]
|
||||
val placesOfBirth = parser.parseExpression("members.![placeOfBirth.city]") as List<*>
|
||||
// evaluates to ["SmilJan", "Idvor"]
|
||||
val placesOfBirth = parser.parseExpression("members.![placeOfBirth.city]")
|
||||
.getValue(societyContext) as List<*>
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
+12
-11
@@ -28,13 +28,14 @@ Kotlin::
|
||||
======
|
||||
|
||||
Selection is supported for arrays and anything that implements `java.lang.Iterable` or
|
||||
`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.
|
||||
`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.
|
||||
|
||||
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:
|
||||
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:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -42,21 +43,21 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
Map newMap = parser.parseExpression("map.?[value<27]").getValue();
|
||||
Map newMap = parser.parseExpression("#map.?[value < 27]").getValue(Map.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val newMap = parser.parseExpression("map.?[value<27]").getValue()
|
||||
val newMap = parser.parseExpression("#map.?[value < 27]").getValue() as Map
|
||||
----
|
||||
======
|
||||
|
||||
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, the syntax is
|
||||
`.^[selectionExpression]`. To obtain the last matching selection, the syntax is
|
||||
`.$[selectionExpression]`.
|
||||
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]`.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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]
|
||||
======
|
||||
|
||||
+1
-1
@@ -76,7 +76,7 @@ Kotlin::
|
||||
|
||||
Used at the class level as above, the annotation indicates a default for all methods of
|
||||
the declaring class (as well as its subclasses). Alternatively, each method can be
|
||||
annotated individually. See xref:data-access/transaction/declarative/annotations.adoc#transaction-declarative-annotations-method-visibility[null] for
|
||||
annotated individually. See xref:data-access/transaction/declarative/annotations.adoc#transaction-declarative-annotations-method-visibility[method visibility] for
|
||||
further details on which methods Spring considers transactional. Note that a class-level
|
||||
annotation does not apply to ancestor classes up the class hierarchy; in such a scenario,
|
||||
inherited methods need to be locally redeclared in order to participate in a
|
||||
|
||||
@@ -85,7 +85,7 @@ email when someone places an order:
|
||||
|
||||
// Call the collaborators to persist the order...
|
||||
|
||||
// Create a thread safe "copy" of the template message and customize it
|
||||
// Create a thread-safe "copy" of the template message and customize it
|
||||
SimpleMailMessage msg = new SimpleMailMessage(this.templateMessage);
|
||||
msg.setTo(order.getCustomer().getEmailAddress());
|
||||
msg.setText(
|
||||
|
||||
@@ -108,7 +108,7 @@ By default, the following `KeyValues` are created:
|
||||
|===
|
||||
|Name | Description
|
||||
|`exception` _(required)_|Name of the exception thrown during the exchange, or `KeyValue#NONE_VALUE`} if no exception happened.
|
||||
|`method` _(required)_|Name of HTTP request method or `"none"` if the request was not received properly.
|
||||
|`method` _(required)_|Name of HTTP request method or `"none"` if not a well-known method.
|
||||
|`outcome` _(required)_|Outcome of the HTTP server exchange.
|
||||
|`status` _(required)_|HTTP response raw status code, or `"UNKNOWN"` if no response was created.
|
||||
|`uri` _(required)_|URI pattern for the matching handler if available, falling back to `REDIRECTION` for 3xx responses, `NOT_FOUND` for 404 responses, `root` for requests with no path info, and `UNKNOWN` for all other requests.
|
||||
@@ -141,7 +141,7 @@ By default, the following `KeyValues` are created:
|
||||
|===
|
||||
|Name | Description
|
||||
|`exception` _(required)_|Name of the exception thrown during the exchange, or `"none"` if no exception happened.
|
||||
|`method` _(required)_|Name of HTTP request method or `"none"` if the request was not received properly.
|
||||
|`method` _(required)_|Name of HTTP request method or `"none"` if not a well-known method.
|
||||
|`outcome` _(required)_|Outcome of the HTTP server exchange.
|
||||
|`status` _(required)_|HTTP response raw status code, or `"UNKNOWN"` if no response was created.
|
||||
|`uri` _(required)_|URI pattern for the matching handler if available, falling back to `REDIRECTION` for 3xx responses, `NOT_FOUND` for 404 responses, `root` for requests with no path info, and `UNKNOWN` for all other requests.
|
||||
@@ -174,7 +174,7 @@ Instrumentation uses the `org.springframework.http.client.observation.ClientRequ
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`method` _(required)_|Name of HTTP request method or `"none"` if the request could not be created.
|
||||
|`method` _(required)_|Name of HTTP request method or `"none"` if not a well-known method.
|
||||
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. Only the path part of the URI is considered.
|
||||
|`client.name` _(required)_|Client name derived from the request URI host.
|
||||
|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received.
|
||||
@@ -203,7 +203,7 @@ Instrumentation uses the `org.springframework.web.reactive.function.client.Clien
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`method` _(required)_|Name of HTTP request method or `"none"` if the request could not be created.
|
||||
|`method` _(required)_|Name of HTTP request method or `"none"` if not a well-known method.
|
||||
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. Only the path part of the URI is considered.
|
||||
|`client.name` _(required)_|Client name derived from the request URI host.
|
||||
|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received.
|
||||
|
||||
@@ -97,11 +97,7 @@ preparation and response extraction through callback interfaces.
|
||||
|
||||
The default constructor uses `java.net.HttpURLConnection` to perform requests. You can
|
||||
switch to a different HTTP library with an implementation of `ClientHttpRequestFactory`.
|
||||
There is built-in support for the following:
|
||||
|
||||
* Apache HttpComponents
|
||||
* Netty
|
||||
* OkHttp
|
||||
Currently, there is also built-in support for Apache HttpComponents and OkHttp.
|
||||
|
||||
For example, to switch to Apache HttpComponents, you can use the following:
|
||||
|
||||
|
||||
+1
-1
@@ -152,7 +152,7 @@ Kotlin::
|
||||
@Autowired
|
||||
lateinit var accountService: AccountService
|
||||
|
||||
lateinit mockMvc: MockMvc
|
||||
lateinit var mockMvc: MockMvc
|
||||
|
||||
@BeforeEach
|
||||
fun setup(wac: WebApplicationContext) {
|
||||
|
||||
+4
-1
@@ -166,4 +166,7 @@ By default, any argument that is not a simple value type (as determined by
|
||||
and is not resolved by any other argument resolver is treated as if it were annotated
|
||||
with `@ModelAttribute`.
|
||||
|
||||
|
||||
WARNING: When compiling to a native image with GraalVM, the implicit `@ModelAttribute`
|
||||
support described above does not allow proper ahead-of-time inference of related data
|
||||
binding reflection hints. As a consequence, it is recommended to explicitly annotate
|
||||
method parameters with `@ModelAttribute` for use in a GraalVM native image.
|
||||
|
||||
@@ -70,8 +70,9 @@ it does the same, but it also compares the computed value against the `If-None-M
|
||||
request header and, if the two are equal, returns a 304 (NOT_MODIFIED).
|
||||
|
||||
This strategy saves network bandwidth but not CPU, as the full response must be computed for each request.
|
||||
State-changing HTTP methods and other HTTP conditional request headers such as `If-Match` and `If-Unmodified-Since` are outside the scope of this filter.
|
||||
Other strategies at the controller level can avoid the computation and have a broader support for HTTP conditional requests.
|
||||
State-changing HTTP methods and other HTTP conditional request headers such as `If-Match` and
|
||||
`If-Unmodified-Since` are outside the scope of this filter. Other strategies at the controller level
|
||||
can avoid the computation and have a broader support for HTTP conditional requests.
|
||||
See xref:web/webmvc/mvc-caching.adoc[HTTP Caching].
|
||||
|
||||
This filter has a `writeWeakETag` parameter that configures the filter to write weak ETags
|
||||
|
||||
+4
-1
@@ -216,4 +216,7 @@ By default, any argument that is not a simple value type (as determined by
|
||||
and is not resolved by any other argument resolver is treated as if it were annotated
|
||||
with `@ModelAttribute`.
|
||||
|
||||
|
||||
WARNING: When compiling to a native image with GraalVM, the implicit `@ModelAttribute`
|
||||
support described above does not allow proper ahead-of-time inference of related data
|
||||
binding reflection hints. As a consequence, it is recommended to explicitly annotate
|
||||
method parameters with `@ModelAttribute` for use in a GraalVM native image.
|
||||
|
||||
@@ -229,34 +229,27 @@ Java initialization API. The following example shows how to do so:
|
||||
|
||||
|
||||
[[websocket-server-runtime-configuration]]
|
||||
== Server Configuration
|
||||
== Configuring the Server
|
||||
[.small]#xref:web/webflux-websocket.adoc#webflux-websocket-server-config[See equivalent in the Reactive stack]#
|
||||
|
||||
Each underlying WebSocket engine exposes configuration properties that control
|
||||
runtime characteristics, such as the size of message buffer sizes, idle timeout,
|
||||
and others.
|
||||
You can configure of the underlying WebSocket server such as input message buffer size,
|
||||
idle timeout, and more.
|
||||
|
||||
For Tomcat, WildFly, and GlassFish, you can add a `ServletServerContainerFactoryBean` to your
|
||||
WebSocket Java config, as the following example shows:
|
||||
For Jakarta WebSocket servers, you can add a `ServletServerContainerFactoryBean` to your
|
||||
Java configuration. For example:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSocket
|
||||
public class WebSocketConfig implements WebSocketConfigurer {
|
||||
|
||||
@Bean
|
||||
public ServletServerContainerFactoryBean createWebSocketContainer() {
|
||||
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
|
||||
container.setMaxTextMessageBufferSize(8192);
|
||||
container.setMaxBinaryMessageBufferSize(8192);
|
||||
return container;
|
||||
}
|
||||
|
||||
}
|
||||
@Bean
|
||||
public ServletServerContainerFactoryBean createWebSocketContainer() {
|
||||
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
|
||||
container.setMaxTextMessageBufferSize(8192);
|
||||
container.setMaxBinaryMessageBufferSize(8192);
|
||||
return container;
|
||||
}
|
||||
----
|
||||
|
||||
The following example shows the XML configuration equivalent of the preceding example:
|
||||
Or to your XML configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes,attributes"]
|
||||
----
|
||||
@@ -277,12 +270,11 @@ The following example shows the XML configuration equivalent of the preceding ex
|
||||
</beans>
|
||||
----
|
||||
|
||||
NOTE: For client-side WebSocket configuration, you should use `WebSocketContainerFactoryBean`
|
||||
(XML) or `ContainerProvider.getWebSocketContainer()` (Java configuration).
|
||||
NOTE: For client Jakarta WebSocket configuration, use
|
||||
ContainerProvider.getWebSocketContainer() in Java configuration, or
|
||||
`WebSocketContainerFactoryBean` in XML.
|
||||
|
||||
For Jetty, you need to supply a pre-configured Jetty `WebSocketServerFactory` and plug
|
||||
that into Spring's `DefaultHandshakeHandler` through your WebSocket Java config.
|
||||
The following example shows how to do so:
|
||||
For Jetty, you can supply a `Consumer` callback to configure the WebSocket server:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -298,11 +290,9 @@ The following example shows how to do so:
|
||||
|
||||
@Bean
|
||||
public DefaultHandshakeHandler handshakeHandler() {
|
||||
|
||||
WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
|
||||
policy.setInputBufferSize(8192);
|
||||
policy.setIdleTimeout(600000);
|
||||
|
||||
return new DefaultHandshakeHandler(
|
||||
new JettyRequestUpgradeStrategy(new WebSocketServerFactory(policy)));
|
||||
}
|
||||
@@ -349,6 +339,10 @@ The following example shows the XML configuration equivalent of the preceding ex
|
||||
</beans>
|
||||
----
|
||||
|
||||
TIP: When using STOMP over WebSocket, you will also need to configure
|
||||
xref:web/websocket/stomp/server-config.adoc[STOMP WebSocket transport]
|
||||
properties.
|
||||
|
||||
|
||||
|
||||
[[websocket-server-allowed-origins]]
|
||||
|
||||
+3
-3
@@ -103,9 +103,9 @@ You can also use the WebSocket transport configuration shown earlier to configur
|
||||
maximum allowed size for incoming STOMP messages. In theory, a WebSocket
|
||||
message can be almost unlimited in size. In practice, WebSocket servers impose
|
||||
limits -- for example, 8K on Tomcat and 64K on Jetty. For this reason, STOMP clients
|
||||
(such as the JavaScript https://github.com/JSteunou/webstomp-client[webstomp-client]
|
||||
and others) split larger STOMP messages at 16K boundaries and send them as multiple
|
||||
WebSocket messages, which requires the server to buffer and re-assemble.
|
||||
such as https://github.com/stomp-js/stompjs[`stomp-js/stompjs`] and others split larger
|
||||
STOMP messages at 16K boundaries and send them as multiple WebSocket messages,
|
||||
which requires the server to buffer and re-assemble.
|
||||
|
||||
Spring's STOMP-over-WebSocket support does this ,so applications can configure the
|
||||
maximum size for STOMP messages irrespective of WebSocket server-specific message
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
[[websocket-stomp-server-config]]
|
||||
= WebSocket Server
|
||||
= WebSocket Transport
|
||||
|
||||
To configure the underlying WebSocket server, the information in
|
||||
xref:web/websocket/server.adoc#websocket-server-runtime-configuration[Server Configuration] applies. For Jetty, however you need to set
|
||||
the `HandshakeHandler` and `WebSocketPolicy` through the `StompEndpointRegistry`:
|
||||
This section explains how to configure the underlying WebSocket server transport.
|
||||
|
||||
For Jakarta WebSocket servers, add a `ServletServerContainerFactoryBean` to your
|
||||
configuration. For examples, see
|
||||
xref:web/websocket/server.adoc#websocket-server-runtime-configuration[Configuring the Server]
|
||||
under the WebSocket section.
|
||||
|
||||
For Jetty WebSocket servers, customize the `JettyRequestUpgradeStrategy` as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -29,5 +34,20 @@ the `HandshakeHandler` and `WebSocketPolicy` through the `StompEndpointRegistry`
|
||||
}
|
||||
----
|
||||
|
||||
In addition to WebSocket server properties, there are also STOMP WebSocket transport properties
|
||||
to customize as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSocketMessageBroker
|
||||
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureWebSocketTransport(WebSocketTransportRegistration registry) {
|
||||
registry.setMessageSizeLimit(4 * 8192);
|
||||
registry.setTimeToFirstMessage(30000);
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
@@ -8,30 +8,30 @@ javaPlatform {
|
||||
|
||||
dependencies {
|
||||
api(platform("com.fasterxml.jackson:jackson-bom:2.14.3"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.10.12"))
|
||||
api(platform("io.netty:netty-bom:4.1.100.Final"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.10.13"))
|
||||
api(platform("io.netty:netty-bom:4.1.107.Final"))
|
||||
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
|
||||
api(platform("io.projectreactor:reactor-bom:2022.0.12"))
|
||||
api(platform("io.projectreactor:reactor-bom:2022.0.17"))
|
||||
api(platform("io.rsocket:rsocket-bom:1.1.3"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.15"))
|
||||
api(platform("org.apache.logging.log4j:log4j-bom:2.20.0"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:11.0.17"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.19"))
|
||||
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:11.0.20"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.4"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.4.0"))
|
||||
api(platform("org.junit:junit-bom:5.9.3"))
|
||||
api(platform("org.mockito:mockito-bom:5.6.0"))
|
||||
api(platform("org.mockito:mockito-bom:5.11.0"))
|
||||
|
||||
constraints {
|
||||
api("com.fasterxml:aalto-xml:1.3.2")
|
||||
api("com.fasterxml.woodstox:woodstox-core:6.5.1")
|
||||
api("com.github.ben-manes.caffeine:caffeine:3.1.8")
|
||||
api("com.github.librepdf:openpdf:1.3.30")
|
||||
api("com.github.librepdf:openpdf:1.3.42")
|
||||
api("com.google.code.findbugs:findbugs:3.0.1")
|
||||
api("com.google.code.findbugs:jsr305:3.0.2")
|
||||
api("com.google.code.gson:gson:2.10.1")
|
||||
api("com.google.protobuf:protobuf-java-util:3.23.2")
|
||||
api("com.googlecode.protobuf-java-format:protobuf-java-format:1.4")
|
||||
api("com.h2database:h2:2.2.220")
|
||||
api("com.h2database:h2:2.2.224")
|
||||
api("com.jayway.jsonpath:json-path:2.8.0")
|
||||
api("com.rometools:rome:1.19.0")
|
||||
api("com.squareup.okhttp3:mockwebserver:3.14.9")
|
||||
@@ -43,7 +43,7 @@ dependencies {
|
||||
api("com.sun.xml.bind:jaxb-xjc:3.0.2")
|
||||
api("com.thoughtworks.qdox:qdox:2.0.3")
|
||||
api("com.thoughtworks.xstream:xstream:1.4.20")
|
||||
api("commons-io:commons-io:2.11.0")
|
||||
api("commons-io:commons-io:2.15.0")
|
||||
api("de.bechte.junit:junit-hierarchicalcontextrunner:4.12.2")
|
||||
api("io.micrometer:context-propagation:1.0.6")
|
||||
api("io.mockk:mockk:1.13.4")
|
||||
@@ -52,11 +52,11 @@ dependencies {
|
||||
api("io.r2dbc:r2dbc-h2:1.0.0.RELEASE")
|
||||
api("io.r2dbc:r2dbc-spi-test:1.0.0.RELEASE")
|
||||
api("io.r2dbc:r2dbc-spi:1.0.0.RELEASE")
|
||||
api("io.reactivex.rxjava3:rxjava:3.1.6")
|
||||
api("io.smallrye.reactive:mutiny:1.9.0")
|
||||
api("io.undertow:undertow-core:2.3.8.Final")
|
||||
api("io.undertow:undertow-servlet:2.3.8.Final")
|
||||
api("io.undertow:undertow-websockets-jsr:2.3.8.Final")
|
||||
api("io.reactivex.rxjava3:rxjava:3.1.8")
|
||||
api("io.smallrye.reactive:mutiny:1.10.0")
|
||||
api("io.undertow:undertow-core:2.3.12.Final")
|
||||
api("io.undertow:undertow-servlet:2.3.12.Final")
|
||||
api("io.undertow:undertow-websockets-jsr:2.3.12.Final")
|
||||
api("io.vavr:vavr:0.10.4")
|
||||
api("jakarta.activation:jakarta.activation-api:2.0.1")
|
||||
api("jakarta.annotation:jakarta.annotation-api:2.0.0")
|
||||
@@ -89,20 +89,20 @@ dependencies {
|
||||
api("net.sf.jopt-simple:jopt-simple:5.0.4")
|
||||
api("net.sourceforge.htmlunit:htmlunit:2.70.0")
|
||||
api("org.apache-extras.beanshell:bsh:2.0b6")
|
||||
api("org.apache.activemq:activemq-broker:5.17.4")
|
||||
api("org.apache.activemq:activemq-kahadb-store:5.17.4")
|
||||
api("org.apache.activemq:activemq-stomp:5.17.4")
|
||||
api("org.apache.activemq:activemq-broker:5.17.6")
|
||||
api("org.apache.activemq:activemq-kahadb-store:5.17.6")
|
||||
api("org.apache.activemq:activemq-stomp:5.17.6")
|
||||
api("org.apache.commons:commons-pool2:2.9.0")
|
||||
api("org.apache.derby:derby:10.16.1.1")
|
||||
api("org.apache.derby:derbyclient:10.16.1.1")
|
||||
api("org.apache.derby:derbytools:10.16.1.1")
|
||||
api("org.apache.httpcomponents.client5:httpclient5:5.2.1")
|
||||
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.2.2")
|
||||
api("org.apache.poi:poi-ooxml:5.2.3")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.14")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.14")
|
||||
api("org.apache.tomcat:tomcat-util:10.1.14")
|
||||
api("org.apache.tomcat:tomcat-websocket:10.1.14")
|
||||
api("org.apache.httpcomponents.client5:httpclient5:5.2.3")
|
||||
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.2.4")
|
||||
api("org.apache.poi:poi-ooxml:5.2.5")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.15")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.15")
|
||||
api("org.apache.tomcat:tomcat-util:10.1.15")
|
||||
api("org.apache.tomcat:tomcat-websocket:10.1.15")
|
||||
api("org.aspectj:aspectjrt:1.9.20.1")
|
||||
api("org.aspectj:aspectjtools:1.9.20.1")
|
||||
api("org.aspectj:aspectjweaver:1.9.20.1")
|
||||
@@ -111,24 +111,22 @@ dependencies {
|
||||
api("org.bouncycastle:bcpkix-jdk18on:1.72")
|
||||
api("org.codehaus.jettison:jettison:1.5.4")
|
||||
api("org.dom4j:dom4j:2.1.4")
|
||||
api("org.eclipse.jetty:jetty-reactive-httpclient:3.0.8")
|
||||
api("org.eclipse.persistence:org.eclipse.persistence.jpa:3.0.3")
|
||||
api("org.eclipse.jetty:jetty-reactive-httpclient:3.0.12")
|
||||
api("org.eclipse.persistence:org.eclipse.persistence.jpa:3.0.4")
|
||||
api("org.eclipse:yasson:2.0.4")
|
||||
api("org.ehcache:ehcache:3.10.8")
|
||||
api("org.ehcache:jcache:1.0.1")
|
||||
api("org.freemarker:freemarker:2.3.32")
|
||||
// Substitute for "javax.management:jmxremote_optional:1.0.1_04" which
|
||||
// is not available on Maven Central
|
||||
api("org.glassfish.external:opendmk_jmxremote_optional_jar:1.0-b01-ea")
|
||||
api("org.glassfish.tyrus:tyrus-container-servlet:2.1.3")
|
||||
api("org.glassfish:jakarta.el:4.0.2")
|
||||
api("org.glassfish.tyrus:tyrus-container-servlet:2.1.3")
|
||||
api("org.graalvm.sdk:graal-sdk:22.3.1")
|
||||
api("org.hamcrest:hamcrest:2.2")
|
||||
api("org.hibernate:hibernate-core-jakarta:5.6.15.Final")
|
||||
api("org.hibernate:hibernate-validator:7.0.5.Final")
|
||||
api("org.hsqldb:hsqldb:2.7.2")
|
||||
api("org.javamoney:moneta:1.4.2")
|
||||
api("org.jruby:jruby:9.4.3.0")
|
||||
api("org.jruby:jruby:9.4.6.0")
|
||||
api("org.junit.support:testng-engine:1.0.4")
|
||||
api("org.mozilla:rhino:1.7.14")
|
||||
api("org.ogce:xpp3:1.1.6")
|
||||
@@ -137,10 +135,10 @@ dependencies {
|
||||
api("org.seleniumhq.selenium:htmlunit-driver:2.70.0")
|
||||
api("org.seleniumhq.selenium:selenium-java:3.141.59")
|
||||
api("org.skyscreamer:jsonassert:1.5.1")
|
||||
api("org.slf4j:slf4j-api:2.0.9")
|
||||
api("org.slf4j:slf4j-api:2.0.12")
|
||||
api("org.testng:testng:7.8.0")
|
||||
api("org.webjars:underscorejs:1.8.3")
|
||||
api("org.webjars:webjars-locator-core:0.53")
|
||||
api("org.webjars:webjars-locator-core:0.55")
|
||||
api("org.xmlunit:xmlunit-assertj:2.9.1")
|
||||
api("org.xmlunit:xmlunit-matchers:2.9.1")
|
||||
api("org.yaml:snakeyaml:1.33")
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
version=6.0.13-SNAPSHOT
|
||||
version=6.0.18
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
Vendored
+10
-10
@@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
@@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
|
||||
+2
-1
@@ -47,7 +47,8 @@ rootProject.children.each {project ->
|
||||
settings.gradle.projectsLoaded {
|
||||
gradleEnterprise {
|
||||
buildScan {
|
||||
File buildDir = settings.gradle.rootProject.getBuildDir()
|
||||
File buildDir = settings.gradle.rootProject
|
||||
.getLayout().getBuildDirectory().getAsFile().get()
|
||||
buildDir.mkdirs()
|
||||
new File(buildDir, "build-scan-uri.txt").text = "(build scan not generated)"
|
||||
buildScanPublished { scan ->
|
||||
|
||||
+6
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -333,12 +333,12 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
||||
Object targetObject = null;
|
||||
Object thisObject = null;
|
||||
try {
|
||||
MethodInvocation mi = ExposeInvocationInterceptor.currentInvocation();
|
||||
targetObject = mi.getThis();
|
||||
if (!(mi instanceof ProxyMethodInvocation _pmi)) {
|
||||
throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
|
||||
MethodInvocation curr = ExposeInvocationInterceptor.currentInvocation();
|
||||
targetObject = curr.getThis();
|
||||
if (!(curr instanceof ProxyMethodInvocation currPmi)) {
|
||||
throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + curr);
|
||||
}
|
||||
pmi = _pmi;
|
||||
pmi = currPmi;
|
||||
thisObject = pmi.getProxy();
|
||||
}
|
||||
catch (IllegalStateException ex) {
|
||||
|
||||
+6
-6
@@ -40,11 +40,12 @@ import org.springframework.util.ClassUtils;
|
||||
*/
|
||||
class AspectJBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor {
|
||||
|
||||
private static final boolean aspectJPresent = ClassUtils.isPresent(
|
||||
"org.aspectj.lang.annotation.Pointcut", AspectJBeanFactoryInitializationAotProcessor.class.getClassLoader());
|
||||
private static final boolean aspectJPresent = ClassUtils.isPresent("org.aspectj.lang.annotation.Pointcut",
|
||||
AspectJBeanFactoryInitializationAotProcessor.class.getClassLoader());
|
||||
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
@Nullable
|
||||
public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
|
||||
if (aspectJPresent) {
|
||||
return AspectDelegate.processAheadOfTime(beanFactory);
|
||||
@@ -52,6 +53,7 @@ class AspectJBeanFactoryInitializationAotProcessor implements BeanFactoryInitial
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inner class to avoid a hard dependency on AspectJ at runtime.
|
||||
*/
|
||||
@@ -61,9 +63,8 @@ class AspectJBeanFactoryInitializationAotProcessor implements BeanFactoryInitial
|
||||
private static AspectContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
|
||||
BeanFactoryAspectJAdvisorsBuilder builder = new BeanFactoryAspectJAdvisorsBuilder(beanFactory);
|
||||
List<Advisor> advisors = builder.buildAspectJAdvisors();
|
||||
return advisors.isEmpty() ? null : new AspectContribution(advisors);
|
||||
return (advisors.isEmpty() ? null : new AspectContribution(advisors));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -84,7 +85,6 @@ class AspectJBeanFactoryInitializationAotProcessor implements BeanFactoryInitial
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -293,7 +293,7 @@ final class InstantiationModelAwarePointcutAdvisorImpl
|
||||
@Override
|
||||
public boolean matches(Method method, Class<?> targetClass, Object... args) {
|
||||
// This can match only on declared pointcut.
|
||||
return (isAspectMaterialized() && this.declaredPointcut.matches(method, targetClass));
|
||||
return (isAspectMaterialized() && this.declaredPointcut.matches(method, targetClass, args));
|
||||
}
|
||||
|
||||
private boolean isAspectMaterialized() {
|
||||
|
||||
@@ -102,6 +102,12 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
*/
|
||||
private List<Advisor> advisors = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* List of minimal {@link AdvisorKeyEntry} instances,
|
||||
* to be assigned to the {@link #advisors} field on reduction.
|
||||
* @since 6.0.10
|
||||
* @see #reduceToAdvisorKey
|
||||
*/
|
||||
private List<Advisor> advisorKey = this.advisors;
|
||||
|
||||
|
||||
@@ -557,18 +563,6 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Serialization support
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
|
||||
// Rely on default serialization; just initialize state after deserialization.
|
||||
ois.defaultReadObject();
|
||||
|
||||
// Initialize transient fields.
|
||||
this.methodCache = new ConcurrentHashMap<>(32);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toProxyConfigString() {
|
||||
return toString();
|
||||
@@ -590,6 +584,19 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Serialization support
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
|
||||
// Rely on default serialization; just initialize state after deserialization.
|
||||
ois.defaultReadObject();
|
||||
|
||||
// Initialize transient fields.
|
||||
this.methodCache = new ConcurrentHashMap<>(32);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Simple wrapper class around a Method. Used as the key when
|
||||
* caching methods, for efficient equals and hashCode comparisons.
|
||||
@@ -639,7 +646,7 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
* @see #getConfigurationOnlyCopy()
|
||||
* @see #getAdvisorKey()
|
||||
*/
|
||||
private static class AdvisorKeyEntry implements Advisor {
|
||||
private static final class AdvisorKeyEntry implements Advisor {
|
||||
|
||||
private final Class<?> adviceType;
|
||||
|
||||
@@ -649,7 +656,6 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
@Nullable
|
||||
private final String methodMatcherKey;
|
||||
|
||||
|
||||
public AdvisorKeyEntry(Advisor advisor) {
|
||||
this.adviceType = advisor.getAdvice().getClass();
|
||||
if (advisor instanceof PointcutAdvisor pointcutAdvisor) {
|
||||
|
||||
@@ -265,18 +265,32 @@ public class ProxyFactoryBean extends ProxyCreatorSupport
|
||||
* Return the type of the proxy. Will check the singleton instance if
|
||||
* already created, else fall back to the proxy interface (in case of just
|
||||
* a single one), the target bean type, or the TargetSource's target class.
|
||||
* @see org.springframework.aop.TargetSource#getTargetClass
|
||||
* @see org.springframework.aop.framework.AopProxy#getProxyClass
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public Class<?> getObjectType() {
|
||||
synchronized (this) {
|
||||
if (this.singletonInstance != null) {
|
||||
return this.singletonInstance.getClass();
|
||||
}
|
||||
}
|
||||
// This might be incomplete since it potentially misses introduced interfaces
|
||||
// from Advisors that will be lazily retrieved via setInterceptorNames.
|
||||
return createAopProxy().getProxyClass(this.proxyClassLoader);
|
||||
try {
|
||||
// This might be incomplete since it potentially misses introduced interfaces
|
||||
// from Advisors that will be lazily retrieved via setInterceptorNames.
|
||||
return createAopProxy().getProxyClass(this.proxyClassLoader);
|
||||
}
|
||||
catch (AopConfigException ex) {
|
||||
if (getTargetClass() == null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Failed to determine early proxy class: " + ex.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-1
@@ -58,6 +58,7 @@ import org.springframework.util.function.SingletonSupplier;
|
||||
* @author Juergen Hoeller
|
||||
* @author Stephane Nicoll
|
||||
* @author He Bo
|
||||
* @author Sebastien Deleuze
|
||||
* @since 3.1.2
|
||||
*/
|
||||
public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
|
||||
@@ -290,7 +291,7 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
|
||||
else if (Future.class.isAssignableFrom(returnType)) {
|
||||
return executor.submit(task);
|
||||
}
|
||||
else if (void.class == returnType) {
|
||||
else if (void.class == returnType || "kotlin.Unit".equals(returnType.getName())) {
|
||||
executor.submit(task);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -186,10 +186,10 @@ public abstract class AopUtils {
|
||||
* this method resolves bridge methods in order to retrieve attributes from
|
||||
* the <i>original</i> method definition.
|
||||
* @param method the method to be invoked, which may come from an interface
|
||||
* @param targetClass the target class for the current invocation.
|
||||
* May be {@code null} or may not even implement the method.
|
||||
* @param targetClass the target class for the current invocation
|
||||
* (can be {@code null} or may not even implement the method)
|
||||
* @return the specific target method, or the original method if the
|
||||
* {@code targetClass} doesn't implement it or is {@code null}
|
||||
* {@code targetClass} does not implement it
|
||||
* @see org.springframework.util.ClassUtils#getMostSpecificMethod
|
||||
*/
|
||||
public static Method getMostSpecificMethod(Method method, @Nullable Class<?> targetClass) {
|
||||
|
||||
@@ -123,7 +123,6 @@ public abstract class ClassFilters {
|
||||
public String toString() {
|
||||
return getClass().getName() + ": " + Arrays.toString(this.filters);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -164,7 +163,6 @@ public abstract class ClassFilters {
|
||||
public String toString() {
|
||||
return getClass().getName() + ": " + Arrays.toString(this.filters);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+23
-25
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -23,7 +23,6 @@ import java.util.Map;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.aspectj.weaver.tools.PointcutExpression;
|
||||
import org.aspectj.weaver.tools.PointcutPrimitive;
|
||||
import org.aspectj.weaver.tools.UnsupportedPointcutPrimitiveException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -65,7 +64,7 @@ public class AspectJExpressionPointcutTests {
|
||||
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() throws NoSuchMethodException {
|
||||
public void setup() throws NoSuchMethodException {
|
||||
getAge = TestBean.class.getMethod("getAge");
|
||||
setAge = TestBean.class.getMethod("setAge", int.class);
|
||||
setSomeNumber = TestBean.class.getMethod("setSomeNumber", Number.class);
|
||||
@@ -246,14 +245,13 @@ public class AspectJExpressionPointcutTests {
|
||||
@Test
|
||||
public void testInvalidExpression() {
|
||||
String expression = "execution(void org.springframework.beans.testfixture.beans.TestBean.setSomeNumber(Number) && args(Double)";
|
||||
assertThatIllegalArgumentException().isThrownBy(
|
||||
getPointcut(expression)::getClassFilter); // call to getClassFilter forces resolution
|
||||
assertThatIllegalArgumentException().isThrownBy(getPointcut(expression)::getClassFilter); // call to getClassFilter forces resolution
|
||||
}
|
||||
|
||||
private TestBean getAdvisedProxy(String pointcutExpression, CallCountingInterceptor interceptor) {
|
||||
TestBean target = new TestBean();
|
||||
|
||||
Pointcut pointcut = getPointcut(pointcutExpression);
|
||||
AspectJExpressionPointcut pointcut = getPointcut(pointcutExpression);
|
||||
|
||||
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
|
||||
advisor.setAdvice(interceptor);
|
||||
@@ -277,40 +275,31 @@ public class AspectJExpressionPointcutTests {
|
||||
@Test
|
||||
public void testWithUnsupportedPointcutPrimitive() {
|
||||
String expression = "call(int org.springframework.beans.testfixture.beans.TestBean.getAge())";
|
||||
assertThatExceptionOfType(UnsupportedPointcutPrimitiveException.class).isThrownBy(() ->
|
||||
getPointcut(expression).getClassFilter()) // call to getClassFilter forces resolution...
|
||||
.satisfies(ex -> assertThat(ex.getUnsupportedPrimitive()).isEqualTo(PointcutPrimitive.CALL));
|
||||
assertThatExceptionOfType(UnsupportedPointcutPrimitiveException.class)
|
||||
.isThrownBy(() -> getPointcut(expression).getClassFilter()) // call to getClassFilter forces resolution...
|
||||
.satisfies(ex -> assertThat(ex.getUnsupportedPrimitive()).isEqualTo(PointcutPrimitive.CALL));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAndSubstitution() {
|
||||
Pointcut pc = getPointcut("execution(* *(..)) and args(String)");
|
||||
PointcutExpression expr = ((AspectJExpressionPointcut) pc).getPointcutExpression();
|
||||
assertThat(expr.getPointcutExpression()).isEqualTo("execution(* *(..)) && args(String)");
|
||||
AspectJExpressionPointcut pc = getPointcut("execution(* *(..)) and args(String)");
|
||||
String expr = pc.getPointcutExpression().getPointcutExpression();
|
||||
assertThat(expr).isEqualTo("execution(* *(..)) && args(String)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleAndSubstitutions() {
|
||||
Pointcut pc = getPointcut("execution(* *(..)) and args(String) and this(Object)");
|
||||
PointcutExpression expr = ((AspectJExpressionPointcut) pc).getPointcutExpression();
|
||||
assertThat(expr.getPointcutExpression()).isEqualTo("execution(* *(..)) && args(String) && this(Object)");
|
||||
AspectJExpressionPointcut pc = getPointcut("execution(* *(..)) and args(String) and this(Object)");
|
||||
String expr = pc.getPointcutExpression().getPointcutExpression();
|
||||
assertThat(expr).isEqualTo("execution(* *(..)) && args(String) && this(Object)");
|
||||
}
|
||||
|
||||
private Pointcut getPointcut(String expression) {
|
||||
private AspectJExpressionPointcut getPointcut(String expression) {
|
||||
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
|
||||
pointcut.setExpression(expression);
|
||||
return pointcut;
|
||||
}
|
||||
|
||||
|
||||
public static class OtherIOther implements IOther {
|
||||
|
||||
@Override
|
||||
public void absquatulate() {
|
||||
// Empty
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchGenericArgument() {
|
||||
String expression = "execution(* set*(java.util.List<org.springframework.beans.testfixture.beans.TestBean>) )";
|
||||
@@ -505,6 +494,15 @@ public class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
|
||||
public static class OtherIOther implements IOther {
|
||||
|
||||
@Override
|
||||
public void absquatulate() {
|
||||
// Empty
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class HasGeneric {
|
||||
|
||||
public void setFriends(List<TestBean> friends) {
|
||||
|
||||
+40
-17
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -42,26 +42,38 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
*/
|
||||
class ArgumentBindingTests {
|
||||
|
||||
@Test
|
||||
void bindingInPointcutUsedByAdvice() {
|
||||
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new TestBean());
|
||||
proxyFactory.addAspect(NamedPointcutWithArgs.class);
|
||||
|
||||
ITestBean proxiedTestBean = proxyFactory.getProxy();
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> proxiedTestBean.setName("enigma"))
|
||||
.withMessage("enigma");
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationArgumentNameBinding() {
|
||||
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new TransactionalBean());
|
||||
proxyFactory.addAspect(PointcutWithAnnotationArgument.class);
|
||||
|
||||
ITransactionalBean proxiedTestBean = proxyFactory.getProxy();
|
||||
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(proxiedTestBean::doInTransaction)
|
||||
.withMessage("Invoked with @Transactional");
|
||||
.isThrownBy(proxiedTestBean::doInTransaction)
|
||||
.withMessage("Invoked with @Transactional");
|
||||
}
|
||||
|
||||
@Test
|
||||
void bindingInPointcutUsedByAdvice() {
|
||||
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new TestBean());
|
||||
proxyFactory.addAspect(NamedPointcutWithArgs.class);
|
||||
ITestBean proxiedTestBean = proxyFactory.getProxy();
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> proxiedTestBean.setName("enigma"))
|
||||
.withMessage("enigma");
|
||||
}
|
||||
|
||||
@Test
|
||||
void bindingWithDynamicAdvice() {
|
||||
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new TestBean());
|
||||
proxyFactory.addAspect(DynamicPointcutWithArgs.class);
|
||||
ITestBean proxiedTestBean = proxyFactory.getProxy();
|
||||
|
||||
proxiedTestBean.applyName(1);
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> proxiedTestBean.applyName("enigma"))
|
||||
.withMessage("enigma");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -94,6 +106,7 @@ class ArgumentBindingTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Mimics Spring's @Transactional annotation without actually introducing the dependency.
|
||||
*/
|
||||
@@ -101,16 +114,17 @@ class ArgumentBindingTests {
|
||||
@interface Transactional {
|
||||
}
|
||||
|
||||
|
||||
@Aspect
|
||||
static class PointcutWithAnnotationArgument {
|
||||
|
||||
@Around(value = "execution(* org.springframework..*.*(..)) && @annotation(transactional)")
|
||||
@Around("execution(* org.springframework..*.*(..)) && @annotation(transactional)")
|
||||
public Object around(ProceedingJoinPoint pjp, Transactional transactional) throws Throwable {
|
||||
throw new IllegalStateException("Invoked with @Transactional");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Aspect
|
||||
static class NamedPointcutWithArgs {
|
||||
|
||||
@@ -121,7 +135,16 @@ class ArgumentBindingTests {
|
||||
public Object doAround(ProceedingJoinPoint pjp, String aString) throws Throwable {
|
||||
throw new IllegalArgumentException(aString);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Aspect("pertarget(execution(* *(..)))")
|
||||
static class DynamicPointcutWithArgs {
|
||||
|
||||
@Around("execution(* *(..)) && args(java.lang.String)")
|
||||
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
|
||||
throw new IllegalArgumentException(String.valueOf(pjp.getArgs()[0]));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -17,29 +17,35 @@
|
||||
package org.springframework.aop.support;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aop.ClassFilter;
|
||||
import org.springframework.aop.MethodMatcher;
|
||||
import org.springframework.aop.Pointcut;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.aop.interceptor.ExposeInvocationInterceptor;
|
||||
import org.springframework.aop.target.EmptyTargetSource;
|
||||
import org.springframework.aop.testfixture.interceptor.NopInterceptor;
|
||||
import org.springframework.beans.testfixture.beans.TestBean;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.testfixture.io.SerializationTestUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Rod Johnson
|
||||
* @author Chris Beams
|
||||
* @author Sebastien Deleuze
|
||||
* @author Juergen Hoeller
|
||||
*/
|
||||
public class AopUtilsTests {
|
||||
class AopUtilsTests {
|
||||
|
||||
@Test
|
||||
public void testPointcutCanNeverApply() {
|
||||
void testPointcutCanNeverApply() {
|
||||
class TestPointcut extends StaticMethodMatcherPointcut {
|
||||
@Override
|
||||
public boolean matches(Method method, @Nullable Class<?> clazzy) {
|
||||
@@ -52,13 +58,13 @@ public class AopUtilsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPointcutAlwaysApplies() {
|
||||
void testPointcutAlwaysApplies() {
|
||||
assertThat(AopUtils.canApply(new DefaultPointcutAdvisor(new NopInterceptor()), Object.class)).isTrue();
|
||||
assertThat(AopUtils.canApply(new DefaultPointcutAdvisor(new NopInterceptor()), TestBean.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPointcutAppliesToOneMethodOnObject() {
|
||||
void testPointcutAppliesToOneMethodOnObject() {
|
||||
class TestPointcut extends StaticMethodMatcherPointcut {
|
||||
@Override
|
||||
public boolean matches(Method method, @Nullable Class<?> clazz) {
|
||||
@@ -78,7 +84,7 @@ public class AopUtilsTests {
|
||||
* that's subverted the singleton construction limitation.
|
||||
*/
|
||||
@Test
|
||||
public void testCanonicalFrameworkClassesStillCanonicalOnDeserialization() throws Exception {
|
||||
void testCanonicalFrameworkClassesStillCanonicalOnDeserialization() throws Exception {
|
||||
assertThat(SerializationTestUtils.serializeAndDeserialize(MethodMatcher.TRUE)).isSameAs(MethodMatcher.TRUE);
|
||||
assertThat(SerializationTestUtils.serializeAndDeserialize(ClassFilter.TRUE)).isSameAs(ClassFilter.TRUE);
|
||||
assertThat(SerializationTestUtils.serializeAndDeserialize(Pointcut.TRUE)).isSameAs(Pointcut.TRUE);
|
||||
@@ -88,4 +94,45 @@ public class AopUtilsTests {
|
||||
assertThat(SerializationTestUtils.serializeAndDeserialize(ExposeInvocationInterceptor.INSTANCE)).isSameAs(ExposeInvocationInterceptor.INSTANCE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInvokeJoinpointUsingReflection() throws Throwable {
|
||||
String name = "foo";
|
||||
TestBean testBean = new TestBean(name);
|
||||
Method method = ReflectionUtils.findMethod(TestBean.class, "getName");
|
||||
Object result = AopUtils.invokeJoinpointUsingReflection(testBean, method, new Object[0]);
|
||||
assertThat(result).isEqualTo(name);
|
||||
}
|
||||
|
||||
@Test // gh-32365
|
||||
void mostSpecificMethodBetweenJdkProxyAndTarget() throws Exception {
|
||||
Class<?> proxyClass = new ProxyFactory(new WithInterface()).getProxyClass(getClass().getClassLoader());
|
||||
Method specificMethod = AopUtils.getMostSpecificMethod(proxyClass.getMethod("handle", List.class), WithInterface.class);
|
||||
assertThat(ResolvableType.forMethodParameter(specificMethod, 0).getGeneric().toClass()).isEqualTo(String.class);
|
||||
}
|
||||
|
||||
@Test // gh-32365
|
||||
void mostSpecificMethodBetweenCglibProxyAndTarget() throws Exception {
|
||||
Class<?> proxyClass = new ProxyFactory(new WithoutInterface()).getProxyClass(getClass().getClassLoader());
|
||||
Method specificMethod = AopUtils.getMostSpecificMethod(proxyClass.getMethod("handle", List.class), WithoutInterface.class);
|
||||
assertThat(ResolvableType.forMethodParameter(specificMethod, 0).getGeneric().toClass()).isEqualTo(String.class);
|
||||
}
|
||||
|
||||
|
||||
interface ProxyInterface {
|
||||
|
||||
void handle(List<String> list);
|
||||
}
|
||||
|
||||
static class WithInterface implements ProxyInterface {
|
||||
|
||||
public void handle(List<String> list) {
|
||||
}
|
||||
}
|
||||
|
||||
static class WithoutInterface {
|
||||
|
||||
public void handle(List<String> list) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-1
@@ -1000,18 +1000,20 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
|
||||
*/
|
||||
protected abstract static class PropertyHandler {
|
||||
|
||||
@Nullable
|
||||
private final Class<?> propertyType;
|
||||
|
||||
private final boolean readable;
|
||||
|
||||
private final boolean writable;
|
||||
|
||||
public PropertyHandler(Class<?> propertyType, boolean readable, boolean writable) {
|
||||
public PropertyHandler(@Nullable Class<?> propertyType, boolean readable, boolean writable) {
|
||||
this.propertyType = propertyType;
|
||||
this.readable = readable;
|
||||
this.writable = writable;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Class<?> getPropertyType() {
|
||||
return this.propertyType;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.time.temporal.Temporal;
|
||||
@@ -615,8 +616,8 @@ public abstract class BeanUtils {
|
||||
* @return a corresponding MethodParameter object
|
||||
*/
|
||||
public static MethodParameter getWriteMethodParameter(PropertyDescriptor pd) {
|
||||
if (pd instanceof GenericTypeAwarePropertyDescriptor typeAwarePd) {
|
||||
return new MethodParameter(typeAwarePd.getWriteMethodParameter());
|
||||
if (pd instanceof GenericTypeAwarePropertyDescriptor gpd) {
|
||||
return new MethodParameter(gpd.getWriteMethodParameter());
|
||||
}
|
||||
else {
|
||||
Method writeMethod = pd.getWriteMethod();
|
||||
@@ -787,38 +788,28 @@ public abstract class BeanUtils {
|
||||
if (editable != null) {
|
||||
if (!editable.isInstance(target)) {
|
||||
throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
|
||||
"] not assignable to Editable class [" + editable.getName() + "]");
|
||||
"] not assignable to editable class [" + editable.getName() + "]");
|
||||
}
|
||||
actualEditable = editable;
|
||||
}
|
||||
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
|
||||
Set<String> ignoredProps = (ignoreProperties != null ? new HashSet<>(Arrays.asList(ignoreProperties)) : null);
|
||||
CachedIntrospectionResults sourceResults = (actualEditable != source.getClass() ?
|
||||
CachedIntrospectionResults.forClass(source.getClass()) : null);
|
||||
|
||||
for (PropertyDescriptor targetPd : targetPds) {
|
||||
Method writeMethod = targetPd.getWriteMethod();
|
||||
if (writeMethod != null && (ignoredProps == null || !ignoredProps.contains(targetPd.getName()))) {
|
||||
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
|
||||
PropertyDescriptor sourcePd = (sourceResults != null ?
|
||||
sourceResults.getPropertyDescriptor(targetPd.getName()) : targetPd);
|
||||
if (sourcePd != null) {
|
||||
Method readMethod = sourcePd.getReadMethod();
|
||||
if (readMethod != null) {
|
||||
ResolvableType sourceResolvableType = ResolvableType.forMethodReturnType(readMethod);
|
||||
ResolvableType targetResolvableType = ResolvableType.forMethodParameter(writeMethod, 0);
|
||||
|
||||
// Ignore generic types in assignable check if either ResolvableType has unresolvable generics.
|
||||
boolean isAssignable =
|
||||
(sourceResolvableType.hasUnresolvableGenerics() || targetResolvableType.hasUnresolvableGenerics() ?
|
||||
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()) :
|
||||
targetResolvableType.isAssignableFrom(sourceResolvableType));
|
||||
|
||||
if (isAssignable) {
|
||||
if (isAssignable(writeMethod, readMethod)) {
|
||||
try {
|
||||
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
|
||||
readMethod.setAccessible(true);
|
||||
}
|
||||
ReflectionUtils.makeAccessible(readMethod);
|
||||
Object value = readMethod.invoke(source);
|
||||
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
|
||||
writeMethod.setAccessible(true);
|
||||
}
|
||||
ReflectionUtils.makeAccessible(writeMethod);
|
||||
writeMethod.invoke(target, value);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
@@ -832,6 +823,24 @@ public abstract class BeanUtils {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isAssignable(Method writeMethod, Method readMethod) {
|
||||
Type paramType = writeMethod.getGenericParameterTypes()[0];
|
||||
if (paramType instanceof Class<?> clazz) {
|
||||
return ClassUtils.isAssignable(clazz, readMethod.getReturnType());
|
||||
}
|
||||
else if (paramType.equals(readMethod.getGenericReturnType())) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
ResolvableType sourceType = ResolvableType.forMethodReturnType(readMethod);
|
||||
ResolvableType targetType = ResolvableType.forMethodParameter(writeMethod, 0);
|
||||
// Ignore generic types in assignable check if either ResolvableType has unresolvable generics.
|
||||
return (sourceType.hasUnresolvableGenerics() || targetType.hasUnresolvableGenerics() ?
|
||||
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()) :
|
||||
targetType.isAssignableFrom(sourceType));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inner class to avoid a hard dependency on Kotlin at runtime.
|
||||
@@ -896,7 +905,6 @@ public abstract class BeanUtils {
|
||||
}
|
||||
return kotlinConstructor.callBy(argParameters);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -129,7 +129,6 @@ public class DirectFieldAccessor extends AbstractNestablePropertyAccessor {
|
||||
ReflectionUtils.makeAccessible(this.field);
|
||||
return this.field.get(getWrappedInstance());
|
||||
}
|
||||
|
||||
catch (IllegalAccessException ex) {
|
||||
throw new InvalidPropertyException(getWrappedClass(),
|
||||
this.field.getName(), "Field is not accessible", ex);
|
||||
|
||||
+21
-12
@@ -159,6 +159,9 @@ import org.springframework.util.StringUtils;
|
||||
public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor,
|
||||
MergedBeanDefinitionPostProcessor, BeanRegistrationAotProcessor, PriorityOrdered, BeanFactoryAware {
|
||||
|
||||
private static final Constructor<?>[] EMPTY_CONSTRUCTOR_ARRAY = new Constructor<?>[0];
|
||||
|
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private final Set<Class<? extends Annotation>> autowiredAnnotationTypes = new LinkedHashSet<>(4);
|
||||
@@ -193,9 +196,10 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
this.autowiredAnnotationTypes.add(Autowired.class);
|
||||
this.autowiredAnnotationTypes.add(Value.class);
|
||||
|
||||
ClassLoader classLoader = AutowiredAnnotationBeanPostProcessor.class.getClassLoader();
|
||||
try {
|
||||
this.autowiredAnnotationTypes.add((Class<? extends Annotation>)
|
||||
ClassUtils.forName("jakarta.inject.Inject", AutowiredAnnotationBeanPostProcessor.class.getClassLoader()));
|
||||
ClassUtils.forName("jakarta.inject.Inject", classLoader));
|
||||
logger.trace("'jakarta.inject.Inject' annotation found and supported for autowiring");
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
@@ -204,7 +208,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
|
||||
try {
|
||||
this.autowiredAnnotationTypes.add((Class<? extends Annotation>)
|
||||
ClassUtils.forName("javax.inject.Inject", AutowiredAnnotationBeanPostProcessor.class.getClassLoader()));
|
||||
ClassUtils.forName("javax.inject.Inject", classLoader));
|
||||
logger.trace("'javax.inject.Inject' annotation found and supported for autowiring");
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
@@ -285,9 +289,16 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
|
||||
@Override
|
||||
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
|
||||
// Register externally managed config members on bean definition.
|
||||
findInjectionMetadata(beanName, beanType, beanDefinition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetBeanDefinition(String beanName) {
|
||||
this.lookupMethodsChecked.remove(beanName);
|
||||
this.injectionMetadataCache.remove(beanName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
|
||||
@@ -323,12 +334,6 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
return metadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetBeanDefinition(String beanName) {
|
||||
this.lookupMethodsChecked.remove(beanName);
|
||||
this.injectionMetadataCache.remove(beanName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> determineBeanType(Class<?> beanClass, String beanName) throws BeanCreationException {
|
||||
checkLookupMethods(beanClass, beanName);
|
||||
@@ -428,7 +433,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
"default constructor to fall back to: " + candidates.get(0));
|
||||
}
|
||||
}
|
||||
candidateConstructors = candidates.toArray(new Constructor<?>[0]);
|
||||
candidateConstructors = candidates.toArray(EMPTY_CONSTRUCTOR_ARRAY);
|
||||
}
|
||||
else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) {
|
||||
candidateConstructors = new Constructor<?>[] {rawCandidates[0]};
|
||||
@@ -441,7 +446,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
candidateConstructors = new Constructor<?>[] {primaryConstructor};
|
||||
}
|
||||
else {
|
||||
candidateConstructors = new Constructor<?>[0];
|
||||
candidateConstructors = EMPTY_CONSTRUCTOR_ARRAY;
|
||||
}
|
||||
this.candidateConstructorsCache.put(beanClass, candidateConstructors);
|
||||
}
|
||||
@@ -582,6 +587,10 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
return;
|
||||
}
|
||||
if (method.getParameterCount() == 0) {
|
||||
if (method.getDeclaringClass().isRecord()) {
|
||||
// Annotations on the compact constructor arguments made available on accessors, ignoring.
|
||||
return;
|
||||
}
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Autowired annotation should only be used on methods with parameters: " +
|
||||
method);
|
||||
@@ -1007,7 +1016,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
hints.reflection().registerField(field);
|
||||
CodeBlock resolver = CodeBlock.of("$T.$L($S)",
|
||||
AutowiredFieldValueResolver.class,
|
||||
(!required) ? "forField" : "forRequiredField", field.getName());
|
||||
(!required ? "forField" : "forRequiredField"), field.getName());
|
||||
AccessControl accessControl = AccessControl.forMember(field);
|
||||
if (!accessControl.isAccessibleFrom(targetClassName)) {
|
||||
return CodeBlock.of("$L.resolveAndSet($L, $L)", resolver,
|
||||
@@ -1022,7 +1031,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
|
||||
CodeBlock.Builder code = CodeBlock.builder();
|
||||
code.add("$T.$L", AutowiredMethodArgumentsResolver.class,
|
||||
(!required) ? "forMethod" : "forRequiredMethod");
|
||||
(!required ? "forMethod" : "forRequiredMethod"));
|
||||
code.add("($S", method.getName());
|
||||
if (method.getParameterCount() > 0) {
|
||||
code.add(", $L", generateParameterTypesCode(method.getParameterTypes()));
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -292,7 +292,7 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
|
||||
if (actualValue != null) {
|
||||
actualValue = typeConverter.convertIfNecessary(actualValue, expectedValue.getClass());
|
||||
}
|
||||
if (!expectedValue.equals(actualValue)) {
|
||||
if (!ObjectUtils.nullSafeEquals(expectedValue, actualValue)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+4
-6
@@ -191,16 +191,14 @@ public final class AutowiredFieldValueResolver extends AutowiredElementResolver
|
||||
return value;
|
||||
}
|
||||
catch (BeansException ex) {
|
||||
throw new UnsatisfiedDependencyException(null, beanName,
|
||||
new InjectionPoint(field), ex);
|
||||
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private Field getField(RegisteredBean registeredBean) {
|
||||
Field field = ReflectionUtils.findField(registeredBean.getBeanClass(),
|
||||
this.fieldName);
|
||||
Assert.notNull(field, () -> "No field '" + this.fieldName + "' found on "
|
||||
+ registeredBean.getBeanClass().getName());
|
||||
Field field = ReflectionUtils.findField(registeredBean.getBeanClass(), this.fieldName);
|
||||
Assert.notNull(field, () -> "No field '" + this.fieldName + "' found on " +
|
||||
registeredBean.getBeanClass().getName());
|
||||
return field;
|
||||
}
|
||||
|
||||
|
||||
+2
-4
@@ -69,6 +69,7 @@ class BeanDefinitionPropertyValueCodeGenerator {
|
||||
|
||||
BeanDefinitionPropertyValueCodeGenerator(GeneratedMethods generatedMethods,
|
||||
@Nullable BiFunction<Object, ResolvableType, CodeBlock> customValueGenerator) {
|
||||
|
||||
this.generatedMethods = generatedMethods;
|
||||
this.delegates = new ArrayList<>();
|
||||
if (customValueGenerator != null) {
|
||||
@@ -143,7 +144,6 @@ class BeanDefinitionPropertyValueCodeGenerator {
|
||||
|
||||
@Nullable
|
||||
CodeBlock generateCode(Object value, ResolvableType type);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -163,7 +163,6 @@ class BeanDefinitionPropertyValueCodeGenerator {
|
||||
'\\', "\\\\"
|
||||
);
|
||||
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public CodeBlock generateCode(Object value, ResolvableType type) {
|
||||
@@ -231,7 +230,6 @@ class BeanDefinitionPropertyValueCodeGenerator {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -321,8 +319,8 @@ class BeanDefinitionPropertyValueCodeGenerator {
|
||||
this.emptyResult = emptyResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
@Nullable
|
||||
public CodeBlock generateCode(Object value, ResolvableType type) {
|
||||
if (this.collectionType.isInstance(value)) {
|
||||
|
||||
+12
-14
@@ -92,10 +92,10 @@ import org.springframework.util.function.ThrowingSupplier;
|
||||
* Supports autowiring constructors, properties by name, and properties by type.
|
||||
*
|
||||
* <p>The main template method to be implemented by subclasses is
|
||||
* {@link #resolveDependency(DependencyDescriptor, String, Set, TypeConverter)},
|
||||
* used for autowiring by type. In case of a factory which is capable of searching
|
||||
* its bean definitions, matching beans will typically be implemented through such
|
||||
* a search. For other factory styles, simplified matching algorithms can be implemented.
|
||||
* {@link #resolveDependency(DependencyDescriptor, String, Set, TypeConverter)}, used for
|
||||
* autowiring. In case of a {@link org.springframework.beans.factory.ListableBeanFactory}
|
||||
* which is capable of searching its bean definitions, matching beans will typically be
|
||||
* implemented through such a search. Otherwise, simplified matching can be implemented.
|
||||
*
|
||||
* <p>Note that this class does <i>not</i> assume or implement bean definition
|
||||
* registry capabilities. See {@link DefaultListableBeanFactory} for an implementation
|
||||
@@ -493,15 +493,13 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||
if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
|
||||
mbdToUse = new RootBeanDefinition(mbd);
|
||||
mbdToUse.setBeanClass(resolvedClass);
|
||||
}
|
||||
|
||||
// Prepare method overrides.
|
||||
try {
|
||||
mbdToUse.prepareMethodOverrides();
|
||||
}
|
||||
catch (BeanDefinitionValidationException ex) {
|
||||
throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
|
||||
beanName, "Validation of method overrides failed", ex);
|
||||
try {
|
||||
mbdToUse.prepareMethodOverrides();
|
||||
}
|
||||
catch (BeanDefinitionValidationException ex) {
|
||||
throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
|
||||
beanName, "Validation of method overrides failed", ex);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -652,7 +650,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||
// Apply SmartInstantiationAwareBeanPostProcessors to predict the
|
||||
// eventual type after a before-instantiation shortcut.
|
||||
if (targetType != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
|
||||
boolean matchingOnlyFactoryBean = typesToMatch.length == 1 && typesToMatch[0] == FactoryBean.class;
|
||||
boolean matchingOnlyFactoryBean = (typesToMatch.length == 1 && typesToMatch[0] == FactoryBean.class);
|
||||
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
|
||||
Class<?> predicted = bp.predictBeanType(targetType, beanName);
|
||||
if (predicted != null &&
|
||||
|
||||
+14
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -161,6 +161,9 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
/** Map from scope identifier String to corresponding Scope. */
|
||||
private final Map<String, Scope> scopes = new LinkedHashMap<>(8);
|
||||
|
||||
/** Application startup metrics. **/
|
||||
private ApplicationStartup applicationStartup = ApplicationStartup.DEFAULT;
|
||||
|
||||
/** Map from bean name to merged RootBeanDefinition. */
|
||||
private final Map<String, RootBeanDefinition> mergedBeanDefinitions = new ConcurrentHashMap<>(256);
|
||||
|
||||
@@ -171,8 +174,6 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
private final ThreadLocal<Object> prototypesCurrentlyInCreation =
|
||||
new NamedThreadLocal<>("Prototype beans currently in creation");
|
||||
|
||||
/** Application startup metrics. **/
|
||||
private ApplicationStartup applicationStartup = ApplicationStartup.DEFAULT;
|
||||
|
||||
/**
|
||||
* Create a new AbstractBeanFactory.
|
||||
@@ -731,7 +732,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
aliases.add(fullBeanName);
|
||||
}
|
||||
String[] retrievedAliases = super.getAliases(beanName);
|
||||
String prefix = factoryPrefix ? FACTORY_BEAN_PREFIX : "";
|
||||
String prefix = (factoryPrefix ? FACTORY_BEAN_PREFIX : "");
|
||||
for (String retrievedAlias : retrievedAliases) {
|
||||
String alias = prefix + retrievedAlias;
|
||||
if (!alias.equals(name)) {
|
||||
@@ -1497,7 +1498,11 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
if (mbd.hasBeanClass()) {
|
||||
return mbd.getBeanClass();
|
||||
}
|
||||
return doResolveBeanClass(mbd, typesToMatch);
|
||||
Class<?> beanClass = doResolveBeanClass(mbd, typesToMatch);
|
||||
if (mbd.hasBeanClass()) {
|
||||
mbd.prepareMethodOverrides();
|
||||
}
|
||||
return beanClass;
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), ex);
|
||||
@@ -1505,6 +1510,10 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
catch (LinkageError err) {
|
||||
throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), err);
|
||||
}
|
||||
catch (BeanDefinitionValidationException ex) {
|
||||
throw new BeanDefinitionStoreException(mbd.getResourceDescription(),
|
||||
beanName, "Validation of method overrides failed", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
+5
-12
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -120,11 +120,7 @@ class ConstructorResolver {
|
||||
|
||||
/**
|
||||
* "autowire constructor" (with constructor arguments by type) behavior.
|
||||
* Also applied if explicit constructor argument values are specified,
|
||||
* matching all remaining arguments with beans from the bean factory.
|
||||
* <p>This corresponds to constructor injection: In this mode, a Spring
|
||||
* bean factory is able to host components that expect constructor-based
|
||||
* dependency resolution.
|
||||
* Also applied if explicit constructor argument values are specified.
|
||||
* @param beanName the name of the bean
|
||||
* @param mbd the merged bean definition for the bean
|
||||
* @param chosenCtors chosen candidate constructors (or {@code null} if none)
|
||||
@@ -613,13 +609,10 @@ class ConstructorResolver {
|
||||
String argDesc = StringUtils.collectionToCommaDelimitedString(argTypes);
|
||||
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
|
||||
"No matching factory method found on class [" + factoryClass.getName() + "]: " +
|
||||
(mbd.getFactoryBeanName() != null ?
|
||||
"factory bean '" + mbd.getFactoryBeanName() + "'; " : "") +
|
||||
(mbd.getFactoryBeanName() != null ? "factory bean '" + mbd.getFactoryBeanName() + "'; " : "") +
|
||||
"factory method '" + mbd.getFactoryMethodName() + "(" + argDesc + ")'. " +
|
||||
"Check that a method with the specified name " +
|
||||
(minNrOfArgs > 0 ? "and arguments " : "") +
|
||||
"exists and that it is " +
|
||||
(isStatic ? "static" : "non-static") + ".");
|
||||
"Check that a method with the specified name " + (minNrOfArgs > 0 ? "and arguments " : "") +
|
||||
"exists and that it is " + (isStatic ? "static" : "non-static") + ".");
|
||||
}
|
||||
else if (void.class == factoryMethodToUse.getReturnType()) {
|
||||
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
|
||||
|
||||
+6
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -567,16 +567,16 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||
*/
|
||||
protected void destroyBean(String beanName, @Nullable DisposableBean bean) {
|
||||
// Trigger destruction of dependent beans first...
|
||||
Set<String> dependencies;
|
||||
Set<String> dependentBeanNames;
|
||||
synchronized (this.dependentBeanMap) {
|
||||
// Within full synchronization in order to guarantee a disconnected Set
|
||||
dependencies = this.dependentBeanMap.remove(beanName);
|
||||
dependentBeanNames = this.dependentBeanMap.remove(beanName);
|
||||
}
|
||||
if (dependencies != null) {
|
||||
if (dependentBeanNames != null) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Retrieved dependent beans for bean '" + beanName + "': " + dependencies);
|
||||
logger.trace("Retrieved dependent beans for bean '" + beanName + "': " + dependentBeanNames);
|
||||
}
|
||||
for (String dependentBeanName : dependencies) {
|
||||
for (String dependentBeanName : dependentBeanNames) {
|
||||
destroySingleton(dependentBeanName);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -261,7 +261,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
|
||||
if (destroyMethod != null) {
|
||||
return destroyMethod;
|
||||
}
|
||||
for (Class<?> beanInterface : beanClass.getInterfaces()) {
|
||||
for (Class<?> beanInterface : ClassUtils.getAllInterfacesForClass(beanClass)) {
|
||||
destroyMethod = findDestroyMethod(beanInterface, methodName);
|
||||
if (destroyMethod != null) {
|
||||
return destroyMethod;
|
||||
|
||||
+3
-3
@@ -71,7 +71,7 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy {
|
||||
synchronized (bd.constructorArgumentLock) {
|
||||
constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;
|
||||
if (constructorToUse == null) {
|
||||
final Class<?> clazz = bd.getBeanClass();
|
||||
Class<?> clazz = bd.getBeanClass();
|
||||
if (clazz.isInterface()) {
|
||||
throw new BeanInstantiationException(clazz, "Specified class is an interface");
|
||||
}
|
||||
@@ -104,7 +104,7 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy {
|
||||
|
||||
@Override
|
||||
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
|
||||
final Constructor<?> ctor, Object... args) {
|
||||
Constructor<?> ctor, Object... args) {
|
||||
|
||||
if (!bd.hasMethodOverrides()) {
|
||||
return BeanUtils.instantiateClass(ctor, args);
|
||||
@@ -128,7 +128,7 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy {
|
||||
|
||||
@Override
|
||||
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
|
||||
@Nullable Object factoryBean, final Method factoryMethod, Object... args) {
|
||||
@Nullable Object factoryBean, Method factoryMethod, Object... args) {
|
||||
|
||||
try {
|
||||
ReflectionUtils.makeAccessible(factoryMethod);
|
||||
|
||||
+7
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -78,8 +78,10 @@ public class PathEditor extends PropertyEditorSupport {
|
||||
if (nioPathCandidate && !text.startsWith("/")) {
|
||||
try {
|
||||
URI uri = ResourceUtils.toURI(text);
|
||||
if (uri.getScheme() != null) {
|
||||
nioPathCandidate = false;
|
||||
String scheme = uri.getScheme();
|
||||
if (scheme != null) {
|
||||
// No NIO candidate except for "C:" style drive letters
|
||||
nioPathCandidate = (scheme.length() == 1);
|
||||
// Let's try NIO file system providers via Paths.get(URI)
|
||||
setValue(Paths.get(uri).normalize());
|
||||
return;
|
||||
@@ -109,7 +111,8 @@ public class PathEditor extends PropertyEditorSupport {
|
||||
setValue(resource.getFile().toPath());
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalArgumentException("Failed to retrieve file for " + resource, ex);
|
||||
throw new IllegalArgumentException(
|
||||
"Could not retrieve file for " + resource + ": " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+39
-1
@@ -2102,7 +2102,7 @@ class DefaultListableBeanFactoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void beanProviderWithParentBeanFactoryReuseOrder() {
|
||||
void beanProviderWithParentBeanFactoryDetectsOrder() {
|
||||
DefaultListableBeanFactory parentBf = new DefaultListableBeanFactory();
|
||||
parentBf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
|
||||
parentBf.registerBeanDefinition("regular", new RootBeanDefinition(TestBean.class));
|
||||
@@ -2110,10 +2110,36 @@ class DefaultListableBeanFactoryTests {
|
||||
lbf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
|
||||
lbf.setParentBeanFactory(parentBf);
|
||||
lbf.registerBeanDefinition("low", new RootBeanDefinition(LowPriorityTestBean.class));
|
||||
|
||||
Stream<Class<?>> orderedTypes = lbf.getBeanProvider(TestBean.class).orderedStream().map(Object::getClass);
|
||||
assertThat(orderedTypes).containsExactly(HighPriorityTestBean.class, LowPriorityTestBean.class, TestBean.class);
|
||||
}
|
||||
|
||||
@Test // gh-28374
|
||||
void beanProviderWithParentBeanFactoryAndMixedOrder() {
|
||||
DefaultListableBeanFactory parentBf = new DefaultListableBeanFactory();
|
||||
parentBf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
|
||||
lbf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
|
||||
lbf.setParentBeanFactory(parentBf);
|
||||
|
||||
lbf.registerSingleton("plainTestBean", new TestBean());
|
||||
|
||||
RootBeanDefinition bd1 = new RootBeanDefinition(PriorityTestBeanFactory.class);
|
||||
bd1.setFactoryMethodName("lowPriorityTestBean");
|
||||
lbf.registerBeanDefinition("lowPriorityTestBean", bd1);
|
||||
|
||||
RootBeanDefinition bd2 = new RootBeanDefinition(PriorityTestBeanFactory.class);
|
||||
bd2.setFactoryMethodName("highPriorityTestBean");
|
||||
parentBf.registerBeanDefinition("highPriorityTestBean", bd2);
|
||||
|
||||
ObjectProvider<TestBean> testBeanProvider = lbf.getBeanProvider(ResolvableType.forClass(TestBean.class));
|
||||
List<TestBean> resolved = testBeanProvider.orderedStream().toList();
|
||||
assertThat(resolved.size()).isEqualTo(3);
|
||||
assertThat(resolved.get(0)).isSameAs(lbf.getBean("highPriorityTestBean"));
|
||||
assertThat(resolved.get(1)).isSameAs(lbf.getBean("lowPriorityTestBean"));
|
||||
assertThat(resolved.get(2)).isSameAs(lbf.getBean("plainTestBean"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void autowireExistingBeanByName() {
|
||||
RootBeanDefinition bd = new RootBeanDefinition(TestBean.class);
|
||||
@@ -3287,6 +3313,18 @@ class DefaultListableBeanFactoryTests {
|
||||
}
|
||||
|
||||
|
||||
private static class PriorityTestBeanFactory {
|
||||
|
||||
public static LowPriorityTestBean lowPriorityTestBean() {
|
||||
return new LowPriorityTestBean();
|
||||
}
|
||||
|
||||
public static HighPriorityTestBean highPriorityTestBean() {
|
||||
return new HighPriorityTestBean();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class NullTestBeanFactoryBean<T> implements FactoryBean<TestBean> {
|
||||
|
||||
@Override
|
||||
|
||||
+5
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -33,6 +33,10 @@ public interface ITestBean extends AgeHolder {
|
||||
|
||||
void setName(String name);
|
||||
|
||||
default void applyName(Object name) {
|
||||
setName(String.valueOf(name));
|
||||
}
|
||||
|
||||
ITestBean getSpouse();
|
||||
|
||||
void setSpouse(ITestBean spouse);
|
||||
|
||||
@@ -6,12 +6,12 @@ dependencies {
|
||||
api(project(":spring-core"))
|
||||
optional(project(":spring-jdbc")) // for Quartz support
|
||||
optional(project(":spring-tx")) // for Quartz support
|
||||
optional("com.github.ben-manes.caffeine:caffeine")
|
||||
optional("jakarta.activation:jakarta.activation-api")
|
||||
optional("jakarta.mail:jakarta.mail-api")
|
||||
optional("javax.cache:cache-api")
|
||||
optional("com.github.ben-manes.caffeine:caffeine")
|
||||
optional("org.quartz-scheduler:quartz")
|
||||
optional("org.freemarker:freemarker")
|
||||
optional("org.quartz-scheduler:quartz")
|
||||
testFixturesApi("org.junit.jupiter:junit-jupiter-api")
|
||||
testFixturesImplementation("org.assertj:assertj-core")
|
||||
testFixturesImplementation("org.mockito:mockito-core")
|
||||
@@ -20,10 +20,10 @@ dependencies {
|
||||
testImplementation(testFixtures(project(":spring-context")))
|
||||
testImplementation(testFixtures(project(":spring-core")))
|
||||
testImplementation(testFixtures(project(":spring-tx")))
|
||||
testImplementation("org.hsqldb:hsqldb")
|
||||
testImplementation("jakarta.annotation:jakarta.annotation-api")
|
||||
testRuntimeOnly("org.ehcache:jcache")
|
||||
testRuntimeOnly("org.ehcache:ehcache")
|
||||
testRuntimeOnly("org.glassfish:jakarta.el")
|
||||
testImplementation("org.hsqldb:hsqldb")
|
||||
testRuntimeOnly("com.sun.mail:jakarta.mail")
|
||||
testRuntimeOnly("org.ehcache:ehcache")
|
||||
testRuntimeOnly("org.ehcache:jcache")
|
||||
testRuntimeOnly("org.glassfish:jakarta.el")
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -236,7 +236,7 @@ public class CaffeineCacheManager implements CacheManager {
|
||||
* Build a common {@link CaffeineCache} instance for the specified cache name,
|
||||
* using the common Caffeine configuration specified on this cache manager.
|
||||
* <p>Delegates to {@link #adaptCaffeineCache} as the adaptation method to
|
||||
* Spring's cache abstraction (allowing for centralized decoration etc),
|
||||
* Spring's cache abstraction (allowing for centralized decoration etc.),
|
||||
* passing in a freshly built native Caffeine Cache instance.
|
||||
* @param name the name of the cache
|
||||
* @return the Spring CaffeineCache adapter (or a decorator thereof)
|
||||
|
||||
+12
-13
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -29,12 +29,10 @@ import org.springframework.core.MethodClassKey;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Abstract implementation of {@link JCacheOperationSource} that caches attributes
|
||||
* Abstract implementation of {@link JCacheOperationSource} that caches operations
|
||||
* for methods and implements a fallback policy: 1. specific target method;
|
||||
* 2. declaring method.
|
||||
*
|
||||
* <p>This implementation caches attributes by method after they are first used.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Juergen Hoeller
|
||||
* @since 4.1
|
||||
@@ -43,24 +41,25 @@ import org.springframework.lang.Nullable;
|
||||
public abstract class AbstractFallbackJCacheOperationSource implements JCacheOperationSource {
|
||||
|
||||
/**
|
||||
* Canonical value held in cache to indicate no caching attribute was
|
||||
* found for this method and we don't need to look again.
|
||||
* Canonical value held in cache to indicate no cache operation was
|
||||
* found for this method, and we don't need to look again.
|
||||
*/
|
||||
private static final Object NULL_CACHING_ATTRIBUTE = new Object();
|
||||
private static final Object NULL_CACHING_MARKER = new Object();
|
||||
|
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private final Map<MethodClassKey, Object> cache = new ConcurrentHashMap<>(1024);
|
||||
private final Map<MethodClassKey, Object> operationCache = new ConcurrentHashMap<>(1024);
|
||||
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public JCacheOperation<?> getCacheOperation(Method method, @Nullable Class<?> targetClass) {
|
||||
MethodClassKey cacheKey = new MethodClassKey(method, targetClass);
|
||||
Object cached = this.cache.get(cacheKey);
|
||||
Object cached = this.operationCache.get(cacheKey);
|
||||
|
||||
if (cached != null) {
|
||||
return (cached != NULL_CACHING_ATTRIBUTE ? (JCacheOperation<?>) cached : null);
|
||||
return (cached != NULL_CACHING_MARKER ? (JCacheOperation<?>) cached : null);
|
||||
}
|
||||
else {
|
||||
JCacheOperation<?> operation = computeCacheOperation(method, targetClass);
|
||||
@@ -68,10 +67,10 @@ public abstract class AbstractFallbackJCacheOperationSource implements JCacheOpe
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Adding cacheable method '" + method.getName() + "' with operation: " + operation);
|
||||
}
|
||||
this.cache.put(cacheKey, operation);
|
||||
this.operationCache.put(cacheKey, operation);
|
||||
}
|
||||
else {
|
||||
this.cache.put(cacheKey, NULL_CACHING_ATTRIBUTE);
|
||||
this.operationCache.put(cacheKey, NULL_CACHING_MARKER);
|
||||
}
|
||||
return operation;
|
||||
}
|
||||
@@ -84,7 +83,7 @@ public abstract class AbstractFallbackJCacheOperationSource implements JCacheOpe
|
||||
return null;
|
||||
}
|
||||
|
||||
// The method may be on an interface, but we need attributes from the target class.
|
||||
// The method may be on an interface, but we need metadata from the target class.
|
||||
// If the target class is null, the method will be unchanged.
|
||||
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
|
||||
|
||||
|
||||
+3
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -212,10 +212,8 @@ public abstract class AnnotationJCacheOperationSource extends AbstractFallbackJC
|
||||
for (Class<?> parameterType : parameterTypes) {
|
||||
parameters.add(parameterType.getName());
|
||||
}
|
||||
|
||||
return method.getDeclaringClass().getName()
|
||||
+ '.' + method.getName()
|
||||
+ '(' + StringUtils.collectionToCommaDelimitedString(parameters) + ')';
|
||||
return method.getDeclaringClass().getName() + '.' + method.getName() +
|
||||
'(' + StringUtils.collectionToCommaDelimitedString(parameters) + ')';
|
||||
}
|
||||
|
||||
private int countNonNull(Object... instances) {
|
||||
|
||||
+2
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -46,6 +46,7 @@ public class BeanFactoryJCacheOperationSourceAdvisor extends AbstractBeanFactory
|
||||
* Set the cache operation attribute source which is used to find cache
|
||||
* attributes. This should usually be identical to the source reference
|
||||
* set on the cache interceptor itself.
|
||||
* @see JCacheInterceptor#setCacheOperationSource
|
||||
*/
|
||||
public void setCacheOperationSource(JCacheOperationSource cacheOperationSource) {
|
||||
this.pointcut.setCacheOperationSource(cacheOperationSource);
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -34,7 +34,7 @@ public interface JCacheOperationSource {
|
||||
* Return the cache operations for this method, or {@code null}
|
||||
* if the method contains no <em>JSR-107</em> related metadata.
|
||||
* @param method the method to introspect
|
||||
* @param targetClass the target class (may be {@code null}, in which case
|
||||
* @param targetClass the target class (can be {@code null}, in which case
|
||||
* the declaring class of the method must be used)
|
||||
* @return the cache operation for this method, or {@code null} if none found
|
||||
*/
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -24,7 +24,7 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* A Pointcut that matches if the underlying {@link JCacheOperationSource}
|
||||
* A {@code Pointcut} that matches if the underlying {@link JCacheOperationSource}
|
||||
* has an operation for a given method.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
|
||||
+1
@@ -121,6 +121,7 @@ public class SimpleMailMessageTests {
|
||||
assertThat(message2.hashCode()).isEqualTo(message1.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public final void testEqualsObject() {
|
||||
SimpleMailMessage message1;
|
||||
SimpleMailMessage message2;
|
||||
|
||||
+6
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.
|
||||
@@ -32,6 +32,7 @@ import java.lang.annotation.Target;
|
||||
* @author Stephane Nicoll
|
||||
* @author Sam Brannen
|
||||
* @since 4.1
|
||||
* @see Cacheable
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@@ -42,8 +43,10 @@ public @interface CacheConfig {
|
||||
* Names of the default caches to consider for caching operations defined
|
||||
* in the annotated class.
|
||||
* <p>If none is set at the operation level, these are used instead of the default.
|
||||
* <p>May be used to determine the target cache (or caches), matching the
|
||||
* qualifier value or the bean names of a specific bean definition.
|
||||
* <p>Names may be used to determine the target cache(s), to be resolved via the
|
||||
* configured {@link #cacheResolver()} which typically delegates to
|
||||
* {@link org.springframework.cache.CacheManager#getCache}.
|
||||
* For further details see {@link Cacheable#cacheNames()}.
|
||||
*/
|
||||
String[] cacheNames() default {};
|
||||
|
||||
|
||||
+6
-2
@@ -70,8 +70,12 @@ public @interface Cacheable {
|
||||
|
||||
/**
|
||||
* Names of the caches in which method invocation results are stored.
|
||||
* <p>Names may be used to determine the target cache (or caches), matching
|
||||
* the qualifier value or bean name of a specific bean definition.
|
||||
* <p>Names may be used to determine the target cache(s), to be resolved via the
|
||||
* configured {@link #cacheResolver()} which typically delegates to
|
||||
* {@link org.springframework.cache.CacheManager#getCache}.
|
||||
* <p>This will usually be a single cache name. If multiple names are specified,
|
||||
* they will be consulted for a cache hit in the order of definition, and they
|
||||
* will all receive a put/evict request for the same newly cached value.
|
||||
* @since 4.2
|
||||
* @see #value
|
||||
* @see CacheConfig#cacheNames
|
||||
|
||||
+24
-28
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -32,20 +32,16 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Abstract implementation of {@link CacheOperation} that caches attributes
|
||||
* Abstract implementation of {@link CacheOperationSource} that caches operations
|
||||
* for methods and implements a fallback policy: 1. specific target method;
|
||||
* 2. target class; 3. declaring method; 4. declaring class/interface.
|
||||
*
|
||||
* <p>Defaults to using the target class's caching attribute if none is
|
||||
* associated with the target method. Any caching attribute associated with
|
||||
* the target method completely overrides a class caching attribute.
|
||||
* <p>Defaults to using the target class's declared cache operations if none are
|
||||
* associated with the target method. Any cache operations associated with
|
||||
* the target method completely override any class-level declarations.
|
||||
* If none found on the target class, the interface that the invoked method
|
||||
* has been called through (in case of a JDK proxy) will be checked.
|
||||
*
|
||||
* <p>This implementation caches attributes by method after they are first
|
||||
* used. If it is ever desirable to allow dynamic changing of cacheable
|
||||
* attributes (which is very unlikely), caching could be made configurable.
|
||||
*
|
||||
* @author Costin Leau
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.1
|
||||
@@ -53,10 +49,10 @@ import org.springframework.util.ClassUtils;
|
||||
public abstract class AbstractFallbackCacheOperationSource implements CacheOperationSource {
|
||||
|
||||
/**
|
||||
* Canonical value held in cache to indicate no caching attribute was
|
||||
* found for this method and we don't need to look again.
|
||||
* Canonical value held in cache to indicate no cache operation was
|
||||
* found for this method, and we don't need to look again.
|
||||
*/
|
||||
private static final Collection<CacheOperation> NULL_CACHING_ATTRIBUTE = Collections.emptyList();
|
||||
private static final Collection<CacheOperation> NULL_CACHING_MARKER = Collections.emptyList();
|
||||
|
||||
|
||||
/**
|
||||
@@ -71,14 +67,14 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
|
||||
* <p>As this base class is not marked Serializable, the cache will be recreated
|
||||
* after serialization - provided that the concrete subclass is Serializable.
|
||||
*/
|
||||
private final Map<Object, Collection<CacheOperation>> attributeCache = new ConcurrentHashMap<>(1024);
|
||||
private final Map<Object, Collection<CacheOperation>> operationCache = new ConcurrentHashMap<>(1024);
|
||||
|
||||
|
||||
/**
|
||||
* Determine the caching attribute for this method invocation.
|
||||
* <p>Defaults to the class's caching attribute if no method attribute is found.
|
||||
* Determine the cache operations for this method invocation.
|
||||
* <p>Defaults to class-declared metadata if no method-level metadata is found.
|
||||
* @param method the method for the current invocation (never {@code null})
|
||||
* @param targetClass the target class for this invocation (may be {@code null})
|
||||
* @param targetClass the target class for this invocation (can be {@code null})
|
||||
* @return {@link CacheOperation} for this method, or {@code null} if the method
|
||||
* is not cacheable
|
||||
*/
|
||||
@@ -90,21 +86,21 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
|
||||
}
|
||||
|
||||
Object cacheKey = getCacheKey(method, targetClass);
|
||||
Collection<CacheOperation> cached = this.attributeCache.get(cacheKey);
|
||||
Collection<CacheOperation> cached = this.operationCache.get(cacheKey);
|
||||
|
||||
if (cached != null) {
|
||||
return (cached != NULL_CACHING_ATTRIBUTE ? cached : null);
|
||||
return (cached != NULL_CACHING_MARKER ? cached : null);
|
||||
}
|
||||
else {
|
||||
Collection<CacheOperation> cacheOps = computeCacheOperations(method, targetClass);
|
||||
if (cacheOps != null) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Adding cacheable method '" + method.getName() + "' with attribute: " + cacheOps);
|
||||
logger.trace("Adding cacheable method '" + method.getName() + "' with operations: " + cacheOps);
|
||||
}
|
||||
this.attributeCache.put(cacheKey, cacheOps);
|
||||
this.operationCache.put(cacheKey, cacheOps);
|
||||
}
|
||||
else {
|
||||
this.attributeCache.put(cacheKey, NULL_CACHING_ATTRIBUTE);
|
||||
this.operationCache.put(cacheKey, NULL_CACHING_MARKER);
|
||||
}
|
||||
return cacheOps;
|
||||
}
|
||||
@@ -129,7 +125,7 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
|
||||
return null;
|
||||
}
|
||||
|
||||
// The method may be on an interface, but we need attributes from the target class.
|
||||
// The method may be on an interface, but we need metadata from the target class.
|
||||
// If the target class is null, the method will be unchanged.
|
||||
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
|
||||
|
||||
@@ -163,19 +159,19 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
|
||||
|
||||
|
||||
/**
|
||||
* Subclasses need to implement this to return the caching attribute for the
|
||||
* Subclasses need to implement this to return the cache operations for the
|
||||
* given class, if any.
|
||||
* @param clazz the class to retrieve the attribute for
|
||||
* @return all caching attribute associated with this class, or {@code null} if none
|
||||
* @param clazz the class to retrieve the cache operations for
|
||||
* @return all cache operations associated with this class, or {@code null} if none
|
||||
*/
|
||||
@Nullable
|
||||
protected abstract Collection<CacheOperation> findCacheOperations(Class<?> clazz);
|
||||
|
||||
/**
|
||||
* Subclasses need to implement this to return the caching attribute for the
|
||||
* Subclasses need to implement this to return the cache operations for the
|
||||
* given method, if any.
|
||||
* @param method the method to retrieve the attribute for
|
||||
* @return all caching attribute associated with this method, or {@code null} if none
|
||||
* @param method the method to retrieve the cache operations for
|
||||
* @return all cache operations associated with this method, or {@code null} if none
|
||||
*/
|
||||
@Nullable
|
||||
protected abstract Collection<CacheOperation> findCacheOperations(Method method);
|
||||
|
||||
+3
-3
@@ -547,10 +547,10 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect the {@link CachePutRequest} for all {@link CacheOperation} using
|
||||
* the specified result value.
|
||||
* Collect a {@link CachePutRequest} for every {@link CacheOperation}
|
||||
* using the specified result value.
|
||||
* @param contexts the contexts to handle
|
||||
* @param result the result value (never {@code null})
|
||||
* @param result the result value
|
||||
* @param putRequests the collection to update
|
||||
*/
|
||||
private void collectPutRequests(Collection<CacheOperationContext> contexts,
|
||||
|
||||
Vendored
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -22,7 +22,7 @@ import org.springframework.lang.Nullable;
|
||||
* Abstract the invocation of a cache operation.
|
||||
*
|
||||
* <p>Does not provide a way to transmit checked exceptions but
|
||||
* provide a special exception that should be used to wrap any
|
||||
* provides a special exception that should be used to wrap any
|
||||
* exception that was thrown by the underlying invocation.
|
||||
* Callers are expected to handle this issue type specifically.
|
||||
*
|
||||
|
||||
Vendored
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -54,7 +54,7 @@ public interface CacheOperationSource {
|
||||
* Return the collection of cache operations for this method,
|
||||
* or {@code null} if the method contains no <em>cacheable</em> annotations.
|
||||
* @param method the method to introspect
|
||||
* @param targetClass the target class (may be {@code null}, in which case
|
||||
* @param targetClass the target class (can be {@code null}, in which case
|
||||
* the declaring class of the method must be used)
|
||||
* @return all cache operations for this method, or {@code null} if none found
|
||||
*/
|
||||
|
||||
spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperationSourcePointcut.java
Vendored
+7
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -28,7 +28,7 @@ import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* A {@code Pointcut} that matches if the underlying {@link CacheOperationSource}
|
||||
* has an attribute for a given method.
|
||||
* has an operation for a given method.
|
||||
*
|
||||
* @author Costin Leau
|
||||
* @author Juergen Hoeller
|
||||
@@ -36,7 +36,7 @@ import org.springframework.util.ObjectUtils;
|
||||
* @since 3.1
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
class CacheOperationSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {
|
||||
final class CacheOperationSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {
|
||||
|
||||
@Nullable
|
||||
private CacheOperationSource cacheOperationSource;
|
||||
@@ -78,7 +78,7 @@ class CacheOperationSourcePointcut extends StaticMethodMatcherPointcut implement
|
||||
* {@link ClassFilter} that delegates to {@link CacheOperationSource#isCandidateClass}
|
||||
* for filtering classes whose methods are not worth searching to begin with.
|
||||
*/
|
||||
private class CacheOperationSourceClassFilter implements ClassFilter {
|
||||
private final class CacheOperationSourceClassFilter implements ClassFilter {
|
||||
|
||||
@Override
|
||||
public boolean matches(Class<?> clazz) {
|
||||
@@ -88,6 +88,7 @@ class CacheOperationSourcePointcut extends StaticMethodMatcherPointcut implement
|
||||
return (cacheOperationSource == null || cacheOperationSource.isCandidateClass(clazz));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private CacheOperationSource getCacheOperationSource() {
|
||||
return cacheOperationSource;
|
||||
}
|
||||
@@ -95,7 +96,7 @@ class CacheOperationSourcePointcut extends StaticMethodMatcherPointcut implement
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
return (this == other || (other instanceof CacheOperationSourceClassFilter that &&
|
||||
ObjectUtils.nullSafeEquals(cacheOperationSource, that.getCacheOperationSource())));
|
||||
ObjectUtils.nullSafeEquals(getCacheOperationSource(), that.getCacheOperationSource())));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -105,9 +106,8 @@ class CacheOperationSourcePointcut extends StaticMethodMatcherPointcut implement
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return CacheOperationSourceClassFilter.class.getName() + ": " + cacheOperationSource;
|
||||
return CacheOperationSourceClassFilter.class.getName() + ": " + getCacheOperationSource();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -121,7 +121,7 @@ public interface SmartLifecycle extends Lifecycle, Phased {
|
||||
/**
|
||||
* Return the phase that this lifecycle object is supposed to run in.
|
||||
* <p>The default implementation returns {@link #DEFAULT_PHASE} in order to
|
||||
* let {@code stop()} callbacks execute after regular {@code Lifecycle}
|
||||
* let {@code stop()} callbacks execute before regular {@code Lifecycle}
|
||||
* implementations.
|
||||
* @see #isAutoStartup()
|
||||
* @see #start()
|
||||
|
||||
+11
-11
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -62,11 +62,13 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* A component provider that provides candidate components from a base package. Can
|
||||
* use {@link CandidateComponentsIndex the index} if it is available of scans the
|
||||
* classpath otherwise. Candidate components are identified by applying exclude and
|
||||
* include filters. {@link AnnotationTypeFilter}, {@link AssignableTypeFilter} include
|
||||
* filters on an annotation/superclass that are annotated with {@link Indexed} are
|
||||
* A component provider that scans for candidate components starting from a
|
||||
* specified base package. Can use the {@linkplain CandidateComponentsIndex component
|
||||
* index}, if it is available, and scans the classpath otherwise.
|
||||
*
|
||||
* <p>Candidate components are identified by applying exclude and include filters.
|
||||
* {@link AnnotationTypeFilter} and {@link AssignableTypeFilter} include filters
|
||||
* for an annotation/target-type that is annotated with {@link Indexed} are
|
||||
* supported: if any other include filter is specified, the index is ignored and
|
||||
* classpath scanning is used instead.
|
||||
*
|
||||
@@ -201,7 +203,6 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
|
||||
* {@link Controller @Controller} stereotype annotations.
|
||||
* <p>Also supports Jakarta EE's {@link jakarta.annotation.ManagedBean} and
|
||||
* JSR-330's {@link jakarta.inject.Named} annotations, if available.
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void registerDefaultFilters() {
|
||||
@@ -305,7 +306,7 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
|
||||
|
||||
|
||||
/**
|
||||
* Scan the class path for candidate components.
|
||||
* Scan the component index or class path for candidate components.
|
||||
* @param basePackage the package to check for annotated classes
|
||||
* @return a corresponding Set of autodetected bean definitions
|
||||
*/
|
||||
@@ -319,7 +320,7 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the index can be used by this instance.
|
||||
* Determine if the component index can be used by this instance.
|
||||
* @return {@code true} if the index is available and the configuration of this
|
||||
* instance is supported by it, {@code false} otherwise
|
||||
* @since 5.0
|
||||
@@ -460,8 +461,7 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
|
||||
}
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
throw new BeanDefinitionStoreException(
|
||||
"Failed to read candidate component class: " + resource, ex);
|
||||
throw new BeanDefinitionStoreException("Failed to read candidate component class: " + resource, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+13
-11
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -387,8 +387,8 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
|
||||
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
|
||||
return;
|
||||
}
|
||||
if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
|
||||
if (ejbAnnotationType != null && bridgedMethod.isAnnotationPresent(ejbAnnotationType)) {
|
||||
if (ejbAnnotationType != null && bridgedMethod.isAnnotationPresent(ejbAnnotationType)) {
|
||||
if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
|
||||
if (Modifier.isStatic(method.getModifiers())) {
|
||||
throw new IllegalStateException("@EJB annotation is not supported on static methods");
|
||||
}
|
||||
@@ -398,7 +398,9 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
|
||||
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
|
||||
currElements.add(new EjbRefElement(method, bridgedMethod, pd));
|
||||
}
|
||||
else if (jakartaResourceType != null && bridgedMethod.isAnnotationPresent(jakartaResourceType)) {
|
||||
}
|
||||
else if (jakartaResourceType != null && bridgedMethod.isAnnotationPresent(jakartaResourceType)) {
|
||||
if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
|
||||
if (Modifier.isStatic(method.getModifiers())) {
|
||||
throw new IllegalStateException("@Resource annotation is not supported on static methods");
|
||||
}
|
||||
@@ -411,7 +413,9 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
|
||||
currElements.add(new ResourceElement(method, bridgedMethod, pd));
|
||||
}
|
||||
}
|
||||
else if (javaxResourceType != null && bridgedMethod.isAnnotationPresent(javaxResourceType)) {
|
||||
}
|
||||
else if (javaxResourceType != null && bridgedMethod.isAnnotationPresent(javaxResourceType)) {
|
||||
if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
|
||||
if (Modifier.isStatic(method.getModifiers())) {
|
||||
throw new IllegalStateException("@Resource annotation is not supported on static methods");
|
||||
}
|
||||
@@ -445,7 +449,7 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
|
||||
* @see #getResource
|
||||
* @see Lazy
|
||||
*/
|
||||
protected Object buildLazyResourceProxy(final LookupElement element, final @Nullable String requestingBeanName) {
|
||||
protected Object buildLazyResourceProxy(LookupElement element, @Nullable String requestingBeanName) {
|
||||
TargetSource ts = new TargetSource() {
|
||||
@Override
|
||||
public Class<?> getTargetClass() {
|
||||
@@ -525,16 +529,16 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
|
||||
String name = element.name;
|
||||
|
||||
if (factory instanceof AutowireCapableBeanFactory autowireCapableBeanFactory) {
|
||||
DependencyDescriptor descriptor = element.getDependencyDescriptor();
|
||||
if (this.fallbackToDefaultTypeMatch && element.isDefaultName && !factory.containsBean(name)) {
|
||||
autowiredBeanNames = new LinkedHashSet<>();
|
||||
resource = autowireCapableBeanFactory.resolveDependency(descriptor, requestingBeanName, autowiredBeanNames, null);
|
||||
resource = autowireCapableBeanFactory.resolveDependency(
|
||||
element.getDependencyDescriptor(), requestingBeanName, autowiredBeanNames, null);
|
||||
if (resource == null) {
|
||||
throw new NoSuchBeanDefinitionException(element.getLookupType(), "No resolvable resource object");
|
||||
}
|
||||
}
|
||||
else {
|
||||
resource = autowireCapableBeanFactory.resolveBeanByName(name, descriptor);
|
||||
resource = autowireCapableBeanFactory.resolveBeanByName(name, element.getDependencyDescriptor());
|
||||
autowiredBeanNames = Collections.singleton(name);
|
||||
}
|
||||
}
|
||||
@@ -661,8 +665,6 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Class representing injection information about an annotated field
|
||||
* or setter method, supporting the @Resource annotation.
|
||||
|
||||
+12
-15
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -73,7 +73,6 @@ final class ConfigurationClass {
|
||||
* Create a new {@link ConfigurationClass} with the given name.
|
||||
* @param metadataReader reader used to parse the underlying {@link Class}
|
||||
* @param beanName must not be {@code null}
|
||||
* @see ConfigurationClass#ConfigurationClass(Class, ConfigurationClass)
|
||||
*/
|
||||
ConfigurationClass(MetadataReader metadataReader, String beanName) {
|
||||
Assert.notNull(beanName, "Bean name must not be null");
|
||||
@@ -87,10 +86,10 @@ final class ConfigurationClass {
|
||||
* using the {@link Import} annotation or automatically processed as a nested
|
||||
* configuration class (if importedBy is not {@code null}).
|
||||
* @param metadataReader reader used to parse the underlying {@link Class}
|
||||
* @param importedBy the configuration class importing this one or {@code null}
|
||||
* @param importedBy the configuration class importing this one
|
||||
* @since 3.1.1
|
||||
*/
|
||||
ConfigurationClass(MetadataReader metadataReader, @Nullable ConfigurationClass importedBy) {
|
||||
ConfigurationClass(MetadataReader metadataReader, ConfigurationClass importedBy) {
|
||||
this.metadata = metadataReader.getAnnotationMetadata();
|
||||
this.resource = metadataReader.getResource();
|
||||
this.importedBy.add(importedBy);
|
||||
@@ -100,7 +99,6 @@ final class ConfigurationClass {
|
||||
* Create a new {@link ConfigurationClass} with the given name.
|
||||
* @param clazz the underlying {@link Class} to represent
|
||||
* @param beanName name of the {@code @Configuration} class bean
|
||||
* @see ConfigurationClass#ConfigurationClass(Class, ConfigurationClass)
|
||||
*/
|
||||
ConfigurationClass(Class<?> clazz, String beanName) {
|
||||
Assert.notNull(beanName, "Bean name must not be null");
|
||||
@@ -114,10 +112,10 @@ final class ConfigurationClass {
|
||||
* using the {@link Import} annotation or automatically processed as a nested
|
||||
* configuration class (if imported is {@code true}).
|
||||
* @param clazz the underlying {@link Class} to represent
|
||||
* @param importedBy the configuration class importing this one (or {@code null})
|
||||
* @param importedBy the configuration class importing this one
|
||||
* @since 3.1.1
|
||||
*/
|
||||
ConfigurationClass(Class<?> clazz, @Nullable ConfigurationClass importedBy) {
|
||||
ConfigurationClass(Class<?> clazz, ConfigurationClass importedBy) {
|
||||
this.metadata = AnnotationMetadata.introspect(clazz);
|
||||
this.resource = new DescriptiveResource(clazz.getName());
|
||||
this.importedBy.add(importedBy);
|
||||
@@ -127,7 +125,6 @@ final class ConfigurationClass {
|
||||
* Create a new {@link ConfigurationClass} with the given name.
|
||||
* @param metadata the metadata for the underlying class to represent
|
||||
* @param beanName name of the {@code @Configuration} class bean
|
||||
* @see ConfigurationClass#ConfigurationClass(Class, ConfigurationClass)
|
||||
*/
|
||||
ConfigurationClass(AnnotationMetadata metadata, String beanName) {
|
||||
Assert.notNull(beanName, "Bean name must not be null");
|
||||
@@ -149,12 +146,12 @@ final class ConfigurationClass {
|
||||
return ClassUtils.getShortName(getMetadata().getClassName());
|
||||
}
|
||||
|
||||
void setBeanName(String beanName) {
|
||||
void setBeanName(@Nullable String beanName) {
|
||||
this.beanName = beanName;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getBeanName() {
|
||||
String getBeanName() {
|
||||
return this.beanName;
|
||||
}
|
||||
|
||||
@@ -164,7 +161,7 @@ final class ConfigurationClass {
|
||||
* @since 3.1.1
|
||||
* @see #getImportedBy()
|
||||
*/
|
||||
public boolean isImported() {
|
||||
boolean isImported() {
|
||||
return !this.importedBy.isEmpty();
|
||||
}
|
||||
|
||||
@@ -198,6 +195,10 @@ final class ConfigurationClass {
|
||||
this.importedResources.put(importedResource, readerClass);
|
||||
}
|
||||
|
||||
Map<String, Class<? extends BeanDefinitionReader>> getImportedResources() {
|
||||
return this.importedResources;
|
||||
}
|
||||
|
||||
void addImportBeanDefinitionRegistrar(ImportBeanDefinitionRegistrar registrar, AnnotationMetadata importingClassMetadata) {
|
||||
this.importBeanDefinitionRegistrars.put(registrar, importingClassMetadata);
|
||||
}
|
||||
@@ -206,10 +207,6 @@ final class ConfigurationClass {
|
||||
return this.importBeanDefinitionRegistrars;
|
||||
}
|
||||
|
||||
Map<String, Class<? extends BeanDefinitionReader>> getImportedResources() {
|
||||
return this.importedResources;
|
||||
}
|
||||
|
||||
void validate(ProblemReporter problemReporter) {
|
||||
Map<String, Object> attributes = this.metadata.getAnnotationAttributes(Configuration.class.getName());
|
||||
|
||||
|
||||
+1
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -44,7 +44,6 @@ import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
||||
* public MyService myService() {
|
||||
* return new MyService();
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <p>If the configuration class above is processed, {@code MyHints} will be
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 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.
|
||||
@@ -38,6 +38,14 @@ import org.springframework.core.env.Profiles;
|
||||
* for web applications. Profiles may also be activated declaratively in
|
||||
* integration tests via the {@code @ActiveProfiles} annotation.
|
||||
*
|
||||
* <p>If no profile is active using one of those options, a default profile is
|
||||
* enabled as a fallback. The name of the default profile is
|
||||
* {@value AbstractEnvironment#RESERVED_DEFAULT_PROFILE_NAME}. This can be changed
|
||||
* via {@link ConfigurableEnvironment#setDefaultProfiles} or declaratively by
|
||||
* setting the {@link AbstractEnvironment#DEFAULT_PROFILES_PROPERTY_NAME
|
||||
* spring.profiles.default} property as a JVM system property, as an environment
|
||||
* variable, or as a Servlet context parameter in {@code web.xml} for web applications.
|
||||
*
|
||||
* <p>The {@code @Profile} annotation may be used in any of the following ways:
|
||||
* <ul>
|
||||
* <li>as a type-level annotation on any class directly or indirectly annotated with
|
||||
|
||||
+24
-22
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -135,17 +135,6 @@ import org.springframework.util.ReflectionUtils;
|
||||
public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
||||
implements ConfigurableApplicationContext {
|
||||
|
||||
/**
|
||||
* The name of the {@link LifecycleProcessor} bean in the context.
|
||||
* If none is supplied, a {@link DefaultLifecycleProcessor} is used.
|
||||
* @since 3.0
|
||||
* @see org.springframework.context.LifecycleProcessor
|
||||
* @see org.springframework.context.support.DefaultLifecycleProcessor
|
||||
* @see #start()
|
||||
* @see #stop()
|
||||
*/
|
||||
public static final String LIFECYCLE_PROCESSOR_BEAN_NAME = "lifecycleProcessor";
|
||||
|
||||
/**
|
||||
* The name of the {@link MessageSource} bean in the context.
|
||||
* If none is supplied, message resolution is delegated to the parent.
|
||||
@@ -166,6 +155,17 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
||||
*/
|
||||
public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";
|
||||
|
||||
/**
|
||||
* The name of the {@link LifecycleProcessor} bean in the context.
|
||||
* If none is supplied, a {@link DefaultLifecycleProcessor} is used.
|
||||
* @since 3.0
|
||||
* @see org.springframework.context.LifecycleProcessor
|
||||
* @see org.springframework.context.support.DefaultLifecycleProcessor
|
||||
* @see #start()
|
||||
* @see #stop()
|
||||
*/
|
||||
public static final String LIFECYCLE_PROCESSOR_BEAN_NAME = "lifecycleProcessor";
|
||||
|
||||
|
||||
static {
|
||||
// Eagerly load the ContextClosedEvent class to avoid weird classloader issues
|
||||
@@ -203,7 +203,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
||||
/** Flag that indicates whether this context has been closed already. */
|
||||
private final AtomicBoolean closed = new AtomicBoolean();
|
||||
|
||||
/** Synchronization monitor for the "refresh" and "destroy". */
|
||||
/** Synchronization monitor for "refresh" and "close". */
|
||||
private final Object startupShutdownMonitor = new Object();
|
||||
|
||||
/** Reference to the JVM shutdown hook, if registered. */
|
||||
@@ -595,7 +595,6 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
||||
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
|
||||
// Invoke factory processors registered as beans in the context.
|
||||
invokeBeanFactoryPostProcessors(beanFactory);
|
||||
|
||||
// Register bean processors that intercept bean creation.
|
||||
registerBeanPostProcessors(beanFactory);
|
||||
beanPostProcess.end();
|
||||
@@ -797,8 +796,9 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the MessageSource.
|
||||
* Use parent's if none defined in this context.
|
||||
* Initialize the {@link MessageSource}.
|
||||
* <p>Uses parent's {@code MessageSource} if none defined in this context.
|
||||
* @see #MESSAGE_SOURCE_BEAN_NAME
|
||||
*/
|
||||
protected void initMessageSource() {
|
||||
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
|
||||
@@ -828,8 +828,9 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the ApplicationEventMulticaster.
|
||||
* Uses SimpleApplicationEventMulticaster if none defined in the context.
|
||||
* Initialize the {@link ApplicationEventMulticaster}.
|
||||
* <p>Uses {@link SimpleApplicationEventMulticaster} if none defined in the context.
|
||||
* @see #APPLICATION_EVENT_MULTICASTER_BEAN_NAME
|
||||
* @see org.springframework.context.event.SimpleApplicationEventMulticaster
|
||||
*/
|
||||
protected void initApplicationEventMulticaster() {
|
||||
@@ -852,15 +853,16 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the LifecycleProcessor.
|
||||
* Uses DefaultLifecycleProcessor if none defined in the context.
|
||||
* Initialize the {@link LifecycleProcessor}.
|
||||
* <p>Uses {@link DefaultLifecycleProcessor} if none defined in the context.
|
||||
* @since 3.0
|
||||
* @see #LIFECYCLE_PROCESSOR_BEAN_NAME
|
||||
* @see org.springframework.context.support.DefaultLifecycleProcessor
|
||||
*/
|
||||
protected void initLifecycleProcessor() {
|
||||
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
|
||||
if (beanFactory.containsLocalBean(LIFECYCLE_PROCESSOR_BEAN_NAME)) {
|
||||
this.lifecycleProcessor =
|
||||
beanFactory.getBean(LIFECYCLE_PROCESSOR_BEAN_NAME, LifecycleProcessor.class);
|
||||
this.lifecycleProcessor = beanFactory.getBean(LIFECYCLE_PROCESSOR_BEAN_NAME, LifecycleProcessor.class);
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Using LifecycleProcessor [" + this.lifecycleProcessor + "]");
|
||||
}
|
||||
|
||||
+8
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -45,7 +45,11 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Default implementation of the {@link LifecycleProcessor} strategy.
|
||||
* Spring's default implementation of the {@link LifecycleProcessor} strategy.
|
||||
*
|
||||
* <p>Provides interaction with {@link Lifecycle} and {@link SmartLifecycle} beans in
|
||||
* groups for specific phases, on startup/shutdown as well as for explicit start/stop
|
||||
* interactions on a {@link org.springframework.context.ConfigurableApplicationContext}.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Juergen Hoeller
|
||||
@@ -314,6 +318,8 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor
|
||||
/**
|
||||
* Helper class for maintaining a group of Lifecycle beans that should be started
|
||||
* and stopped together based on their 'phase' value (or the default value of 0).
|
||||
* The group is expected to be created in an ad-hoc fashion and group members are
|
||||
* expected to always have the same 'phase' value.
|
||||
*/
|
||||
private class LifecycleGroup {
|
||||
|
||||
|
||||
+4
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -27,6 +27,7 @@ import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.springframework.context.ResourceLoaderAware;
|
||||
@@ -400,7 +401,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractResourceBased
|
||||
|
||||
/**
|
||||
* Refresh the PropertiesHolder for the given bundle filename.
|
||||
* The holder can be {@code null} if not cached before, or a timed-out cache entry
|
||||
* <p>The holder can be {@code null} if not cached before, or a timed-out cache entry
|
||||
* (potentially getting re-validated against the current last-modified timestamp).
|
||||
* @param filename the bundle filename (basename + Locale)
|
||||
* @param propHolder the current PropertiesHolder for the bundle
|
||||
@@ -561,7 +562,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractResourceBased
|
||||
|
||||
private volatile long refreshTimestamp = -2;
|
||||
|
||||
private final ReentrantLock refreshLock = new ReentrantLock();
|
||||
private final Lock refreshLock = new ReentrantLock();
|
||||
|
||||
/** Cache to hold already generated MessageFormats per message code. */
|
||||
private final ConcurrentMap<String, Map<Locale, MessageFormat>> cachedMessageFormats =
|
||||
|
||||
+9
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -18,12 +18,13 @@ package org.springframework.ejb.config;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.jndi.JndiObjectFactoryBean;
|
||||
|
||||
/**
|
||||
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser}
|
||||
* implementation for parsing '{@code local-slsb}' tags and
|
||||
* creating plain {@link JndiObjectFactoryBean} definitions.
|
||||
* creating plain {@link JndiObjectFactoryBean} definitions on 6.0.
|
||||
*
|
||||
* @author Rob Harrop
|
||||
* @author Juergen Hoeller
|
||||
@@ -36,4 +37,10 @@ class LocalStatelessSessionBeanDefinitionParser extends AbstractJndiLocatingBean
|
||||
return JndiObjectFactoryBean.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isEligibleAttribute(String attributeName) {
|
||||
return (super.isEligibleAttribute(attributeName) &&
|
||||
BeanUtils.getPropertyDescriptor(JndiObjectFactoryBean.class, extractPropertyName(attributeName)) != null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+9
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -18,12 +18,13 @@ package org.springframework.ejb.config;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.jndi.JndiObjectFactoryBean;
|
||||
|
||||
/**
|
||||
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser}
|
||||
* implementation for parsing '{@code remote-slsb}' tags and
|
||||
* creating plain {@link JndiObjectFactoryBean} definitions.
|
||||
* creating plain {@link JndiObjectFactoryBean} definitions as of 6.0.
|
||||
*
|
||||
* @author Rob Harrop
|
||||
* @author Juergen Hoeller
|
||||
@@ -36,4 +37,10 @@ class RemoteStatelessSessionBeanDefinitionParser extends AbstractJndiLocatingBea
|
||||
return JndiObjectFactoryBean.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isEligibleAttribute(String attributeName) {
|
||||
return (super.isEligibleAttribute(attributeName) &&
|
||||
BeanUtils.getPropertyDescriptor(JndiObjectFactoryBean.class, extractPropertyName(attributeName)) != null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+18
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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,6 +50,9 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public class MetadataNamingStrategy implements ObjectNamingStrategy, InitializingBean {
|
||||
|
||||
private static final char[] QUOTABLE_CHARS = new char[] {',', '=', ':', '"'};
|
||||
|
||||
|
||||
/**
|
||||
* The {@code JmxAttributeSource} implementation to use for reading metadata.
|
||||
*/
|
||||
@@ -132,10 +135,23 @@ public class MetadataNamingStrategy implements ObjectNamingStrategy, Initializin
|
||||
}
|
||||
Hashtable<String, String> properties = new Hashtable<>();
|
||||
properties.put("type", ClassUtils.getShortName(managedClass));
|
||||
properties.put("name", beanKey);
|
||||
properties.put("name", quoteIfNecessary(beanKey));
|
||||
return ObjectNameManager.getInstance(domain, properties);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String quoteIfNecessary(String value) {
|
||||
return shouldQuote(value) ? ObjectName.quote(value) : value;
|
||||
}
|
||||
|
||||
private static boolean shouldQuote(String value) {
|
||||
for (char quotableChar : QUOTABLE_CHARS) {
|
||||
if (value.indexOf(quotableChar) != -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ public interface TaskScheduler {
|
||||
* wrapping a cron expression
|
||||
* @return a {@link ScheduledFuture} representing pending completion of the task,
|
||||
* or {@code null} if the given Trigger object never fires (i.e. returns
|
||||
* {@code null} from {@link Trigger#nextExecutionTime})
|
||||
* {@code null} from {@link Trigger#nextExecution})
|
||||
* @throws org.springframework.core.task.TaskRejectedException if the given task was not accepted
|
||||
* for internal reasons (e.g. a pool overload handling policy or a pool shutdown in progress)
|
||||
* @see org.springframework.scheduling.support.CronTrigger
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -98,8 +98,8 @@ public class AsyncAnnotationBeanPostProcessor extends AbstractBeanFactoryAwareAd
|
||||
* applying the corresponding default if a supplier is not resolvable.
|
||||
* @since 5.1
|
||||
*/
|
||||
public void configure(
|
||||
@Nullable Supplier<Executor> executor, @Nullable Supplier<AsyncUncaughtExceptionHandler> exceptionHandler) {
|
||||
public void configure(@Nullable Supplier<Executor> executor,
|
||||
@Nullable Supplier<AsyncUncaughtExceptionHandler> exceptionHandler) {
|
||||
|
||||
this.executor = executor;
|
||||
this.exceptionHandler = exceptionHandler;
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -102,9 +102,9 @@ public @interface Scheduled {
|
||||
|
||||
/**
|
||||
* A time zone for which the cron expression will be resolved. By default, this
|
||||
* attribute is the empty String (i.e. the server's local time zone will be used).
|
||||
* attribute is the empty String (i.e. the scheduler's time zone will be used).
|
||||
* @return a zone id accepted by {@link java.util.TimeZone#getTimeZone(String)},
|
||||
* or an empty String to indicate the server's default time zone
|
||||
* or an empty String to indicate the scheduler's default time zone
|
||||
* @since 4.0
|
||||
* @see org.springframework.scheduling.support.CronTrigger#CronTrigger(String, java.util.TimeZone)
|
||||
* @see java.util.TimeZone
|
||||
|
||||
+5
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -26,7 +26,6 @@ import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -430,14 +429,14 @@ public class ScheduledAnnotationBeanPostProcessor
|
||||
Assert.isTrue(initialDelay.isNegative(), "'initialDelay' not supported for cron triggers");
|
||||
processedSchedule = true;
|
||||
if (!Scheduled.CRON_DISABLED.equals(cron)) {
|
||||
TimeZone timeZone;
|
||||
CronTrigger trigger;
|
||||
if (StringUtils.hasText(zone)) {
|
||||
timeZone = StringUtils.parseTimeZoneString(zone);
|
||||
trigger = new CronTrigger(cron, StringUtils.parseTimeZoneString(zone));
|
||||
}
|
||||
else {
|
||||
timeZone = TimeZone.getDefault();
|
||||
trigger = new CronTrigger(cron);
|
||||
}
|
||||
tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
|
||||
tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, trigger)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -73,7 +73,7 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
|
||||
|
||||
/**
|
||||
* Set the ThreadFactory to use for the ExecutorService's thread pool.
|
||||
* Default is the underlying ExecutorService's default thread factory.
|
||||
* The default is the underlying ExecutorService's default thread factory.
|
||||
* <p>In a Jakarta EE or other managed environment with JSR-236 support,
|
||||
* consider specifying a JNDI-located ManagedThreadFactory: by default,
|
||||
* to be found at "java:comp/DefaultManagedThreadFactory".
|
||||
@@ -108,7 +108,7 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
|
||||
/**
|
||||
* Set whether to wait for scheduled tasks to complete on shutdown,
|
||||
* not interrupting running tasks and executing all tasks in the queue.
|
||||
* <p>Default is {@code false}, shutting down immediately through interrupting
|
||||
* <p>The default is {@code false}, shutting down immediately through interrupting
|
||||
* ongoing tasks and clearing the queue. Switch this flag to {@code true} if
|
||||
* you prefer fully completed tasks at the expense of a longer shutdown phase.
|
||||
* <p>Note that Spring's container shutdown continues while ongoing tasks
|
||||
@@ -119,6 +119,8 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
|
||||
* property instead of or in addition to this property.
|
||||
* @see java.util.concurrent.ExecutorService#shutdown()
|
||||
* @see java.util.concurrent.ExecutorService#shutdownNow()
|
||||
* @see #shutdown()
|
||||
* @see #setAwaitTerminationSeconds
|
||||
*/
|
||||
public void setWaitForTasksToCompleteOnShutdown(boolean waitForJobsToCompleteOnShutdown) {
|
||||
this.waitForTasksToCompleteOnShutdown = waitForJobsToCompleteOnShutdown;
|
||||
@@ -237,7 +239,7 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the given remaining task which never commended execution,
|
||||
* Cancel the given remaining task which never commenced execution,
|
||||
* as returned from {@link ExecutorService#shutdownNow()}.
|
||||
* @param task the task to cancel (typically a {@link RunnableFuture})
|
||||
* @since 5.0.5
|
||||
|
||||
+18
-27
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -29,17 +29,15 @@ import org.springframework.util.StringUtils;
|
||||
* Created using the {@code parse*} methods.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Juergen Hoeller
|
||||
* @since 5.3
|
||||
*/
|
||||
final class BitsCronField extends CronField {
|
||||
|
||||
public static final BitsCronField ZERO_NANOS = forZeroNanos();
|
||||
|
||||
private static final long MASK = 0xFFFFFFFFFFFFFFFFL;
|
||||
|
||||
|
||||
@Nullable
|
||||
private static BitsCronField zeroNanos = null;
|
||||
|
||||
|
||||
// we store at most 60 bits, for seconds and minutes, so a 64-bit long suffices
|
||||
private long bits;
|
||||
|
||||
@@ -48,16 +46,14 @@ final class BitsCronField extends CronField {
|
||||
super(type);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a {@code BitsCronField} enabled for 0 nanoseconds.
|
||||
*/
|
||||
public static BitsCronField zeroNanos() {
|
||||
if (zeroNanos == null) {
|
||||
BitsCronField field = new BitsCronField(Type.NANO);
|
||||
field.setBit(0);
|
||||
zeroNanos = field;
|
||||
}
|
||||
return zeroNanos;
|
||||
private static BitsCronField forZeroNanos() {
|
||||
BitsCronField field = new BitsCronField(Type.NANO);
|
||||
field.setBit(0);
|
||||
return field;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,7 +104,6 @@ final class BitsCronField extends CronField {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private static BitsCronField parseDate(String value, BitsCronField.Type type) {
|
||||
if (value.equals("?")) {
|
||||
value = "*";
|
||||
@@ -174,6 +169,7 @@ final class BitsCronField extends CronField {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public <T extends Temporal & Comparable<? super T>> T nextOrSame(T temporal) {
|
||||
@@ -217,7 +213,6 @@ final class BitsCronField extends CronField {
|
||||
else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void setBits(ValueRange range) {
|
||||
@@ -247,7 +242,14 @@ final class BitsCronField extends CronField {
|
||||
}
|
||||
|
||||
private void clearBit(int index) {
|
||||
this.bits &= ~(1L << index);
|
||||
this.bits &= ~(1L << index);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
return (this == other || (other instanceof BitsCronField that &&
|
||||
type() == that.type() && this.bits == that.bits));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -255,17 +257,6 @@ final class BitsCronField extends CronField {
|
||||
return Long.hashCode(this.bits);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof BitsCronField other)) {
|
||||
return false;
|
||||
}
|
||||
return type() == other.type() && this.bits == other.bits;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder(type().toString());
|
||||
|
||||
+10
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -29,9 +29,14 @@ import org.springframework.util.StringUtils;
|
||||
* <a href="https://www.manpagez.com/man/5/crontab/">crontab expression</a>
|
||||
* that can calculate the next time it matches.
|
||||
*
|
||||
* <p>{@code CronExpression} instances are created through
|
||||
* {@link #parse(String)}; the next match is determined with
|
||||
* {@link #next(Temporal)}.
|
||||
* <p>{@code CronExpression} instances are created through {@link #parse(String)};
|
||||
* the next match is determined with {@link #next(Temporal)}.
|
||||
*
|
||||
* <p>Supports a Quartz day-of-month/week field with an L/# expression. Follows
|
||||
* common cron conventions in every other respect, including 0-6 for SUN-SAT
|
||||
* (plus 7 for SUN as well). Note that Quartz deviates from the day-of-week
|
||||
* convention in cron through 1-7 for SUN-SAT whereas Spring strictly follows
|
||||
* cron even in combination with the optional Quartz-specific L/# expressions.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 5.3
|
||||
@@ -168,7 +173,7 @@ public final class CronExpression {
|
||||
* the cron format
|
||||
*/
|
||||
public static CronExpression parse(String expression) {
|
||||
Assert.hasLength(expression, "Expression string must not be empty");
|
||||
Assert.hasLength(expression, "Expression must not be empty");
|
||||
|
||||
expression = resolveMacros(expression);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -29,17 +29,24 @@ import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Single field in a cron pattern. Created using the {@code parse*} methods,
|
||||
* main and only entry point is {@link #nextOrSame(Temporal)}.
|
||||
* the main and only entry point is {@link #nextOrSame(Temporal)}.
|
||||
*
|
||||
* <p>Supports a Quartz day-of-month/week field with an L/# expression. Follows
|
||||
* common cron conventions in every other respect, including 0-6 for SUN-SAT
|
||||
* (plus 7 for SUN as well). Note that Quartz deviates from the day-of-week
|
||||
* convention in cron through 1-7 for SUN-SAT whereas Spring strictly follows
|
||||
* cron even in combination with the optional Quartz-specific L/# expressions.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 5.3
|
||||
*/
|
||||
abstract class CronField {
|
||||
|
||||
private static final String[] MONTHS = new String[]{"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP",
|
||||
"OCT", "NOV", "DEC"};
|
||||
private static final String[] MONTHS = new String[]
|
||||
{"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
|
||||
|
||||
private static final String[] DAYS = new String[]{"MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"};
|
||||
private static final String[] DAYS = new String[]
|
||||
{"MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"};
|
||||
|
||||
private final Type type;
|
||||
|
||||
@@ -48,11 +55,12 @@ abstract class CronField {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a {@code CronField} enabled for 0 nanoseconds.
|
||||
*/
|
||||
public static CronField zeroNanos() {
|
||||
return BitsCronField.zeroNanos();
|
||||
return BitsCronField.ZERO_NANOS;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,6 +177,7 @@ abstract class CronField {
|
||||
* day-of-month, month, day-of-week.
|
||||
*/
|
||||
protected enum Type {
|
||||
|
||||
NANO(ChronoField.NANO_OF_SECOND, ChronoUnit.SECONDS),
|
||||
SECOND(ChronoField.SECOND_OF_MINUTE, ChronoUnit.MINUTES, ChronoField.NANO_OF_SECOND),
|
||||
MINUTE(ChronoField.MINUTE_OF_HOUR, ChronoUnit.HOURS, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND),
|
||||
@@ -177,21 +186,18 @@ abstract class CronField {
|
||||
MONTH(ChronoField.MONTH_OF_YEAR, ChronoUnit.YEARS, ChronoField.DAY_OF_MONTH, ChronoField.HOUR_OF_DAY, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND),
|
||||
DAY_OF_WEEK(ChronoField.DAY_OF_WEEK, ChronoUnit.WEEKS, ChronoField.HOUR_OF_DAY, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND);
|
||||
|
||||
|
||||
private final ChronoField field;
|
||||
|
||||
private final ChronoUnit higherOrder;
|
||||
|
||||
private final ChronoField[] lowerOrders;
|
||||
|
||||
|
||||
Type(ChronoField field, ChronoUnit higherOrder, ChronoField... lowerOrders) {
|
||||
this.field = field;
|
||||
this.higherOrder = higherOrder;
|
||||
this.lowerOrders = lowerOrders;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the value of this type for the given temporal.
|
||||
* @return the value of this type
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user