Compare commits

..

1 Commits

Author SHA1 Message Date
Spring Builds 379e1f22ee Release v6.1.1 2023-11-23 08:51:35 +00:00
1912 changed files with 18163 additions and 24790 deletions
+36
View File
@@ -0,0 +1,36 @@
Juergen Hoeller <jhoeller@vmware.com>
Juergen Hoeller <jhoeller@vmware.com> <jhoeller@pivotal.io>
Juergen Hoeller <jhoeller@vmware.com> <jhoeller@gopivotal.com>
Rossen Stoyanchev <rstoyanchev@vmware.com>
Rossen Stoyanchev <rstoyanchev@vmware.com> <rstoyanchev@pivotal.io>
Rossen Stoyanchev <rstoyanchev@vmware.com> <rstoyanchev@gopivotal.com>
Phillip Webb <pwebb@vmware.com>
Phillip Webb <pwebb@vmware.com> <pwebb@pivotal.io>
Phillip Webb <pwebb@vmware.com> <pwebb@gopivotal.com>
Chris Beams <cbeams@vmware.com>
Chris Beams <cbeams@vmware.com> <cbeams@pivotal.io>
Chris Beams <cbeams@vmware.com> <cbeams@gopivotal.com>
Arjen Poutsma <poutsmaa@vmware.com>
Arjen Poutsma <poutsmaa@vmware.com> <apoutsma@pivotal.io>
Arjen Poutsma <poutsmaa@vmware.com> <apoutsma@gopivotal.com>
Arjen Poutsma <poutsmaa@vmware.com> <poutsma@mac.com>
Arjen Poutsma <poutsmaa@vmware.com> <apoutsma@vmware.com>
Oliver Drotbohm <odrotbohm@vmware.com>
Oliver Drotbohm <odrotbohm@vmware.com> <ogierke@vmware.com>
Oliver Drotbohm <odrotbohm@vmware.com> <ogierke@pivotal.io>
Oliver Drotbohm <odrotbohm@vmware.com> <ogierke@gopivotal.com>
Dave Syer <dsyer@vmware.com>
Dave Syer <dsyer@vmware.com> <dsyer@pivotal.io>
Dave Syer <dsyer@vmware.com> <dsyer@gopivotal.com>
Dave Syer <dsyer@vmware.com> <david_syer@hotmail.com>
Andy Clement <aclement@vmware.com>
Andy Clement <aclement@vmware.com> <aclement@pivotal.io>
Andy Clement <aclement@vmware.com> <aclement@gopivotal.com>
Andy Clement <aclement@vmware.com> <andrew.clement@gmail.com>
Sam Brannen <sbrannen@vmware.com>
Sam Brannen <sbrannen@vmware.com> <sbrannen@pivotal.io>
Sam Brannen <sbrannen@vmware.com> <sam@sambrannen.com>
Simon Basle <sbasle@vmware.com>
Simon Baslé <sbasle@vmware.com>
<dmitry.katsubo@gmail.com> <dmitry.katsubo@gmai.com>
Nick Williams <nicholas@nicholaswilliams.net>
+1 -1
View File
@@ -1,3 +1,3 @@
# Enable auto-env through the sdkman_auto_env config
# Add key=value pairs of SDKs to use below
java=17.0.9-librca
java=17.0.8.1-librca
+2 -2
View File
@@ -1,4 +1,4 @@
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [![Build Status](https://ci.spring.io/api/v1/teams/spring-framework/pipelines/spring-framework-6.1.x/jobs/build/badge)](https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-6.1.x?groups=Build") [![Revved up by Gradle Enterprise](https://img.shields.io/badge/Revved%20up%20by-Gradle%20Enterprise-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.spring.io/scans?search.rootProjectNames=spring)
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [![Build Status](https://ci.spring.io/api/v1/teams/spring-framework/pipelines/spring-framework-6.0.x/jobs/build/badge)](https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-6.0.x?groups=Build") [![Revved up by Gradle Enterprise](https://img.shields.io/badge/Revved%20up%20by-Gradle%20Enterprise-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.spring.io/scans?search.rootProjectNames=spring)
This is the home of the Spring Framework: the foundation for all [Spring projects](https://spring.io/projects). Collectively the Spring Framework and the family of Spring projects are often referred to simply as "Spring".
@@ -31,7 +31,7 @@ Information regarding CI builds can be found in the [Spring Framework Concourse
## Stay in Touch
Follow [@SpringCentral](https://twitter.com/springcentral), [@SpringFramework](https://twitter.com/springframework), and its [team members](https://twitter.com/springframework/lists/team/members) on 𝕏. In-depth articles can be found at [The Spring Blog](https://spring.io/blog/), and releases are announced via our [releases feed](https://spring.io/blog/category/releases).
Follow [@SpringCentral](https://twitter.com/springcentral), [@SpringFramework](https://twitter.com/springframework), and its [team members](https://twitter.com/springframework/lists/team/members) on Twitter. In-depth articles can be found at [The Spring Blog](https://spring.io/blog/), and releases are announced via our [releases feed](https://spring.io/blog/category/releases).
## License
+6 -3
View File
@@ -4,7 +4,7 @@ plugins {
id 'org.jetbrains.kotlin.plugin.serialization' version "${kotlinVersion}" apply false
id 'org.jetbrains.dokka' version '1.8.20'
id 'org.unbroken-dome.xjc' version '2.0.0' apply false
id 'com.github.ben-manes.versions' version '0.50.0'
id 'com.github.ben-manes.versions' version '0.49.0'
id 'com.github.johnrengelman.shadow' version '8.1.1' apply false
id 'de.undercouch.download' version '5.4.0'
id 'me.champeau.jmh' version '0.7.2' apply false
@@ -16,8 +16,6 @@ ext {
javaProjects = subprojects.findAll { !it.name.startsWith("framework-") }
}
description = "Spring Framework"
configure(allprojects) { project ->
apply plugin: "org.springframework.build.localdev"
group = "org.springframework"
@@ -118,3 +116,8 @@ configure([rootProject] + javaProjects) { project ->
configure(moduleProjects) { project ->
apply from: "${rootDir}/gradle/spring-module.gradle"
}
configure(rootProject) {
description = "Spring Framework"
apply plugin: 'org.springframework.build.api-diff'
}
+15
View File
@@ -22,6 +22,21 @@ but doesn't affect the classpath of dependent projects.
This plugin does not provide a `provided` configuration, as the native `compileOnly` and `testCompileOnly`
configurations are preferred.
### API Diff
This plugin uses the [Gradle JApiCmp](https://github.com/melix/japicmp-gradle-plugin) plugin
to generate API Diff reports for each Spring Framework module. This plugin is applied once on the root
project and creates tasks in each framework module. Unlike previous versions of this part of the build,
there is no need for checking out a specific tag. The plugin will fetch the JARs we want to compare the
current working version with. You can generate the reports for all modules or a single module:
```
./gradlew apiDiff -PbaselineVersion=5.1.0.RELEASE
./gradlew :spring-core:apiDiff -PbaselineVersion=5.1.0.RELEASE
```
The reports are located under `build/reports/api-diff/$OLDVERSION_to_$NEWVERSION/`.
### RuntimeHints Java Agent
+6 -1
View File
@@ -21,13 +21,18 @@ dependencies {
checkstyle "io.spring.javaformat:spring-javaformat-checkstyle:${javaFormatVersion}"
implementation "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}"
implementation "org.jetbrains.kotlin:kotlin-compiler-embeddable:${kotlinVersion}"
implementation "org.gradle:test-retry-gradle-plugin:1.5.6"
implementation "me.champeau.gradle:japicmp-gradle-plugin:0.3.0"
implementation "org.gradle:test-retry-gradle-plugin:1.4.1"
implementation "io.spring.javaformat:spring-javaformat-gradle-plugin:${javaFormatVersion}"
implementation "io.spring.nohttp:nohttp-gradle:0.0.11"
}
gradlePlugin {
plugins {
apiDiffPlugin {
id = "org.springframework.build.api-diff"
implementationClass = "org.springframework.build.api.ApiDiffPlugin"
}
conventionsPlugin {
id = "org.springframework.build.conventions"
implementationClass = "org.springframework.build.ConventionsPlugin"
+1 -1
View File
@@ -1,2 +1,2 @@
org.gradle.caching=true
javaFormatVersion=0.0.41
javaFormatVersion=0.0.39
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -50,12 +50,12 @@ public class CheckstyleConventions {
project.getPlugins().apply(CheckstylePlugin.class);
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g"));
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
checkstyle.setToolVersion("10.12.7");
checkstyle.setToolVersion("10.12.5");
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
checkstyleDependencies.add(
project.getDependencies().create("io.spring.javaformat:spring-javaformat-checkstyle:" + version));
checkstyleDependencies
.add(project.getDependencies().create("io.spring.javaformat:spring-javaformat-checkstyle:" + version));
});
}
@@ -0,0 +1,142 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.build.api;
import java.io.File;
import java.net.URI;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
import me.champeau.gradle.japicmp.JapicmpPlugin;
import me.champeau.gradle.japicmp.JapicmpTask;
import org.gradle.api.GradleException;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.jvm.tasks.Jar;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* {@link Plugin} that applies the {@code "japicmp-gradle-plugin"}
* and create tasks for all subprojects named {@code "spring-*"}, diffing the public API one by one
* and creating the reports in {@code "build/reports/api-diff/$OLDVERSION_to_$NEWVERSION/"}.
* <p>{@code "./gradlew apiDiff -PbaselineVersion=5.1.0.RELEASE"} will output the
* reports for the API diff between the baseline version and the current one for all modules.
* You can limit the report to a single module with
* {@code "./gradlew :spring-core:apiDiff -PbaselineVersion=5.1.0.RELEASE"}.
*
* @author Brian Clozel
*/
public class ApiDiffPlugin implements Plugin<Project> {
private static final Logger logger = LoggerFactory.getLogger(ApiDiffPlugin.class);
public static final String TASK_NAME = "apiDiff";
private static final String BASELINE_VERSION_PROPERTY = "baselineVersion";
private static final List<String> PACKAGE_INCLUDES = Collections.singletonList("org.springframework.*");
private static final URI SPRING_MILESTONE_REPOSITORY = URI.create("https://repo.spring.io/milestone");
@Override
public void apply(Project project) {
if (project.hasProperty(BASELINE_VERSION_PROPERTY) && project.equals(project.getRootProject())) {
project.getPluginManager().apply(JapicmpPlugin.class);
project.getPlugins().withType(JapicmpPlugin.class,
plugin -> applyApiDiffConventions(project));
}
}
private void applyApiDiffConventions(Project project) {
String baselineVersion = project.property(BASELINE_VERSION_PROPERTY).toString();
project.subprojects(subProject -> {
if (subProject.getName().startsWith("spring-")) {
createApiDiffTask(baselineVersion, subProject);
}
});
}
private void createApiDiffTask(String baselineVersion, Project project) {
if (isProjectEligible(project)) {
// Add Spring Milestone repository for generating diffs against previous milestones
project.getRootProject()
.getRepositories()
.maven(mavenArtifactRepository -> mavenArtifactRepository.setUrl(SPRING_MILESTONE_REPOSITORY));
JapicmpTask apiDiff = project.getTasks().create(TASK_NAME, JapicmpTask.class);
apiDiff.setDescription("Generates an API diff report with japicmp");
apiDiff.setGroup(JavaBasePlugin.DOCUMENTATION_GROUP);
apiDiff.setOldClasspath(createBaselineConfiguration(baselineVersion, project));
TaskProvider<Jar> jar = project.getTasks().withType(Jar.class).named("jar");
apiDiff.setNewArchives(project.getLayout().files(jar.get().getArchiveFile().get().getAsFile()));
apiDiff.setNewClasspath(getRuntimeClassPath(project));
apiDiff.setPackageIncludes(PACKAGE_INCLUDES);
apiDiff.setOnlyModified(true);
apiDiff.setIgnoreMissingClasses(true);
// Ignore Kotlin metadata annotations since they contain
// illegal HTML characters and fail the report generation
apiDiff.setAnnotationExcludes(Collections.singletonList("@kotlin.Metadata"));
apiDiff.setHtmlOutputFile(getOutputFile(baselineVersion, project));
apiDiff.dependsOn(project.getTasks().getByName("jar"));
}
}
private boolean isProjectEligible(Project project) {
return project.getPlugins().hasPlugin(JavaPlugin.class)
&& project.getPlugins().hasPlugin(MavenPublishPlugin.class);
}
private Configuration createBaselineConfiguration(String baselineVersion, Project project) {
String baseline = String.join(":",
project.getGroup().toString(), project.getName(), baselineVersion);
Dependency baselineDependency = project.getDependencies().create(baseline + "@jar");
Configuration baselineConfiguration = project.getRootProject().getConfigurations().detachedConfiguration(baselineDependency);
try {
// eagerly resolve the baseline configuration to check whether this is a new Spring module
baselineConfiguration.resolve();
return baselineConfiguration;
}
catch (GradleException exception) {
logger.warn("Could not resolve {} - assuming this is a new Spring module.", baseline);
}
return project.getRootProject().getConfigurations().detachedConfiguration();
}
private Configuration getRuntimeClassPath(Project project) {
return project.getConfigurations().getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);
}
private File getOutputFile(String baseLineVersion, Project project) {
String buildDirectoryPath = project.getRootProject()
.getLayout().getBuildDirectory().getAsFile().get().getAbsolutePath();
Path outDir = Paths.get(buildDirectoryPath, "reports", "api-diff",
baseLineVersion + "_to_" + project.getRootProject().getVersion());
return project.file(outDir.resolve(project.getName() + ".html").toString());
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
FROM ubuntu:jammy-20231211.1
FROM ubuntu:jammy-20231004
ADD setup.sh /setup.sh
ADD get-jdk-url.sh /get-jdk-url.sh
+1 -1
View File
@@ -9,7 +9,7 @@ case "$1" in
echo "https://download.bell-sw.com/java/21.0.1+12/bellsoft-jdk21.0.1+12-linux-amd64.tar.gz"
;;
java22)
echo "https://download.java.net/java/early_access/jdk22/28/GPL/openjdk-22-ea+28_linux-x64_bin.tar.gz"
echo "https://download.java.net/java/early_access/jdk22/19/GPL/openjdk-22-ea+19_linux-x64_bin.tar.gz"
;;
*)
echo $"Unknown java version"
-2
View File
@@ -255,8 +255,6 @@ jobs:
plan:
- get: ci-image
- get: git-repo
- get: every-morning
trigger: true
- put: repo-status-jdk22-build
params: { state: "pending", commit: "git-repo" }
- do:
+1 -1
View File
@@ -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,JDK21,JDK22 \
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Porg.gradle.java.installations.fromEnv=JDK17,JDK21 \
-PmainToolchain=${MAIN_TOOLCHAIN} -PtestToolchain=${TEST_TOOLCHAIN} --no-daemon --max-workers=4 check antora
popd > /dev/null
+3 -3
View File
@@ -27,8 +27,8 @@ javadoc {
author = true
header = rootProject.description
use = true
overview = project.relativePath("$rootProject.rootDir/framework-docs/src/docs/api/overview.html")
destinationDir = file("$project.docsDir/javadoc-api")
overview = "$rootProject.rootDir/framework-docs/src/docs/api/overview.html"
destinationDir = file("${project.buildDir}/docs/javadoc-api")
splitIndex = true
links(rootProject.ext.javadocLinks)
addBooleanOption('Xdoclint:syntax,reference', true) // only check syntax and reference with doclint
@@ -52,7 +52,7 @@ rootProject.tasks.dokkaHtmlMultiModule.configure {
tasks.named("javadoc")
}
moduleName.set("spring-framework")
outputDirectory.set(file("$docsDir/kdoc-api"))
outputDirectory.set(project.file("$buildDir/docs/kdoc-api"))
includes.from("$rootProject.rootDir/framework-docs/src/docs/api/dokka-overview.md")
}
@@ -19,53 +19,15 @@ of the classpath -- for example, deployed within the application's JAR file.
The following table lists all currently supported Spring properties.
.Supported Spring Properties
[cols="1,1", stripes=odd]
|===
| Name | Description
| `spring.aot.enabled`
| Indicates the application should run with AOT generated artifacts. See
xref:core/aot.adoc[Ahead of Time Optimizations] and
{spring-framework-api}++/aot/AotDetector.html#AOT_ENABLED++[`AotDetector`]
for details.
| `spring.beaninfo.ignore`
| Instructs Spring to use the `Introspector.IGNORE_ALL_BEANINFO` mode when calling the
JavaBeans `Introspector`. See
{spring-framework-api}++/beans/StandardBeanInfoFactory.html#IGNORE_BEANINFO_PROPERTY_NAME++[`CachedIntrospectionResults`]
for details.
| `spring.cache.reactivestreams.ignore`
| Instructs Spring's caching infrastructure to ignore the presence of Reactive Streams,
in particular Reactor's `Mono`/`Flux` in `@Cacheable` method return type declarations. See
{spring-framework-api}++/cache/interceptor/CacheAspectSupport.html#IGNORE_REACTIVESTREAMS_PROPERTY_NAME++[`CacheAspectSupport`]
for details.
| `spring.classformat.ignore`
| Instructs Spring to ignore class format exceptions during classpath scanning, in
particular for unsupported class file versions. See
{spring-framework-api}++/context/annotation/ClassPathScanningCandidateComponentProvider.html#IGNORE_CLASSFORMAT_PROPERTY_NAME++[`ClassPathScanningCandidateComponentProvider`]
for details.
| `spring.context.checkpoint`
| Property that specifies a common context checkpoint. See
xref:integration/checkpoint-restore.adoc#_automatic_checkpointrestore_at_startup[Automatic
checkpoint/restore at startup] and
{spring-framework-api}++/context/support/DefaultLifecycleProcessor.html#CHECKPOINT_PROPERTY_NAME++[`DefaultLifecycleProcessor`]
for details.
| `spring.context.exit`
| Property for terminating the JVM when the context reaches a specific phase. See
xref:integration/checkpoint-restore.adoc#_automatic_checkpointrestore_at_startup[Automatic
checkpoint/restore at startup] and
{spring-framework-api}++/context/support/DefaultLifecycleProcessor.html#EXIT_PROPERTY_NAME++[`DefaultLifecycleProcessor`]
for details.
| `spring.context.expression.maxLength`
| The maximum length for
xref:core/expressions/evaluation.adoc#expressions-parser-configuration[Spring Expression Language]
expressions used in XML bean definitions, `@Value`, etc.
| `spring.expression.compiler.mode`
| The mode to use when compiling expressions for the
xref:core/expressions/evaluation.adoc#expressions-compiler-configuration[Spring Expression Language].
@@ -291,8 +291,6 @@ to consider:
* `final` classes cannot be proxied, because they cannot be extended.
* `final` methods cannot be advised, because they cannot be overridden.
* `private` methods cannot be advised, because they cannot be overridden.
* Methods that are not visible, typically package private methods in a parent class
from a different package, cannot be advised because they are effectively private.
NOTE: There is no need to add CGLIB to your classpath. CGLIB is repackaged and included
in the `spring-core` JAR. In other words, CGLIB-based AOP works "out of the box", as do
@@ -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
@@ -533,11 +533,6 @@ standard AspectJ. The following example shows the `aop.xml` file:
</aspectj>
----
NOTE: It is recommended to only weave specific classes (typically those in the
application packages, as shown in the `aop.xml` example above) in order
to avoid side effects such as AspectJ dump files and warnings.
This is also a best practice from an efficiency perspective.
Now we can move on to the Spring-specific portion of the configuration. We need
to configure a `LoadTimeWeaver` (explained later). This load-time weaver is the
essential component responsible for weaving the aspect configuration in one or
@@ -719,29 +714,10 @@ Furthermore, the compiled aspect classes need to be available on the classpath.
[[aop-aj-ltw-aop_dot_xml]]
=== `META-INF/aop.xml`
=== 'META-INF/aop.xml'
The AspectJ LTW infrastructure is configured by using one or more `META-INF/aop.xml`
files that are on the Java classpath (either directly or, more typically, in jar files).
For example:
[source,xml,indent=0,subs="verbatim"]
----
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver>
<!-- only weave classes in our application-specific packages -->
<include within="com.xyz.*"/>
</weaver>
</aspectj>
----
NOTE: It is recommended to only weave specific classes (typically those in the
application packages, as shown in the `aop.xml` example above) in order
to avoid side effects such as AspectJ dump files and warnings.
This is also a best practice from an efficiency perspective.
The structure and contents of this file is detailed in the LTW part of the
{aspectj-docs-devguide}/ltw-configuration.html[AspectJ reference
@@ -156,7 +156,6 @@ Java::
/**
* Bean definitions for {@link DataSourceConfiguration}
*/
@Generated
public class DataSourceConfiguration__BeanDefinitions {
/**
* Get the bean definition for 'dataSourceConfiguration'
@@ -191,9 +190,6 @@ Java::
NOTE: The exact code generated may differ depending on the exact nature of your bean definitions.
TIP: Each generated class is annotated with `org.springframework.aot.generate.Generated` to
identify them if they need to be excluded, for instance by static analysis tools.
The generated code above creates bean definitions equivalent to the `@Configuration` class, but in a direct way and without the use of reflection if at all possible.
There is a bean definition for `dataSourceConfiguration` and one for `dataSourceBean`.
When a `datasource` instance is required, a `BeanInstanceSupplier` is called.
@@ -155,8 +155,6 @@ If there is no other resolution indicator (such as a qualifier or a primary mark
for a non-unique dependency situation, Spring matches the injection point name
(that is, the field name or parameter name) against the target bean names and chooses the
same-named candidate, if any.
Since version 6.1, this requires the `-parameters` Java compiler flag to be present.
====
That said, if you intend to express annotation-driven injection by name, do not
@@ -516,8 +516,8 @@ as the following example shows:
[[beans-definition-profiles-default]]
=== Default Profile
The default profile represents the profile that is enabled if no profile is active. Consider
the following example:
The default profile represents the profile that is enabled by default. Consider the
following example:
[tabs]
======
@@ -558,9 +558,9 @@ Kotlin::
----
======
If xref:#beans-definition-profiles-enable[no profile is active], the `dataSource` is
created. You can see this as a way to provide a default definition for one or more
beans. If any profile is enabled, the default profile does not apply.
If no profile is active, the `dataSource` is created. You can see this
as a way to provide a default definition for one or more beans. If any
profile is enabled, the default profile does not apply.
The name of the default profile is `default`. You can change the name of
the default profile by using `setDefaultProfiles()` on the `Environment` or,
@@ -592,40 +592,6 @@ Kotlin::
[[beans-factory-thread-safety]]
=== Thread Safety and Visibility
The Spring core container publishes created singleton instances in a thread-safe manner,
guarding access through a singleton lock and guaranteeing visibility in other threads.
As a consequence, application-provided bean classes do not have to be concerned about the
visibility of their initialization state. Regular configuration fields do not have to be
marked as `volatile` as long as they are only mutated during the initialization phase,
providing visibility guarantees similar to `final` even for setter-based configuration
state that is mutable during that initial phase. If such fields get changed after the
bean creation phase and its subsequent initial publication, they need to be declared as
`volatile` or guarded by a common lock whenever accessed.
Note that concurrent access to such configuration state in singleton bean instances,
e.g. for controller instances or repository instances, is perfectly thread-safe after
such safe initial publication from the container side. This includes common singleton
`FactoryBean` instances which are processed within the general singleton lock as well.
For destruction callbacks, the configuration state remains thread-safe but any runtime
state accumulated between initialization and destruction should be kept in thread-safe
structures (or in `volatile` fields for simple cases) as per common Java guidelines.
Deeper `Lifecycle` integration as shown above involves runtime-mutable state such as
a `runnable` field which will have to be declared as `volatile`. While the common
lifecycle callbacks follow a certain order, e.g. a start callback is guaranteed to
only happen after full initialization and a stop callback only after an initial start,
there is a special case with the common stop before destroy arrangement: It is strongly
recommended that the internal state in any such bean also allows for an immediate
destroy callback without a preceding stop since this may happen during an extraordinary
shutdown after a cancelled bootstrap or in case of a stop timeout caused by another bean.
[[beans-factory-aware]]
== `ApplicationContextAware` and `BeanNameAware`
@@ -324,6 +324,7 @@ Kotlin::
[[beans-factory-scopes-application]]
=== Application Scope
@@ -373,6 +374,7 @@ Kotlin::
[[beans-factory-scopes-websocket]]
=== WebSocket Scope
@@ -382,6 +384,7 @@ xref:web/websocket/stomp/scope.adoc[WebSocket scope] for more details.
[[beans-factory-scopes-other-injection]]
=== Scoped Beans as Dependencies
@@ -541,19 +544,6 @@ see xref:core/aop/proxying.adoc[Proxying Mechanisms].
[[beans-factory-scopes-injection]]
=== Injecting Request/Session References Directly
As an alternative to factory scopes, a Spring `WebApplicationContext` also supports
the injection of `HttpServletRequest`, `HttpServletResponse`, `HttpSession`,
`WebRequest` and (if JSF is present) `FacesContext` and `ExternalContext` into
Spring-managed beans, simply through type-based autowiring next to regular injection
points for other beans. Spring generally injects proxies for such request and session
objects which has the advantage of working in singleton beans and serializable beans
as well, similar to scoped proxies for factory-scoped beans.
[[beans-factory-scopes-custom]]
== Custom Scopes
@@ -1,15 +1,15 @@
[[beans-introduction]]
= Introduction to the Spring IoC Container and Beans
This chapter covers the Spring Framework implementation of the Inversion of Control (IoC)
principle. Dependency injection (DI) is a specialized form of IoC, whereby objects define
their dependencies (that is, the other objects they work with) only through constructor
arguments, arguments to a factory method, or properties that are set on the object
instance after it is constructed or returned from a factory method. The IoC container
This chapter covers the Spring Framework implementation of the Inversion of Control
(IoC) principle. IoC is also known as dependency injection (DI). It is a process whereby
objects define their dependencies (that is, the other objects they work with) only through
constructor arguments, arguments to a factory method, or properties that are set on the
object instance after it is constructed or returned from a factory method. The container
then injects those dependencies when it creates the bean. This process is fundamentally
the inverse (hence the name, Inversion of Control) of the bean itself controlling the
instantiation or location of its dependencies by using direct construction of classes or
a mechanism such as the Service Locator pattern.
the inverse (hence the name, Inversion of Control) of the bean itself
controlling the instantiation or location of its dependencies by using direct
construction of classes or a mechanism such as the Service Locator pattern.
The `org.springframework.beans` and `org.springframework.context` packages are the basis
for Spring Framework's IoC container. The
@@ -3,5 +3,17 @@
:page-section-summary-toc: 1
This section covers how to use annotations in your Java code to configure the Spring
container.
container. It includes the following topics:
* xref:core/beans/java/basic-concepts.adoc[Basic Concepts: `@Bean` and `@Configuration`]
* xref:core/beans/java/instantiating-container.adoc[Instantiating the Spring Container by Using `AnnotationConfigApplicationContext`]
* xref:core/beans/java/bean-annotation.adoc[Using the `@Bean` Annotation]
* xref:core/beans/java/configuration-annotation.adoc[Using the `@Configuration` annotation]
* xref:core/beans/java/composing-configuration-classes.adoc[Composing Java-based Configurations]
* xref:core/beans/environment.adoc#beans-definition-profiles[Bean Definition Profiles]
* xref:core/beans/environment.adoc#beans-property-source-abstraction[`PropertySource` Abstraction]
* xref:core/beans/environment.adoc#beans-using-propertysource[Using `@PropertySource`]
* xref:core/beans/environment.adoc#beans-placeholder-resolution-in-statements[Placeholder Resolution in Statements]
@@ -1,12 +1,12 @@
[[expressions-evaluation]]
= Evaluation
This section introduces programmatic use of SpEL's interfaces and its expression language.
The complete language reference can be found in the
This section introduces the simple use of SpEL interfaces and its expression language.
The complete language reference can be found in
xref:core/expressions/language-ref.adoc[Language Reference].
The following code demonstrates how to use the SpEL API to evaluate the literal string
expression, `Hello World`.
The following code introduces the SpEL API to evaluate the literal string expression,
`Hello World`.
[tabs]
======
@@ -18,7 +18,7 @@ Java::
Expression exp = parser.parseExpression("'Hello World'"); // <1>
String message = (String) exp.getValue();
----
<1> The value of the message variable is `"Hello World"`.
<1> The value of the message variable is `'Hello World'`.
Kotlin::
+
@@ -28,24 +28,24 @@ Kotlin::
val exp = parser.parseExpression("'Hello World'") // <1>
val message = exp.value as String
----
<1> The value of the message variable is `"Hello World"`.
<1> The value of the message variable is `'Hello World'`.
======
The SpEL classes and interfaces you are most likely to use are located in the
`org.springframework.expression` package and its sub-packages, such as `spel.support`.
The `ExpressionParser` interface is responsible for parsing an expression string. In the
preceding example, the expression string is a string literal denoted by the surrounding
single quotation marks. The `Expression` interface is responsible for evaluating the
defined expression string. The two types of exceptions that can be thrown when calling
`parser.parseExpression(...)` and `exp.getValue(...)` are `ParseException` and
`EvaluationException`, respectively.
The `ExpressionParser` interface is responsible for parsing an expression string. In
the preceding example, the expression string is a string literal denoted by the surrounding single
quotation marks. The `Expression` interface is responsible for evaluating the previously defined
expression string. Two exceptions that can be thrown, `ParseException` and
`EvaluationException`, when calling `parser.parseExpression` and `exp.getValue`,
respectively.
SpEL supports a wide range of features such as calling methods, accessing properties,
SpEL supports a wide range of features, such as calling methods, accessing properties,
and calling constructors.
In the following method invocation example, we call the `concat` method on the string
literal, `Hello World`.
In the following example of method invocation, we call the `concat` method on the string literal:
[tabs]
======
@@ -57,7 +57,7 @@ Java::
Expression exp = parser.parseExpression("'Hello World'.concat('!')"); // <1>
String message = (String) exp.getValue();
----
<1> The value of `message` is now `"Hello World!"`.
<1> The value of `message` is now 'Hello World!'.
Kotlin::
+
@@ -67,11 +67,10 @@ Kotlin::
val exp = parser.parseExpression("'Hello World'.concat('!')") // <1>
val message = exp.value as String
----
<1> The value of `message` is now `"Hello World!"`.
<1> The value of `message` is now 'Hello World!'.
======
The following example demonstrates how to access the `Bytes` JavaBean property of the
string literal, `Hello World`.
The following example of calling a JavaBean property calls the `String` property `Bytes`:
[tabs]
======
@@ -101,10 +100,10 @@ Kotlin::
======
SpEL also supports nested properties by using the standard dot notation (such as
`prop1.prop2.prop3`) as well as the corresponding setting of property values.
`prop1.prop2.prop3`) and also the corresponding setting of property values.
Public fields may also be accessed.
The following example shows how to use dot notation to get the length of a string literal.
The following example shows how to use dot notation to get the length of a literal:
[tabs]
======
@@ -134,7 +133,7 @@ Kotlin::
======
The String's constructor can be called instead of using a string literal, as the following
example shows.
example shows:
[tabs]
======
@@ -146,7 +145,7 @@ Java::
Expression exp = parser.parseExpression("new String('hello world').toUpperCase()"); // <1>
String message = exp.getValue(String.class);
----
<1> Construct a new `String` from the literal and convert it to upper case.
<1> Construct a new `String` from the literal and make it be upper case.
Kotlin::
+
@@ -156,9 +155,10 @@ Kotlin::
val exp = parser.parseExpression("new String('hello world').toUpperCase()") // <1>
val message = exp.getValue(String::class.java)
----
<1> Construct a new `String` from the literal and convert it to upper case.
<1> Construct a new `String` from the literal and make it be upper case.
======
Note the use of the generic method: `public <T> T getValue(Class<T> desiredResultType)`.
Using this method removes the need to cast the value of the expression to the desired
result type. An `EvaluationException` is thrown if the value cannot be cast to the
@@ -166,8 +166,8 @@ type `T` or converted by using the registered type converter.
The more common usage of SpEL is to provide an expression string that is evaluated
against a specific object instance (called the root object). The following example shows
how to retrieve the `name` property from an instance of the `Inventor` class and how to
reference the `name` property in a boolean expression.
how to retrieve the `name` property from an instance of the `Inventor` class or
create a boolean condition:
[tabs]
======
@@ -240,7 +240,7 @@ It excludes Java type references, constructors, and bean references. It also req
you to explicitly choose the level of support for properties and methods in expressions.
By default, the `create()` static factory method enables only read access to properties.
You can also obtain a builder to configure the exact level of support needed, targeting
one or some combination of the following.
one or some combination of the following:
* Custom `PropertyAccessor` only (no reflection)
* Data binding properties for read-only access
@@ -252,15 +252,16 @@ one or some combination of the following.
By default, SpEL uses the conversion service available in Spring core
(`org.springframework.core.convert.ConversionService`). This conversion service comes
with many built-in converters for common conversions, but is also fully extensible so
that you can add custom conversions between types. Additionally, it is generics-aware.
This means that, when you work with generic types in expressions, SpEL attempts
conversions to maintain type correctness for any objects it encounters.
with many built-in converters for common conversions but is also fully extensible so that
you can add custom conversions between types. Additionally, it is
generics-aware. This means that, when you work with generic types in
expressions, SpEL attempts conversions to maintain type correctness for any objects
it encounters.
What does this mean in practice? Suppose assignment, using `setValue()`, is being used
to set a `List` property. The type of the property is actually `List<Boolean>`. SpEL
recognizes that the elements of the list need to be converted to `Boolean` before
being placed in it. The following example shows how to do so.
being placed in it. The following example shows how to do so:
[tabs]
======
@@ -324,7 +325,7 @@ constructor before setting the specified value. If the element type does not hav
default constructor, `null` will be added to the array or list. If there is no built-in
or custom converter that knows how to set the value, `null` will remain in the array or
list at the specified index. The following example demonstrates how to automatically grow
the list.
the list:
[tabs]
======
@@ -379,25 +380,16 @@ Kotlin::
----
======
By default, a SpEL expression cannot contain more than 10,000 characters; however, the
`maxExpressionLength` is configurable. If you create a `SpelExpressionParser`
programmatically, you can specify a custom `maxExpressionLength` when creating the
`SpelParserConfiguration` that you provide to the `SpelExpressionParser`. If you wish to
set the `maxExpressionLength` used for parsing SpEL expressions within an
`ApplicationContext` -- for example, in XML bean definitions, `@Value`, etc. -- you can
set a JVM system property or Spring property named `spring.context.expression.maxLength`
to the maximum expression length needed by your application (see
xref:appendix.adoc#appendix-spring-properties[Supported Spring Properties]).
[[expressions-spel-compilation]]
== SpEL Compilation
Spring provides a basic compiler for SpEL expressions. Expressions are usually
interpreted, which provides a lot of dynamic flexibility during evaluation but does not
provide optimum performance. For occasional expression usage, this is fine, but, when
used by other components such as Spring Integration, performance can be very important,
and there is no real need for the dynamism.
Spring Framework 4.1 includes a basic expression compiler. Expressions are usually
interpreted, which provides a lot of dynamic flexibility during evaluation but
does not provide optimum performance. For occasional expression usage,
this is fine, but, when used by other components such as Spring Integration,
performance can be very important, and there is no real need for the dynamism.
The SpEL compiler is intended to address this need. During evaluation, the compiler
generates a Java class that embodies the expression behavior at runtime and uses that
@@ -410,17 +402,16 @@ information can cause trouble later if the types of the various expression eleme
change over time. For this reason, compilation is best suited to expressions whose
type information is not going to change on repeated evaluations.
Consider the following basic expression.
Consider the following basic expression:
[source,java,indent=0,subs="verbatim,quotes"]
----
someArray[0].someProperty.someOtherProperty < 0.1
someArray[0].someProperty.someOtherProperty < 0.1
----
Because the preceding expression involves array access, some property de-referencing, and
numeric operations, the performance gain can be very noticeable. In an example micro
benchmark run of 50,000 iterations, it took 75ms to evaluate by using the interpreter and
only 3ms using the compiled version of the expression.
Because the preceding expression involves array access, some property de-referencing,
and numeric operations, the performance gain can be very noticeable. In an example
micro benchmark run of 50000 iterations, it took 75ms to evaluate by using the
interpreter and only 3ms using the compiled version of the expression.
[[expressions-compiler-configuration]]
@@ -428,34 +419,33 @@ only 3ms using the compiled version of the expression.
The compiler is not turned on by default, but you can turn it on in either of two
different ways. You can turn it on by using the parser configuration process
(xref:core/expressions/evaluation.adoc#expressions-parser-configuration[discussed
earlier]) or by using a Spring property when SpEL usage is embedded inside another
component. This section discusses both of these options.
(xref:core/expressions/evaluation.adoc#expressions-parser-configuration[discussed earlier]) or by using a Spring property
when SpEL usage is embedded inside another component. This section discusses both of
these options.
The compiler can operate in one of three modes, which are captured in the
`org.springframework.expression.spel.SpelCompilerMode` enum. The modes are as follows.
`org.springframework.expression.spel.SpelCompilerMode` enum. The modes are as follows:
* `OFF` (default): The compiler is switched off.
* `IMMEDIATE`: In immediate mode, the expressions are compiled as soon as possible. This
is typically after the first interpreted evaluation. If the compiled expression fails
(typically due to a type changing, as described earlier), the caller of the expression
evaluation receives an exception.
* `MIXED`: In mixed mode, the expressions silently switch between interpreted and
compiled mode over time. After some number of interpreted runs, they switch to compiled
form and, if something goes wrong with the compiled form (such as a type changing, as
described earlier), the expression automatically switches back to interpreted form
again. Sometime later, it may generate another compiled form and switch to it.
Basically, the exception that the user gets in `IMMEDIATE` mode is instead handled
internally.
is typically after the first interpreted evaluation. If the compiled expression fails
(typically due to a type changing, as described earlier), the caller of the expression
evaluation receives an exception.
* `MIXED`: In mixed mode, the expressions silently switch between interpreted and compiled
mode over time. After some number of interpreted runs, they switch to compiled
form and, if something goes wrong with the compiled form (such as a type changing, as
described earlier), the expression automatically switches back to interpreted form
again. Sometime later, it may generate another compiled form and switch to it. Basically,
the exception that the user gets in `IMMEDIATE` mode is instead handled internally.
`IMMEDIATE` mode exists because `MIXED` mode could cause issues for expressions that
have side effects. If a compiled expression blows up after partially succeeding, it
may have already done something that has affected the state of the system. If this
has happened, the caller may not want it to silently re-run in interpreted mode,
since part of the expression may be run twice.
since part of the expression may be running twice.
After selecting a mode, use the `SpelParserConfiguration` to configure the parser. The
following example shows how to do so.
following example shows how to do so:
[tabs]
======
@@ -492,16 +482,15 @@ Kotlin::
----
======
When you specify the compiler mode, you can also specify a `ClassLoader` (passing `null`
is allowed). Compiled expressions are defined in a child `ClassLoader` created under any
that is supplied. It is important to ensure that, if a `ClassLoader` is specified, it can
see all the types involved in the expression evaluation process. If you do not specify a
`ClassLoader`, a default `ClassLoader` is used (typically the context `ClassLoader` for
the thread that is running during expression evaluation).
When you specify the compiler mode, you can also specify a classloader (passing null is allowed).
Compiled expressions are defined in a child classloader created under any that is supplied.
It is important to ensure that, if a classloader is specified, it can see all the types involved in
the expression evaluation process. If you do not specify a classloader, a default classloader is used
(typically the context classloader for the thread that is running during expression evaluation).
The second way to configure the compiler is for use when SpEL is embedded inside some
other component and it may not be possible to configure it through a configuration
object. In such cases, it is possible to set the `spring.expression.compiler.mode`
object. In these cases, it is possible to set the `spring.expression.compiler.mode`
property via a JVM system property (or via the
xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism) to one of the
`SpelCompilerMode` enum values (`off`, `immediate`, or `mixed`).
@@ -510,14 +499,18 @@ xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism) to
[[expressions-compiler-limitations]]
=== Compiler Limitations
Spring does not support compiling every kind of expression. The primary focus is on
common expressions that are likely to be used in performance-critical contexts. The
following kinds of expressions cannot be compiled.
Since Spring Framework 4.1, the basic compilation framework is in place. However, the framework
does not yet support compiling every kind of expression. The initial focus has been on the
common expressions that are likely to be used in performance-critical contexts. The following
kinds of expression cannot be compiled at the moment:
* Expressions involving assignment
* Expressions relying on the conversion service
* Expressions using custom resolvers or accessors
* Expressions using selection or projection
Compilation of additional kinds of expressions may be supported in the future.
More types of expressions will be compilable in the future.
@@ -2,4 +2,24 @@
= Language Reference
:page-section-summary-toc: 1
This section describes how the Spring Expression Language works.
This section describes how the Spring Expression Language works. It covers the following
topics:
* xref:core/expressions/language-ref/literal.adoc[Literal Expressions]
* xref:core/expressions/language-ref/properties-arrays.adoc[Properties, Arrays, Lists, Maps, and Indexers]
* xref:core/expressions/language-ref/inline-lists.adoc[Inline Lists]
* xref:core/expressions/language-ref/inline-maps.adoc[Inline Maps]
* xref:core/expressions/language-ref/array-construction.adoc[Array Construction]
* xref:core/expressions/language-ref/methods.adoc[Methods]
* xref:core/expressions/language-ref/operators.adoc[Operators]
* xref:core/expressions/language-ref/types.adoc[Types]
* xref:core/expressions/language-ref/constructors.adoc[Constructors]
* xref:core/expressions/language-ref/variables.adoc[Variables]
* xref:core/expressions/language-ref/functions.adoc[User-Defined Functions]
* xref:core/expressions/language-ref/bean-references.adoc[Bean References]
* xref:core/expressions/language-ref/operator-ternary.adoc[Ternary Operator (If-Then-Else)]
* xref:core/expressions/language-ref/operator-elvis.adoc[The Elvis Operator]
* xref:core/expressions/language-ref/operator-safe-navigation.adoc[Safe Navigation Operator]
@@ -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]
@@ -1,8 +1,8 @@
[[expressions-ref-variables]]
= Variables
You can reference variables in an expression by using the `#variableName` syntax. Variables
are set by using the `setVariable()` method in `EvaluationContext` implementations.
You can reference variables in the expression by using the `#variableName` syntax. Variables
are set by using the `setVariable` method on `EvaluationContext` implementations.
[NOTE]
====
@@ -29,7 +29,7 @@ Java::
context.setVariable("newName", "Mike Tesla");
parser.parseExpression("name = #newName").getValue(context, tesla);
System.out.println(tesla.getName()); // "Mike Tesla"
System.out.println(tesla.getName()) // "Mike Tesla"
----
Kotlin::
@@ -53,10 +53,8 @@ Kotlin::
The `#this` variable is always defined and refers to the current evaluation object
(against which unqualified references are resolved). The `#root` variable is always
defined and refers to the root context object. Although `#this` may vary as components of
an expression are evaluated, `#root` always refers to the root.
The following example shows how to use the `#this` variable in conjunction with
xref:core/expressions/language-ref/collection-selection.adoc[collection selection].
an expression are evaluated, `#root` always refers to the root. The following examples
show how to use the `#this` and `#root` variables:
[tabs]
======
@@ -64,95 +62,40 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
// Create a list of prime integers.
List<Integer> primes = List.of(2, 3, 5, 7, 11, 13, 17);
// create an array of integers
List<Integer> primes = new ArrayList<>();
primes.addAll(Arrays.asList(2,3,5,7,11,13,17));
// Create parser and set variable 'primes' as the list of integers.
// create parser and set variable 'primes' as the array of integers
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataAccess();
context.setVariable("primes", primes);
// Select all prime numbers > 10 from the list (using selection ?{...}).
String expression = "#primes.?[#this > 10]";
// Evaluates to a list containing [11, 13, 17].
List<Integer> primesGreaterThanTen =
parser.parseExpression(expression).getValue(context, List.class);
// all prime numbers > 10 from the list (using selection ?{...})
// evaluates to [11, 13, 17]
List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression(
"#primes.?[#this>10]").getValue(context);
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
// Create a list of prime integers.
val primes = listOf(2, 3, 5, 7, 11, 13, 17)
// create an array of integers
val primes = ArrayList<Int>()
primes.addAll(listOf(2, 3, 5, 7, 11, 13, 17))
// Create parser and set variable 'primes' as the list of integers.
// create parser and set variable 'primes' as the array of integers
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadWriteDataBinding().build()
val context = SimpleEvaluationContext.forReadOnlyDataAccess()
context.setVariable("primes", primes)
// Select all prime numbers > 10 from the list (using selection ?{...}).
val expression = "#primes.?[#this > 10]"
// Evaluates to a list containing [11, 13, 17].
val primesGreaterThanTen = parser.parseExpression(expression)
.getValue(context) as List<Int>
// all prime numbers > 10 from the list (using selection ?{...})
// evaluates to [11, 13, 17]
val primesGreaterThanTen = parser.parseExpression(
"#primes.?[#this>10]").getValue(context) as List<Int>
----
======
The following example shows how to use the `#this` and `#root` variables together in
conjunction with
xref:core/expressions/language-ref/collection-projection.adoc[collection projection].
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
// Create parser and evaluation context.
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
// Create an inventor to use as the root context object.
Inventor tesla = new Inventor("Nikola Tesla");
tesla.setInventions("Telephone repeater", "Tesla coil transformer");
// Iterate over all inventions of the Inventor referenced as the #root
// object, and generate a list of strings whose contents take the form
// "<inventor's name> invented the <invention>." (using projection !{...}).
String expression = "#root.inventions.![#root.name + ' invented the ' + #this + '.']";
// Evaluates to a list containing:
// "Nikola Tesla invented the Telephone repeater."
// "Nikola Tesla invented the Tesla coil transformer."
List<String> results = parser.parseExpression(expression)
.getValue(context, tesla, List.class);
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
// Create parser and evaluation context.
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadWriteDataBinding().build()
// Create an inventor to use as the root context object.
val tesla = Inventor("Nikola Tesla")
tesla.setInventions("Telephone repeater", "Tesla coil transformer")
// Iterate over all inventions of the Inventor referenced as the #root
// object, and generate a list of strings whose contents take the form
// "<inventor's name> invented the <invention>." (using projection !{...}).
val expression = "#root.inventions.![#root.name + ' invented the ' + #this + '.']"
// Evaluates to a list containing:
// "Nikola Tesla invented the Telephone repeater."
// "Nikola Tesla invented the Tesla coil transformer."
val results = parser.parseExpression(expression)
.getValue(context, tesla, List::class.java)
----
======
@@ -230,19 +230,6 @@ provided you stick to the required connection lookup pattern. Note that JTA does
savepoints or custom isolation levels and has a different timeout mechanism but otherwise
exposes similar behavior in terms of JDBC resources and JDBC commit/rollback management.
For JTA-style lazy retrieval of actual resource connections, Spring provides a
corresponding `DataSource` proxy class for the target connection pool: see
{spring-framework-api}/jdbc/datasource/LazyConnectionDataSourceProxy.html[`LazyConnectionDataSourceProxy`].
This is particularly useful for potentially empty transactions without actual statement
execution (never fetching an actual resource in such a scenario), and also in front of
a routing `DataSource` which means to take the transaction-synchronized read-only flag
and/or isolation level into account (e.g. `IsolationLevelDataSourceRouter`).
`LazyConnectionDataSourceProxy` also provides special support for a read-only connection
pool to use during a read-only transaction, avoiding the overhead of switching the JDBC
Connection's read-only flag at the beginning and end of every transaction when fetching
it from the primary connection pool (which may be costly depending on the JDBC driver).
NOTE: As of 5.3, Spring provides an extended `JdbcTransactionManager` variant which adds
exception translation capabilities on commit/rollback (aligned with `JdbcTemplate`).
Where `DataSourceTransactionManager` will only ever throw `TransactionSystemException`
@@ -407,12 +407,6 @@ exposes the Hibernate transaction as a JDBC transaction if you have set up the p
`DataSource` for which the transactions are supposed to be exposed through the
`dataSource` property of the `HibernateTransactionManager` class.
For JTA-style lazy retrieval of actual resource connections, Spring provides a
corresponding `DataSource` proxy class for the target connection pool: see
{spring-framework-api}/jdbc/datasource/LazyConnectionDataSourceProxy.html[`LazyConnectionDataSourceProxy`].
This is particularly useful for Hibernate read-only transactions which can often
be processed from a local cache rather than hitting the database.
[[orm-hibernate-resources]]
== Comparing Container-managed and Locally Defined Resources
@@ -506,20 +506,13 @@ if you have not already done so, to get more detailed coverage of Spring's decla
The recommended strategy for JPA is local transactions through JPA's native transaction
support. Spring's `JpaTransactionManager` provides many capabilities known from local
JDBC transactions (such as transaction-specific isolation levels and resource-level
read-only optimizations) against any regular JDBC connection pool, without requiring
a JTA transaction coordinator and XA-capable resources.
read-only optimizations) against any regular JDBC connection pool (no XA requirement).
Spring JPA also lets a configured `JpaTransactionManager` expose a JPA transaction
to JDBC access code that accesses the same `DataSource`, provided that the registered
`JpaDialect` supports retrieval of the underlying JDBC `Connection`. Spring provides
dialects for the EclipseLink and Hibernate JPA implementations. See the
xref:data-access/orm/jpa.adoc#orm-jpa-dialect[next section] for details on `JpaDialect`.
For JTA-style lazy retrieval of actual resource connections, Spring provides a
corresponding `DataSource` proxy class for the target connection pool: see
{spring-framework-api}/jdbc/datasource/LazyConnectionDataSourceProxy.html[`LazyConnectionDataSourceProxy`].
This is particularly useful for JPA read-only transactions which can often
be processed from a local cache rather than hitting the database.
`JpaDialect` supports retrieval of the underlying JDBC `Connection`.
Spring provides dialects for the EclipseLink and Hibernate JPA implementations.
See the xref:data-access/orm/jpa.adoc#orm-jpa-dialect[next section] for details on the `JpaDialect` mechanism.
[[orm-jpa-dialect]]
@@ -440,8 +440,12 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val tuples: MutableList<Array<Any>> = ArrayList()
tuples.add(arrayOf("John", 35))
tuples.add(arrayOf("Ann", 50))
client.sql("SELECT id, name, state FROM table WHERE age IN (:ages)")
.bind("ages", arrayOf(35, 50))
.bind("tuples", arrayOf(35, 50))
----
======
@@ -38,11 +38,6 @@ within the method.
A reactive transaction managed by `ReactiveTransactionManager` uses the Reactor context
instead of thread-local attributes. As a consequence, all participating data access
operations need to execute within the same Reactor context in the same reactive pipeline.
When configured with a `ReactiveTransactionManager`, all transaction-demarcated methods
are expected to return a reactive pipeline. Void methods or regular return types need
to be associated with a regular `PlatformTransactionManager`, e.g. through the
`transactionManager` attribute of the corresponding `@Transactional` declarations.
====
The following image shows a conceptual view of calling a method on a transactional proxy:
+1 -1
View File
@@ -29,7 +29,7 @@ Brannen, Ramnivas Laddad, Arjen Poutsma, Chris Beams, Tareq Abedrabbo, Andy Clem
Syer, Oliver Gierke, Rossen Stoyanchev, Phillip Webb, Rob Winch, Brian Clozel, Stephane
Nicoll, Sebastien Deleuze, Jay Bryant, Mark Paluch
Copyright © 2002 - 2024 VMware, Inc. All Rights Reserved.
Copyright © 2002 - 2023 VMware, Inc. All Rights Reserved.
Copies of this document may be made for your own use and for distribution to others,
provided that you do not charge any fee for such copies and further provided that each
@@ -15,7 +15,8 @@ Conceptually, checkpoint and restore align with the xref:core/beans/factory-natu
== On-demand checkpoint/restore of a running application
A checkpoint can be created on demand, for example using a command like `jcmd application.jar JDK.checkpoint`. Before the creation of the checkpoint, Spring stops all the running beans, giving them a chance to close resources if needed by implementing `Lifecycle.stop`. After restore, the same beans are restarted, with `Lifecycle.start` allowing beans to reopen resources when relevant. For libraries that do not depend on Spring, custom checkpoint/restore integration can be provided by implementing `org.crac.Resource` and registering the related instance.
A checkpoint can be created on demand, for example using a command like `jcmd application.jar JDK.checkpoint`. Before the creation of the checkpoint, Spring Framework
stops all the running beans, giving them a chance to close resources if needed by implementing `Lifecycle.stop`. After restore, the same beans are restarted, with `Lifecycle.start` allowing beans to reopen resources when relevant. For libraries that do not depend on Spring, custom checkpoint/restore integration can be provided by implementing `org.crac.Resource` and registering the related instance.
WARNING: Leveraging checkpoint/restore of a running application typically requires additional lifecycle management to gracefully stop and start using resources like files or sockets and stop active threads.
@@ -28,11 +29,6 @@ startup during the `LifecycleProcessor.onRefresh` phase. After this phase has co
`InitializingBean#afterPropertiesSet` callbacks have been invoked; but the lifecycle has not started, and the
`ContextRefreshedEvent` has not yet been published.
For testing purposes, it is also possible to leverage the `-Dspring.context.exit=onRefresh` JVM system property which
triggers similar behavior, but instead of creating a checkpoint, it exits your Spring application at the same lifecycle
phase without requiring the Project CraC dependency/JVM or Linux. This can be useful to check if connections to remote
services are required when the beans are not started, and potentially refine the configuration to avoid that.
WARNING: As mentioned above, and especially in use cases where the CRaC files are shipped as part of a deployable artifact (a container image for example), operate with the assumption that any sensitive data "seen" by the JVM ends up in the CRaC files, and assess carefully the related security implications.
NOTE: Automatic checkpoint/restore is a way to "fast-forward" the startup of the application to a phase where the application context is about to start, but it does not allow to have a fully warmed-up JVM.
@@ -19,7 +19,7 @@ been published.
To create the archive, two additional JVM flags must be specified:
* `-XX:ArchiveClassesAtExit=application.jsa`: creates the CDS archive on exit
* `-XX:ArchiveClassesAtExit=app-cds.jsa`: creates the CDS archive on exit
* `-Dspring.context.exit=onRefresh`: starts and then immediately exits your Spring
application as described above
@@ -40,8 +40,8 @@ The base CDS archive can be created by issuing the following command:
== Using the Archive
Once the archive is available, add `-XX:SharedArchiveFile=application.jsa` to your startup
script to use it, assuming an `application.jsa` file in the working directory.
Once the archive is available, add `-XX:SharedArchiveFile=app-cds.jsa` to your startup
script to use it, assuming a `app-cds.jsa` file in the working directory.
To figure out how effective the cache is, you can enable class loading logs by adding
an extra attribute: `-Xlog:class+load:file=cds.log`. This creates a `cds.log` with every
@@ -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
|`code.function` _(required)_|Name of Java `Method` that is scheduled for execution.
|`code.namespace` _(required)_|Canonical name of the class of the bean instance that holds the scheduled method, or `"ANONYMOUS"` for anonymous classes.
|`code.namespace` _(required)_|Canonical name of the class of the bean instance that holds the scheduled method.
|`error` _(required)_|Class name of the exception thrown during the execution, or `"none"` if no exception happened.
|`exception` _(deprecated)_|Duplicates the `error` key and might be removed in the future.
|`outcome` _(required)_|Outcome of the method execution. Can be `"SUCCESS"`, `"ERROR"` or `"UNKNOWN"` (if for example the operation was cancelled during execution).
@@ -906,8 +906,8 @@ For `WebClient`:
[source,java,indent=0,subs="verbatim,quotes"]
----
WebClient webClient = WebClient.builder().baseUrl("https://api.github.com/").build();
WebClientAdapter adapter = WebClientAdapter.create(webClient);
WebClient client = WebClient.builder().baseUrl("https://api.github.com/").build();
WebClientAdapter adapter = WebClientAdapter.forClient(webClient)
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
RepositoryService service = factory.createClient(RepositoryService.class);
@@ -1078,34 +1078,11 @@ underlying HTTP client, which operates at a lower level and provides more contro
[[rest-http-interface-exceptions]]
=== Error Handling
=== Exception Handling
To customize error response handling, you need to configure the underlying HTTP client.
For `RestClient`:
By default, `RestClient` raises `RestClientException` for 4xx and 5xx HTTP status codes.
To customize this, register a response status handler that applies to all responses
performed through the client:
[source,java,indent=0,subs="verbatim,quotes"]
----
RestClient restClient = RestClient.builder()
.defaultStatusHandler(HttpStatusCode::isError, (request, response) -> ...)
.build();
RestClientAdapter adapter = RestClientAdapter.create(restClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
----
For more details and options, such as suppressing error status codes, see the Javadoc of
`defaultStatusHandler` in `RestClient.Builder`.
For `WebClient`:
By default, `WebClient` raises `WebClientResponseException` for 4xx and 5xx HTTP status codes.
To customize this, register a response status handler that applies to all responses
performed through the client:
By default, `WebClient` raises `WebClientResponseException` for 4xx and 5xx HTTP status
codes. To customize this, you can register a response status handler that applies to all
responses performed through the client:
[source,java,indent=0,subs="verbatim,quotes"]
----
@@ -1113,28 +1090,10 @@ performed through the client:
.defaultStatusHandler(HttpStatusCode::isError, resp -> ...)
.build();
WebClientAdapter adapter = WebClientAdapter.create(webClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(adapter).build();
WebClientAdapter clientAdapter = WebClientAdapter.forClient(webClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory
.builder(clientAdapter).build();
----
For more details and options, such as suppressing error status codes, see the Javadoc of
`defaultStatusHandler` in `WebClient.Builder`.
For `RestTemplate`:
By default, `RestTemplate` raises `RestClientException` for 4xx and 5xx HTTP status codes.
To customize this, register an error handler that applies to all responses
performed through the client:
[source,java,indent=0,subs="verbatim,quotes"]
----
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(myErrorHandler);
RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
----
For more details and options, see the Javadoc of `setErrorHandler` in `RestTemplate` and
the `ResponseErrorHandler` hierarchy.
@@ -252,9 +252,7 @@ application server environments, as well -- in particular on Tomcat and Jetty.
As of 6.1, `ThreadPoolTaskScheduler` provides a pause/resume capability and graceful
shutdown through Spring's lifecycle management. There is also a new option called
`SimpleAsyncTaskScheduler` which is aligned with JDK 21's Virtual Threads, using a
single scheduler thread but firing up a new thread for every scheduled task execution
(except for fixed-delay tasks which all operate on a single scheduler thread, so for
this virtual-thread-aligned option, fixed rates and cron triggers are recommended).
single scheduler thread but firing up a new thread for every scheduled task execution.
@@ -479,12 +477,12 @@ the framework to invoke a suspending function as a `Publisher`.
The Spring Framework will obtain a `Publisher` for the annotated method once and will
schedule a `Runnable` in which it subscribes to said `Publisher`. These inner regular
subscriptions occur according to the corresponding `cron`/`fixedDelay`/`fixedRate` configuration.
subscriptions occur according to the corresponding `cron`/fixedDelay`/`fixedRate` configuration.
If the `Publisher` emits `onNext` signal(s), these are ignored and discarded (the same way
return values from synchronous `@Scheduled` methods are ignored).
In the following example, the `Flux` emits `onNext("Hello")`, `onNext("World")` every 5
In the following example, the `Flux` emits `onNext("Hello"), onNext("World")` every 5
seconds, but these values are unused:
[source,java,indent=0,subs="verbatim,quotes"]
@@ -13,7 +13,7 @@ Most of the code samples of the reference documentation are
provided in Kotlin in addition to Java.
The easiest way to build a Spring application with Kotlin is to leverage Spring Boot and
its {spring-boot-docs}/boot-features-kotlin.html[dedicated Kotlin support].
its{spring-boot-docs}/boot-features-kotlin.html[dedicated Kotlin support].
{spring-site-guides}/tutorials/spring-boot-kotlin/[This comprehensive tutorial]
will teach you how to build Spring Boot applications with Kotlin using https://start.spring.io/#!language=kotlin&type=gradle-project[start.spring.io].
@@ -107,7 +107,7 @@ NOTE: Spring Boot is based on JavaConfig and
{spring-boot-issues}/8115[does not yet provide specific support for functional bean definition],
but you can experimentally use functional bean definitions through Spring Boot's `ApplicationContextInitializer` support.
See {stackoverflow-questions}/45935931/how-to-use-functional-bean-definition-kotlin-dsl-with-spring-boot-and-spring-w/46033685#46033685[this Stack Overflow answer]
for more details and up-to-date information. See also the experimental Kofu DSL developed in {spring-github-org}-experimental/spring-fu[Spring Fu incubator].
for more details and up-to-date information. See also the experimental Kofu DSL developed in {spring-github-org}/spring-fu[Spring Fu incubator].
@@ -14,10 +14,8 @@ Spring Framework provides support for Coroutines on the following scope:
* Suspending function support in Spring MVC and WebFlux annotated `@Controller`
* Extensions for WebFlux {spring-framework-api-kdoc}/spring-webflux/org.springframework.web.reactive.function.client/index.html[client] and {spring-framework-api-kdoc}/spring-webflux/org.springframework.web.reactive.function.server/index.html[server] functional API.
* WebFlux.fn {spring-framework-api-kdoc}/spring-webflux/org.springframework.web.reactive.function.server/co-router.html[coRouter { }] DSL
* WebFlux {spring-framework-api-kdoc}/spring-web/org.springframework.web.server/-co-web-filter/index.html[`CoWebFilter`]
* Suspending function and `Flow` support in RSocket `@MessageMapping` annotated methods
* Extensions for {spring-framework-api-kdoc}/spring-messaging/org.springframework.messaging.rsocket/index.html[`RSocketRequester`]
* Spring AOP
@@ -10,21 +10,22 @@ The easiest way to learn how to build a Spring application with Kotlin is to fol
== `start.spring.io`
The easiest way to start a new Spring Framework project in Kotlin is to create a new Spring
Boot project on https://start.spring.io/#!language=kotlin&type=gradle-project-kotlin[start.spring.io].
Boot 2 project on https://start.spring.io/#!language=kotlin&type=gradle-project[start.spring.io].
[[choosing-the-web-flavor]]
== Choosing the Web Flavor
Spring Framework comes with two different web stacks: xref:web/webmvc.adoc#mvc[Spring MVC] and
Spring Framework now comes with two different web stacks: xref:web/webmvc.adoc#mvc[Spring MVC] and
xref:testing/unit.adoc#mock-objects-web-reactive[Spring WebFlux].
Spring WebFlux is recommended if you want to create applications that will deal with latency,
long-lived connections or streaming scenarios.
long-lived connections, streaming scenarios or if you want to use the web functional
Kotlin DSL.
For other use cases, especially if you are using blocking technologies such as JPA, Spring
MVC is the recommended choice.
MVC and its annotation-based programming model is the recommended choice.
@@ -2,12 +2,15 @@
= Requirements
:page-section-summary-toc: 1
Spring Framework supports Kotlin 1.7+ and requires
Spring Framework supports Kotlin 1.3+ and requires
https://search.maven.org/artifact/org.jetbrains.kotlin/kotlin-stdlib[`kotlin-stdlib`]
(or one of its variants, such as https://search.maven.org/artifact/org.jetbrains.kotlin/kotlin-stdlib-jdk8[`kotlin-stdlib-jdk8`])
and https://search.maven.org/artifact/org.jetbrains.kotlin/kotlin-reflect[`kotlin-reflect`]
to be present on the classpath. They are provided by default if you bootstrap a Kotlin project on
https://start.spring.io/#!language=kotlin&type=gradle-project[start.spring.io].
WARNING: Kotlin {kotlin-docs}/inline-classes.html[inline classes] are not yet supported.
NOTE: The {jackson-github-org}/jackson-module-kotlin[Jackson Kotlin module] is required
for serializing or deserializing JSON data for Kotlin classes with Jackson, so make sure to add the
`com.fasterxml.jackson.module:jackson-module-kotlin` dependency to your project if you have such need.
@@ -18,9 +18,28 @@ Kotlin and the Spring Framework:
The following Github projects offer examples that you can learn from and possibly even extend:
* https://github.com/spring-guides/tut-spring-boot-kotlin[tut-spring-boot-kotlin]: Sources of {spring-site}/guides/tutorials/spring-boot-kotlin/[the official Spring + Kotlin tutorial]
* https://github.com/sdeleuze/spring-boot-kotlin-demo[spring-boot-kotlin-demo]: Regular Spring Boot and Spring Data JPA project
* https://github.com/mixitconf/mixit[mixit]: Spring Boot, WebFlux, and Reactive Spring Data MongoDB
* https://github.com/mixitconf/mixit[mixit]: Spring Boot 2, WebFlux, and Reactive Spring Data MongoDB
* https://github.com/sdeleuze/spring-kotlin-functional[spring-kotlin-functional]: Standalone WebFlux and functional bean definition DSL
* https://github.com/sdeleuze/spring-kotlin-fullstack[spring-kotlin-fullstack]: WebFlux Kotlin fullstack example with Kotlin2js for frontend instead of JavaScript or TypeScript
* https://github.com/spring-petclinic/spring-petclinic-kotlin[spring-petclinic-kotlin]: Kotlin version of the Spring PetClinic Sample Application
* https://github.com/sdeleuze/spring-kotlin-deepdive[spring-kotlin-deepdive]: A step-by-step migration guide for Boot 1.0 and Java to Boot 2.0 and Kotlin
* https://github.com/spring-cloud/spring-cloud-gcp/tree/master/spring-cloud-gcp-kotlin-samples/spring-cloud-gcp-kotlin-app-sample[spring-cloud-gcp-kotlin-app-sample]: Spring Boot with Google Cloud Platform Integrations
[[issues]]
== Issues
The following list categorizes the pending issues related to Spring and Kotlin support:
* Spring Framework
** {spring-framework-issues}/20606[Unable to use WebTestClient with mock server in Kotlin]
** {spring-framework-issues}/20496[Support null-safety at generics, varargs and array elements level]
* Kotlin
** {kotlin-issues}/KT-6380[Parent issue for Spring Framework support]
** {kotlin-issues}/KT-5464[Kotlin requires type inference where Java doesn't]
** {kotlin-issues}/KT-20283[Smart cast regression with open classes]
** {kotlin-issues}/KT-14984[Impossible to pass not all SAM argument as function]
** {kotlin-issues}/KT-15125[Support JSR 223 bindings directly via script variables]
** {kotlin-issues}/KT-6653[Kotlin properties do not override Java-style getters and setters]
@@ -97,9 +97,6 @@ does not require the `kotlin-noarg` plugin if the module uses Spring Data object
[[injecting-dependencies]]
== Injecting Dependencies
[[favor-constructor-injection]]
=== Favor constructor injection
Our recommendation is to try to favor constructor injection with `val` read-only (and
non-nullable when possible) {kotlin-docs}/properties.html[properties],
as the following example shows:
@@ -133,41 +130,7 @@ as the following example shows:
}
----
[[internal-functions-name-mangling]]
=== Internal functions name mangling
Kotlin functions with the `internal` {kotlin-docs}/visibility-modifiers.html#class-members[visibility modifier] have
their names mangled when compiled to JVM bytecode, which has a side effect when injecting dependencies by name.
For example, this Kotlin class:
[source,kotlin,indent=0]
----
@Configuration
class SampleConfiguration {
@Bean
internal fun sampleBean() = SampleBean()
}
----
Translates to this Java representation of the compiled JVM bytecode:
[source,java,indent=0]
----
@Configuration
@Metadata(/* ... */)
public class SampleConfiguration {
@Bean
@NotNull
public SampleBean sampleBean$demo_kotlin_internal_test() {
return new SampleBean();
}
}
----
As a consequence, the related bean name represented as a Kotlin string is `"sampleBean\$demo_kotlin_internal_test"`,
instead of `"sampleBean"` for the regular `public` function use-case. Make sure to use the mangled name when injecting
such bean by name.
[[injecting-configuration-properties]]
== Injecting Configuration Properties
@@ -8,9 +8,9 @@
Spring Framework comes with a Kotlin router DSL available in 3 flavors:
* xref:web/webmvc-functional.adoc[WebMvc.fn DSL] with {spring-framework-api-kdoc}/spring-webmvc/org.springframework.web.servlet.function/router.html[router { }]
* xref:web/webflux-functional.adoc[WebFlux.fn Reactive DSL] with {spring-framework-api-kdoc}/spring-webflux/org.springframework.web.reactive.function.server/router.html[router { }]
* xref:languages/kotlin/coroutines.adoc[WebFlux.fn Coroutines DSL] with {spring-framework-api-kdoc}/spring-webflux/org.springframework.web.reactive.function.server/co-router.html[coRouter { }]
* WebMvc.fn DSL with {spring-framework-api-kdoc}/spring-webmvc/org.springframework.web.servlet.function/router.html[router { }]
* WebFlux.fn <<web-reactive#webflux-fn, Reactive>> DSL with {spring-framework-api-kdoc}/spring-webflux/org.springframework.web.reactive.function.server/router.html[router { }]
* WebFlux.fn <<Coroutines>> DSL with {spring-framework-api-kdoc}/spring-webflux/org.springframework.web.reactive.function.server/co-router.html[coRouter { }]
These DSL let you write clean and idiomatic Kotlin code to build a `RouterFunction` instance as the following example shows:
@@ -126,7 +126,7 @@ project for more details.
[[kotlin-multiplatform-serialization]]
== Kotlin multiplatform serialization
{kotlin-github-org}/kotlinx.serialization[Kotlin multiplatform serialization] is
As of Spring Framework 5.3, {kotlin-github-org}/kotlinx.serialization[Kotlin multiplatform serialization] is
supported in Spring MVC, Spring WebFlux and Spring Messaging (RSocket). The builtin support currently targets CBOR, JSON, and ProtoBuf formats.
To enable it, follow {kotlin-github-org}/kotlinx.serialization#setup[those instructions] to add the related dependency and plugin.
@@ -45,7 +45,7 @@ xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism.
[NOTE]
====
The `@ContextHierarchy` annotation is not supported in AOT mode.
The `@ContextHierarchy` annotation is currently not supported in AOT mode.
====
To provide test-specific runtime hints for use within a GraalVM native image, you have
@@ -42,15 +42,13 @@ become cumbersome if a custom factory needs to be used across an entire test sui
issue is addressed through support for automatic discovery of default
`ContextCustomizerFactory` implementations through the `SpringFactoriesLoader` mechanism.
For example, the modules that make up the testing support in Spring Framework and Spring
Specifically, the modules that make up the testing support in Spring Framework and Spring
Boot declare all core default `ContextCustomizerFactory` implementations under the
`org.springframework.test.context.ContextCustomizerFactory` key in their
`META-INF/spring.factories` properties files. The `spring.factories` file for the
`spring-test` module can be viewed
{spring-framework-code}/spring-test/src/main/resources/META-INF/spring.factories[here].
Third-party frameworks and developers can contribute their own `ContextCustomizerFactory`
implementations to the list of default factories in the same manner through their own
`spring.factories` files.
`META-INF/spring.factories` properties files. Third-party frameworks and developers can
contribute their own `ContextCustomizerFactory` implementations to the list of default
factories in the same manner through their own `META-INF/spring.factories` properties
files.
[[testcontext-context-customizers-merging]]
@@ -80,12 +80,12 @@ become cumbersome if a custom listener needs to be used across an entire test su
issue is addressed through support for automatic discovery of default
`TestExecutionListener` implementations through the `SpringFactoriesLoader` mechanism.
For example, the `spring-test` module declares all core default `TestExecutionListener`
Specifically, the `spring-test` module declares all core default `TestExecutionListener`
implementations under the `org.springframework.test.context.TestExecutionListener` key in
its {spring-framework-code}/spring-test/src/main/resources/META-INF/spring.factories[`META-INF/spring.factories`
properties file]. Third-party frameworks and developers can contribute their own
`TestExecutionListener` implementations to the list of default listeners in the same
manner through their own `spring.factories` files.
its `META-INF/spring.factories` properties file. Third-party frameworks and developers
can contribute their own `TestExecutionListener` implementations to the list of default
listeners in the same manner through their own `META-INF/spring.factories` properties
file.
[[testcontext-tel-config-ordering]]
== Ordering `TestExecutionListener` Implementations
@@ -207,3 +207,4 @@ Kotlin::
}
----
======
@@ -198,9 +198,6 @@ controller method xref:web/webmvc/mvc-controller/ann-validation.adoc[Validation]
TIP: Using `@ModelAttribute` is optional. By default, any argument that is not a simple
value type as determined by
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty]
_AND_ that is not resolved by any other argument resolver is treated as an implicit `@ModelAttribute`.
_AND_ that is not resolved by any other argument resolver is treated as an `@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.
@@ -509,7 +509,7 @@ Kotlin::
[[webflux-ann-httpexchange-annotation]]
== `@HttpExchange`
[.small]#xref:web/webmvc/mvc-controller/ann-requestmapping.adoc#mvc-ann-httpexchange-annotation[See equivalent in the Servlet stack]#
[.small]#xref:web/webmvc/mvc-controller/ann-requestmapping.adoc#mvc-ann-httpexchange-annotation[See equivalent in the Reactive stack]#
As an alternative to `@RequestMapping`, you can also handle requests with `@HttpExchange`
methods. Such methods are declared on an
@@ -78,9 +78,8 @@ 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
@@ -243,9 +243,4 @@ xref:web/webmvc/mvc-controller/ann-validation.adoc[Validation].
TIP: Using `@ModelAttribute` is optional. By default, any parameter that is not a simple
value type as determined by
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty]
_AND_ that is not resolved by any other argument resolver is treated as an implicit `@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.
_AND_ that is not resolved by any other argument resolver is treated as an `@ModelAttribute`.
@@ -38,7 +38,7 @@ The next example uses server-side configuration to register a custom authenticat
interceptor. Note that an interceptor needs only to authenticate and set
the user header on the CONNECT `Message`. Spring notes and saves the authenticated
user and associate it with subsequent STOMP messages on the same session. The following
example shows how to register a custom authentication interceptor:
example shows how register a custom authentication interceptor:
[source,java,indent=0,subs="verbatim,quotes"]
----
@@ -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 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.
(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.
Spring's STOMP-over-WebSocket support does this ,so applications can configure the
maximum size for STOMP messages irrespective of WebSocket server-specific message
+24 -24
View File
@@ -7,31 +7,30 @@ javaPlatform {
}
dependencies {
api(platform("com.fasterxml.jackson:jackson-bom:2.15.3"))
api(platform("io.micrometer:micrometer-bom:1.12.2"))
api(platform("io.netty:netty-bom:4.1.104.Final"))
api(platform("com.fasterxml.jackson:jackson-bom:2.15.2"))
api(platform("io.micrometer:micrometer-bom:1.12.0"))
api(platform("io.netty:netty-bom:4.1.101.Final"))
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
api(platform("io.projectreactor:reactor-bom:2023.0.2"))
api(platform("io.projectreactor:reactor-bom:2023.0.0"))
api(platform("io.rsocket:rsocket-bom:1.1.3"))
api(platform("org.apache.groovy:groovy-bom:4.0.17"))
api(platform("org.apache.groovy:groovy-bom:4.0.15"))
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
api(platform("org.assertj:assertj-bom:3.25.1"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.5"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.5"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.3"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.3"))
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3"))
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.0"))
api(platform("org.junit:junit-bom:5.10.1"))
api(platform("org.mockito:mockito-bom:5.8.0"))
api(platform("org.mockito:mockito-bom:5.7.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.36")
api("com.github.librepdf:openpdf:1.3.33")
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.25.1")
api("com.google.protobuf:protobuf-java-util:3.25.0")
api("com.h2database:h2:2.2.224")
api("com.jayway.jsonpath:json-path:2.8.0")
api("com.rometools:rome:1.19.0")
@@ -100,16 +99,17 @@ dependencies {
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.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.18")
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.18")
api("org.apache.tomcat:tomcat-util:10.1.18")
api("org.apache.tomcat:tomcat-websocket:10.1.18")
api("org.aspectj:aspectjrt:1.9.21")
api("org.aspectj:aspectjtools:1.9.21")
api("org.aspectj:aspectjweaver:1.9.21")
api("org.apache.httpcomponents.client5:httpclient5:5.2.1")
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.2.3")
api("org.apache.poi:poi-ooxml:5.2.4")
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")
api("org.assertj:assertj-core:3.24.2")
api("org.awaitility:awaitility:4.2.0")
api("org.bouncycastle:bcpkix-jdk18on:1.72")
api("org.codehaus.jettison:jettison:1.5.4")
@@ -131,7 +131,7 @@ dependencies {
api("org.hsqldb:hsqldb:2.7.2")
api("org.javamoney:moneta:1.4.2")
api("org.jruby:jruby:9.4.5.0")
api("org.junit.support:testng-engine:1.0.5")
api("org.junit.support:testng-engine:1.0.4")
api("org.mozilla:rhino:1.7.14")
api("org.ogce:xpp3:1.1.6")
api("org.python:jython-standalone:2.7.3")
@@ -139,8 +139,8 @@ 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.11")
api("org.testng:testng:7.9.0")
api("org.slf4j:slf4j-api:2.0.9")
api("org.testng:testng:7.8.0")
api("org.webjars:underscorejs:1.8.3")
api("org.webjars:webjars-locator-core:0.55")
api("org.xmlunit:xmlunit-assertj:2.9.1")
+2 -2
View File
@@ -1,10 +1,10 @@
version=6.1.3
version=6.1.1
org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m
org.gradle.parallel=true
kotlinVersion=1.9.22
kotlinVersion=1.9.20
kotlin.jvm.target.validation.mode=ignore
kotlin.stdlib.default.dependency=false
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
@@ -1,19 +1,3 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.aop.framework.autoproxy
import kotlinx.coroutines.delay
@@ -110,12 +94,12 @@ class AspectJAutoProxyInterceptorKotlinIntegrationTests(
open class Echo {
open fun echo(value: String): String {
return value
return value;
}
open suspend fun suspendingEcho(value: String): String {
delay(1)
return value
return value;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2018 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.
@@ -49,13 +49,10 @@ public interface TargetSource extends TargetClassAware {
* Will all calls to {@link #getTarget()} return the same object?
* <p>In that case, there will be no need to invoke {@link #releaseTarget(Object)},
* and the AOP framework can cache the return value of {@link #getTarget()}.
* <p>The default implementation returns {@code false}.
* @return {@code true} if the target is immutable
* @see #getTarget
*/
default boolean isStatic() {
return false;
}
boolean isStatic();
/**
* Return a target instance. Invoked immediately before the
@@ -70,11 +67,9 @@ public interface TargetSource extends TargetClassAware {
/**
* Release the given target object obtained from the
* {@link #getTarget()} method, if any.
* <p>The default implementation is empty.
* @param target object obtained from a call to {@link #getTarget()}
* @throws Exception if the object can't be released
*/
default void releaseTarget(Object target) throws Exception {
}
void releaseTarget(Object target) throws Exception;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -78,7 +78,6 @@ import org.springframework.util.StringUtils;
* @author Juergen Hoeller
* @author Ramnivas Laddad
* @author Dave Syer
* @author Yanming Zhou
* @since 2.0
*/
@SuppressWarnings("serial")
@@ -334,15 +333,13 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
Object targetObject = null;
Object thisObject = null;
try {
MethodInvocation curr = ExposeInvocationInterceptor.currentInvocation();
if (curr.getMethod() == method) {
targetObject = curr.getThis();
if (!(curr instanceof ProxyMethodInvocation currPmi)) {
throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + curr);
}
pmi = currPmi;
thisObject = pmi.getProxy();
MethodInvocation mi = ExposeInvocationInterceptor.currentInvocation();
targetObject = mi.getThis();
if (!(mi instanceof ProxyMethodInvocation _pmi)) {
throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
}
pmi = _pmi;
thisObject = pmi.getProxy();
}
catch (IllegalStateException ex) {
// No current invocation...
@@ -474,11 +471,10 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
}
}
if (targetMethod != originalMethod && (shadowMatch == null ||
(Proxy.isProxyClass(targetMethod.getDeclaringClass()) &&
(shadowMatch.neverMatches() || containsAnnotationPointcut())))) {
(shadowMatch.neverMatches() && Proxy.isProxyClass(targetMethod.getDeclaringClass())))) {
// Fall back to the plain original method in case of no resolvable match or a
// negative match on a proxy class (which doesn't carry any annotations on its
// redeclared methods), as well as for annotation pointcuts.
// redeclared methods).
methodToMatch = originalMethod;
try {
shadowMatch = obtainPointcutExpression().matchesMethodExecution(methodToMatch);
@@ -517,10 +513,6 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
return shadowMatch;
}
private boolean containsAnnotationPointcut() {
return resolveExpression().contains("@annotation");
}
@Override
public boolean equals(@Nullable Object other) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -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, args));
return (isAspectMaterialized() && this.declaredPointcut.matches(method, targetClass));
}
private boolean isAspectMaterialized() {
@@ -201,8 +201,9 @@ class ConfigBeanDefinitionParser implements BeanDefinitionParser {
List<BeanReference> beanReferences = new ArrayList<>();
List<Element> declareParents = DomUtils.getChildElementsByTagName(aspectElement, DECLARE_PARENTS);
for (Element declareParent : declareParents) {
beanDefinitions.add(parseDeclareParents(declareParent, parserContext));
for (int i = METHOD_INDEX; i < declareParents.size(); i++) {
Element declareParentsElement = declareParents.get(i);
beanDefinitions.add(parseDeclareParents(declareParentsElement, parserContext));
}
// We have to parse "advice" and all the advice kinds in one loop, to get the
@@ -404,14 +405,24 @@ class ConfigBeanDefinitionParser implements BeanDefinitionParser {
*/
private Class<?> getAdviceClass(Element adviceElement, ParserContext parserContext) {
String elementName = parserContext.getDelegate().getLocalName(adviceElement);
return switch (elementName) {
case BEFORE -> AspectJMethodBeforeAdvice.class;
case AFTER -> AspectJAfterAdvice.class;
case AFTER_RETURNING_ELEMENT -> AspectJAfterReturningAdvice.class;
case AFTER_THROWING_ELEMENT -> AspectJAfterThrowingAdvice.class;
case AROUND -> AspectJAroundAdvice.class;
default -> throw new IllegalArgumentException("Unknown advice kind [" + elementName + "].");
};
if (BEFORE.equals(elementName)) {
return AspectJMethodBeforeAdvice.class;
}
else if (AFTER.equals(elementName)) {
return AspectJAfterAdvice.class;
}
else if (AFTER_RETURNING_ELEMENT.equals(elementName)) {
return AspectJAfterReturningAdvice.class;
}
else if (AFTER_THROWING_ELEMENT.equals(elementName)) {
return AspectJAfterThrowingAdvice.class;
}
else if (AROUND.equals(elementName)) {
return AspectJAroundAdvice.class;
}
else {
throw new IllegalArgumentException("Unknown advice kind [" + elementName + "].");
}
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2021 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,8 +42,10 @@ class ScopedProxyBeanDefinitionDecorator implements BeanDefinitionDecorator {
@Override
public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
boolean proxyTargetClass = true;
if (node instanceof Element ele && ele.hasAttribute(PROXY_TARGET_CLASS)) {
proxyTargetClass = Boolean.parseBoolean(ele.getAttribute(PROXY_TARGET_CLASS));
if (node instanceof Element ele) {
if (ele.hasAttribute(PROXY_TARGET_CLASS)) {
proxyTargetClass = Boolean.parseBoolean(ele.getAttribute(PROXY_TARGET_CLASS));
}
}
// Register the original bean definition as it will be referenced by the scoped proxy
@@ -102,23 +102,8 @@ 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;
/**
* Optional field for {@link AopProxy} implementations to store metadata in.
* Used by {@link JdkDynamicAopProxy}.
* @since 6.1.3
* @see JdkDynamicAopProxy#JdkDynamicAopProxy(AdvisedSupport)
*/
@Nullable
transient volatile Object proxyMetadataCache;
/**
* No-arg constructor for use as a JavaBean.
@@ -506,7 +491,6 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
*/
protected void adviceChanged() {
this.methodCache.clear();
this.proxyMetadataCache = null;
}
/**
@@ -567,6 +551,18 @@ 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();
@@ -588,19 +584,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);
}
/**
* Simple wrapper class around a Method. Used as the key when
* caching methods, for efficient equals and hashCode comparisons.
@@ -650,7 +633,7 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
* @see #getConfigurationOnlyCopy()
* @see #getAdvisorKey()
*/
private static final class AdvisorKeyEntry implements Advisor {
private static class AdvisorKeyEntry implements Advisor {
private final Class<?> adviceType;
@@ -660,6 +643,7 @@ 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) {
@@ -38,8 +38,8 @@ abstract class CoroutinesUtils {
@Nullable
@SuppressWarnings({"unchecked", "rawtypes"})
static Object awaitSingleOrNull(@Nullable Object value, Object continuation) {
return MonoKt.awaitSingleOrNull(value instanceof Mono mono ? mono : Mono.justOrEmpty(value),
static Object awaitSingleOrNull(Object value, Object continuation) {
return MonoKt.awaitSingleOrNull(value instanceof Mono mono ? mono : Mono.just(value),
(Continuation<Object>) continuation);
}
@@ -16,8 +16,6 @@
package org.springframework.aop.framework;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
@@ -73,16 +71,34 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
private static final long serialVersionUID = 5531744639992436476L;
private static final String COROUTINES_FLOW_CLASS_NAME = "kotlinx.coroutines.flow.Flow";
/*
* NOTE: We could avoid the code duplication between this class and the CGLIB
* proxies by refactoring "invoke" into a template method. However, this approach
* adds at least 10% performance overhead versus a copy-paste solution, so we sacrifice
* elegance for performance (we have a good test suite to ensure that the different
* proxies behave the same :-)).
* This way, we can also more easily take advantage of minor optimizations in each class.
*/
/** We use a static Log to avoid serialization issues. */
private static final Log logger = LogFactory.getLog(JdkDynamicAopProxy.class);
private static final String COROUTINES_FLOW_CLASS_NAME = "kotlinx.coroutines.flow.Flow";
/** Config used to configure this proxy. */
private final AdvisedSupport advised;
/** Cached in {@link AdvisedSupport#proxyMetadataCache}. */
private transient ProxiedInterfacesCache cache;
private final Class<?>[] proxiedInterfaces;
/**
* Is the {@link #equals} method defined on the proxied interfaces?
*/
private boolean equalsDefined;
/**
* Is the {@link #hashCode} method defined on the proxied interfaces?
*/
private boolean hashCodeDefined;
/**
@@ -94,17 +110,8 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {
Assert.notNull(config, "AdvisedSupport must not be null");
this.advised = config;
// Initialize ProxiedInterfacesCache if not cached already
ProxiedInterfacesCache cache;
if (config.proxyMetadataCache instanceof ProxiedInterfacesCache proxiedInterfacesCache) {
cache = proxiedInterfacesCache;
}
else {
cache = new ProxiedInterfacesCache(config);
config.proxyMetadataCache = cache;
}
this.cache = cache;
this.proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
findDefinedEqualsAndHashCodeMethods(this.proxiedInterfaces);
}
@@ -118,13 +125,13 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
if (logger.isTraceEnabled()) {
logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
}
return Proxy.newProxyInstance(determineClassLoader(classLoader), this.cache.proxiedInterfaces, this);
return Proxy.newProxyInstance(determineClassLoader(classLoader), this.proxiedInterfaces, this);
}
@SuppressWarnings("deprecation")
@Override
public Class<?> getProxyClass(@Nullable ClassLoader classLoader) {
return Proxy.getProxyClass(determineClassLoader(classLoader), this.cache.proxiedInterfaces);
return Proxy.getProxyClass(determineClassLoader(classLoader), this.proxiedInterfaces);
}
/**
@@ -153,6 +160,28 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
return classLoader;
}
/**
* Finds any {@link #equals} or {@link #hashCode} method that may be defined
* on the supplied set of interfaces.
* @param proxiedInterfaces the interfaces to introspect
*/
private void findDefinedEqualsAndHashCodeMethods(Class<?>[] proxiedInterfaces) {
for (Class<?> proxiedInterface : proxiedInterfaces) {
Method[] methods = proxiedInterface.getDeclaredMethods();
for (Method method : methods) {
if (AopUtils.isEqualsMethod(method)) {
this.equalsDefined = true;
}
if (AopUtils.isHashCodeMethod(method)) {
this.hashCodeDefined = true;
}
if (this.equalsDefined && this.hashCodeDefined) {
return;
}
}
}
}
/**
* Implementation of {@code InvocationHandler.invoke}.
@@ -169,11 +198,11 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
Object target = null;
try {
if (!this.cache.equalsDefined && AopUtils.isEqualsMethod(method)) {
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
// The target does not implement the equals(Object) method itself.
return equals(args[0]);
}
else if (!this.cache.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
// The target does not implement the hashCode() method itself.
return hashCode();
}
@@ -295,63 +324,4 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
return JdkDynamicAopProxy.class.hashCode() * 13 + this.advised.getTargetSource().hashCode();
}
//---------------------------------------------------------------------
// 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.cache = new ProxiedInterfacesCache(this.advised);
}
/**
* Holder for the complete proxied interfaces and derived metadata,
* to be cached in {@link AdvisedSupport#proxyMetadataCache}.
* @since 6.1.3
*/
private static final class ProxiedInterfacesCache {
final Class<?>[] proxiedInterfaces;
final boolean equalsDefined;
final boolean hashCodeDefined;
ProxiedInterfacesCache(AdvisedSupport config) {
this.proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(config, true);
// Find any {@link #equals} or {@link #hashCode} method that may be defined
// on the supplied set of interfaces.
boolean equalsDefined = false;
boolean hashCodeDefined = false;
for (Class<?> proxiedInterface : this.proxiedInterfaces) {
Method[] methods = proxiedInterface.getDeclaredMethods();
for (Method method : methods) {
if (AopUtils.isEqualsMethod(method)) {
equalsDefined = true;
if (hashCodeDefined) {
break;
}
}
if (AopUtils.isHashCodeMethod(method)) {
hashCodeDefined = true;
if (equalsDefined) {
break;
}
}
}
if (equalsDefined && hashCodeDefined) {
break;
}
}
this.equalsDefined = equalsDefined;
this.hashCodeDefined = hashCodeDefined;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2022 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.
@@ -58,7 +58,6 @@ 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 {
@@ -74,6 +73,8 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
protected final Log logger = LogFactory.getLog(getClass());
private final Map<Method, AsyncTaskExecutor> executors = new ConcurrentHashMap<>(16);
private SingletonSupplier<Executor> defaultExecutor;
private SingletonSupplier<AsyncUncaughtExceptionHandler> exceptionHandler;
@@ -84,9 +85,6 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
@Nullable
private StringValueResolver embeddedValueResolver;
private final Map<Method, AsyncTaskExecutor> executors = new ConcurrentHashMap<>(16);
/**
* Create a new instance with a default {@link AsyncUncaughtExceptionHandler}.
* @param defaultExecutor the {@code Executor} (typically a Spring {@code AsyncTaskExecutor}
@@ -159,7 +157,6 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
if (beanFactory instanceof ConfigurableBeanFactory configurableBeanFactory) {
this.embeddedValueResolver = new EmbeddedValueResolver(configurableBeanFactory);
}
this.executors.clear();
}
@@ -293,7 +290,7 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
else if (Future.class.isAssignableFrom(returnType)) {
return executor.submit(task);
}
else if (void.class == returnType || "kotlin.Unit".equals(returnType.getName())) {
else if (void.class == returnType) {
executor.submit(task);
return null;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,6 +32,7 @@ import org.springframework.core.Ordered;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
/**
* AOP Alliance {@code MethodInterceptor} that processes method invocations
@@ -100,9 +101,10 @@ public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport imple
@Nullable
public Object invoke(final MethodInvocation invocation) throws Throwable {
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
final Method userMethod = BridgeMethodResolver.getMostSpecificMethod(invocation.getMethod(), targetClass);
Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);
final Method userDeclaredMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
AsyncTaskExecutor executor = determineAsyncExecutor(userMethod);
AsyncTaskExecutor executor = determineAsyncExecutor(userDeclaredMethod);
if (executor == null) {
throw new IllegalStateException(
"No executor specified and no default executor set on AsyncExecutionInterceptor either");
@@ -116,10 +118,10 @@ public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport imple
}
}
catch (ExecutionException ex) {
handleError(ex.getCause(), userMethod, invocation.getArguments());
handleError(ex.getCause(), userDeclaredMethod, invocation.getArguments());
}
catch (Throwable ex) {
handleError(ex, userMethod, invocation.getArguments());
handleError(ex, userDeclaredMethod, invocation.getArguments());
}
return null;
};
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -199,11 +199,12 @@ public abstract class AopUtils {
* @return the specific target method, or the original method if the
* {@code targetClass} doesn't implement it or is {@code null}
* @see org.springframework.util.ClassUtils#getMostSpecificMethod
* @see org.springframework.core.BridgeMethodResolver#getMostSpecificMethod
*/
public static Method getMostSpecificMethod(Method method, @Nullable Class<?> targetClass) {
Class<?> specificTargetClass = (targetClass != null ? ClassUtils.getUserClass(targetClass) : null);
return BridgeMethodResolver.getMostSpecificMethod(method, specificTargetClass);
Method resolvedMethod = ClassUtils.getMostSpecificMethod(method, specificTargetClass);
// If we are dealing with method with generic parameters, find the original method.
return BridgeMethodResolver.findBridgedMethod(resolvedMethod);
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -52,7 +52,7 @@ public class ControlFlowPointcut implements Pointcut, ClassFilter, MethodMatcher
/**
* The class against which to match.
* @since 6.1
* <p>Available for use in subclasses since 6.1.
*/
protected final Class<?> clazz;
@@ -153,6 +153,16 @@ public abstract class AbstractBeanFactoryBasedTargetSource implements TargetSour
}
}
@Override
public boolean isStatic() {
return false;
}
@Override
public void releaseTarget(Object target) throws Exception {
// Nothing to do here.
}
/**
* Copy configuration from the other AbstractBeanFactoryBasedTargetSource object.
@@ -72,6 +72,11 @@ public abstract class AbstractLazyCreationTargetSource implements TargetSource {
return (this.lazyTarget != null ? this.lazyTarget.getClass() : null);
}
@Override
public boolean isStatic() {
return false;
}
/**
* Returns the lazy-initialized target object,
* creating it on-the-fly if it doesn't exist already.
@@ -86,6 +91,11 @@ public abstract class AbstractLazyCreationTargetSource implements TargetSource {
return this.lazyTarget;
}
@Override
public void releaseTarget(Object target) throws Exception {
// nothing to do
}
/**
* Subclasses should implement this method to return the lazy initialized object.
@@ -116,6 +116,13 @@ public final class EmptyTargetSource implements TargetSource, Serializable {
return null;
}
/**
* Nothing to release.
*/
@Override
public void releaseTarget(Object target) {
}
/**
* Returns the canonical instance on deserialization in case
@@ -66,11 +66,21 @@ public class HotSwappableTargetSource implements TargetSource, Serializable {
return this.target.getClass();
}
@Override
public final boolean isStatic() {
return false;
}
@Override
public synchronized Object getTarget() {
return this.target;
}
@Override
public void releaseTarget(Object target) {
// nothing to do
}
/**
* Swap the target, returning the old target object.
@@ -67,6 +67,11 @@ public class SingletonTargetSource implements TargetSource, Serializable {
return this.target;
}
@Override
public void releaseTarget(Object target) {
// nothing to do
}
@Override
public boolean isStatic() {
return true;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2018 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,6 +73,14 @@ public abstract class AbstractRefreshableTargetSource implements TargetSource, R
return this.targetObject.getClass();
}
/**
* Not static.
*/
@Override
public boolean isStatic() {
return false;
}
@Override
@Nullable
public final synchronized Object getTarget() {
@@ -82,6 +90,13 @@ public abstract class AbstractRefreshableTargetSource implements TargetSource, R
return this.targetObject;
}
/**
* No need to release target.
*/
@Override
public void releaseTarget(Object object) {
}
@Override
public final synchronized void refresh() {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for {@link AspectJAdviceParameterNameDiscoverer}.
* Unit tests for {@link AspectJAdviceParameterNameDiscoverer}.
*
* @author Adrian Colyer
* @author Chris Beams
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 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,6 +23,7 @@ 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;
@@ -49,7 +50,6 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
* @author Rob Harrop
* @author Rod Johnson
* @author Chris Beams
* @author Yanming Zhou
*/
public class AspectJExpressionPointcutTests {
@@ -65,7 +65,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,13 +246,14 @@ 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();
AspectJExpressionPointcut pointcut = getPointcut(pointcutExpression);
Pointcut pointcut = getPointcut(pointcutExpression);
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
advisor.setAdvice(interceptor);
@@ -276,31 +277,40 @@ 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() {
AspectJExpressionPointcut pc = getPointcut("execution(* *(..)) and args(String)");
String expr = pc.getPointcutExpression().getPointcutExpression();
assertThat(expr).isEqualTo("execution(* *(..)) && args(String)");
Pointcut pc = getPointcut("execution(* *(..)) and args(String)");
PointcutExpression expr = ((AspectJExpressionPointcut) pc).getPointcutExpression();
assertThat(expr.getPointcutExpression()).isEqualTo("execution(* *(..)) && args(String)");
}
@Test
public void testMultipleAndSubstitutions() {
AspectJExpressionPointcut pc = getPointcut("execution(* *(..)) and args(String) and this(Object)");
String expr = pc.getPointcutExpression().getPointcutExpression();
assertThat(expr).isEqualTo("execution(* *(..)) && args(String) && this(Object)");
Pointcut pc = getPointcut("execution(* *(..)) and args(String) and this(Object)");
PointcutExpression expr = ((AspectJExpressionPointcut) pc).getPointcutExpression();
assertThat(expr.getPointcutExpression()).isEqualTo("execution(* *(..)) && args(String) && this(Object)");
}
private AspectJExpressionPointcut getPointcut(String expression) {
private Pointcut 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>) )";
@@ -414,19 +424,6 @@ public class AspectJExpressionPointcutTests {
assertThat(ajexp.matches(BeanA.class.getMethod("getAge"), proxy.getClass())).isTrue();
}
@Test
public void testNotAnnotationOnCglibProxyMethod() throws Exception {
String expression = "!@annotation(test.annotation.transaction.Tx)";
AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut();
ajexp.setExpression(expression);
ProxyFactory factory = new ProxyFactory(new BeanA());
factory.setProxyTargetClass(true);
BeanA proxy = (BeanA) factory.getProxy();
assertThat(ajexp.matches(BeanA.class.getMethod("getAge"), proxy.getClass())).isFalse();
}
@Test
public void testAnnotationOnDynamicProxyMethod() throws Exception {
String expression = "@annotation(test.annotation.transaction.Tx)";
@@ -439,18 +436,6 @@ public class AspectJExpressionPointcutTests {
assertThat(ajexp.matches(IBeanA.class.getMethod("getAge"), proxy.getClass())).isTrue();
}
@Test
public void testNotAnnotationOnDynamicProxyMethod() throws Exception {
String expression = "!@annotation(test.annotation.transaction.Tx)";
AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut();
ajexp.setExpression(expression);
ProxyFactory factory = new ProxyFactory(new BeanA());
factory.setProxyTargetClass(false);
IBeanA proxy = (IBeanA) factory.getProxy();
assertThat(ajexp.matches(IBeanA.class.getMethod("getAge"), proxy.getClass())).isFalse();
}
@Test
public void testAnnotationOnMethodWithWildcard() throws Exception {
String expression = "execution(@(test.annotation..*) * *(..))";
@@ -520,15 +505,6 @@ public class AspectJExpressionPointcutTests {
}
public static class OtherIOther implements IOther {
@Override
public void absquatulate() {
// Empty
}
}
public static class HasGeneric {
public void setFriends(List<TestBean> friends) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* 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.
@@ -31,7 +31,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Tests for {@link TypePatternClassFilter}.
* Unit tests for the {@link TypePatternClassFilter} class.
*
* @author Rod Johnson
* @author Rick Evans
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -42,38 +42,26 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
*/
class ArgumentBindingTests {
@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");
}
@Test
void bindingInPointcutUsedByAdvice() {
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new TestBean());
proxyFactory.addAspect(NamedPointcutWithArgs.class);
ITestBean proxiedTestBean = proxyFactory.getProxy();
ITestBean proxiedTestBean = proxyFactory.getProxy();
assertThatIllegalArgumentException()
.isThrownBy(() -> proxiedTestBean.setName("enigma"))
.withMessage("enigma");
.isThrownBy(() -> proxiedTestBean.setName("enigma"))
.withMessage("enigma");
}
@Test
void bindingWithDynamicAdvice() {
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new TestBean());
proxyFactory.addAspect(DynamicPointcutWithArgs.class);
ITestBean proxiedTestBean = proxyFactory.getProxy();
void annotationArgumentNameBinding() {
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new TransactionalBean());
proxyFactory.addAspect(PointcutWithAnnotationArgument.class);
proxiedTestBean.applyName(1);
assertThatIllegalArgumentException()
.isThrownBy(() -> proxiedTestBean.applyName("enigma"))
.withMessage("enigma");
ITransactionalBean proxiedTestBean = proxyFactory.getProxy();
assertThatIllegalStateException()
.isThrownBy(proxiedTestBean::doInTransaction)
.withMessage("Invoked with @Transactional");
}
@Test
@@ -106,7 +94,6 @@ class ArgumentBindingTests {
}
}
/**
* Mimics Spring's @Transactional annotation without actually introducing the dependency.
*/
@@ -114,16 +101,15 @@ class ArgumentBindingTests {
@interface Transactional {
}
@Aspect
static class PointcutWithAnnotationArgument {
@Around("execution(* org.springframework..*.*(..)) && @annotation(transactional)")
@Around(value = "execution(* org.springframework..*.*(..)) && @annotation(transactional)")
public Object around(ProceedingJoinPoint pjp, Transactional transactional) throws Throwable {
throw new IllegalStateException("Invoked with @Transactional");
}
}
}
@Aspect
static class NamedPointcutWithArgs {
@@ -135,16 +121,7 @@ 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]));
}
}
}
@@ -354,8 +354,8 @@ public class ProxyFactoryTests {
list.add(proxy1);
list.add(proxy2);
AnnotationAwareOrderComparator.sort(list);
assertThat(list).element(0).isSameAs(proxy2);
assertThat(list).element(1).isSameAs(proxy1);
assertThat(list.get(0)).isSameAs(proxy2);
assertThat(list.get(1)).isSameAs(proxy1);
}
@Test
@@ -370,8 +370,8 @@ public class ProxyFactoryTests {
list.add(proxy1);
list.add(proxy2);
AnnotationAwareOrderComparator.sort(list);
assertThat(list).element(0).isSameAs(proxy2);
assertThat(list).element(1).isSameAs(proxy1);
assertThat(list.get(0)).isSameAs(proxy2);
assertThat(list.get(1)).isSameAs(proxy1);
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,7 +30,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link DebugInterceptor}.
* Unit tests for the {@link DebugInterceptor} class.
*
* @author Rick Evans
* @author Chris Beams
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link SimpleTraceInterceptor}.
* Unit tests for the {@link SimpleTraceInterceptor} class.
*
* @author Rick Evans
* @author Chris Beams
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,7 +24,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
import static org.mockito.Mockito.mock;
/**
* Tests for {@link DefaultScopedObject}.
* Unit tests for the {@link DefaultScopedObject} class.
*
* @author Rick Evans
* @author Chris Beams
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* 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.
@@ -39,8 +39,8 @@ public class ScopedProxyAutowireTests {
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(
qualifiedResource(ScopedProxyAutowireTests.class, "scopedAutowireFalse.xml"));
assertThat(Arrays.asList(bf.getBeanNamesForType(TestBean.class, false, false))).contains("scoped");
assertThat(Arrays.asList(bf.getBeanNamesForType(TestBean.class, true, false))).contains("scoped");
assertThat(Arrays.asList(bf.getBeanNamesForType(TestBean.class, false, false)).contains("scoped")).isTrue();
assertThat(Arrays.asList(bf.getBeanNamesForType(TestBean.class, true, false)).contains("scoped")).isTrue();
assertThat(bf.containsSingleton("scoped")).isFalse();
TestBean autowired = (TestBean) bf.getBean("autowired");
TestBean unscoped = (TestBean) bf.getBean("unscoped");
@@ -53,8 +53,8 @@ public class ScopedProxyAutowireTests {
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(
qualifiedResource(ScopedProxyAutowireTests.class, "scopedAutowireTrue.xml"));
assertThat(Arrays.asList(bf.getBeanNamesForType(TestBean.class, true, false))).contains("scoped");
assertThat(Arrays.asList(bf.getBeanNamesForType(TestBean.class, false, false))).contains("scoped");
assertThat(Arrays.asList(bf.getBeanNamesForType(TestBean.class, true, false)).contains("scoped")).isTrue();
assertThat(Arrays.asList(bf.getBeanNamesForType(TestBean.class, false, false)).contains("scoped")).isTrue();
assertThat(bf.containsSingleton("scoped")).isFalse();
TestBean autowired = (TestBean) bf.getBean("autowired");
TestBean scoped = (TestBean) bf.getBean("scoped");
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* 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.
@@ -22,7 +22,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link ScopedProxyUtils}.
* Unit tests for {@link ScopedProxyUtils}.
*
* @author Sam Brannen
* @since 5.1.10
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link ClassFilters}.
* Unit tests for {@link ClassFilters}.
*
* @author Rod Johnson
* @author Chris Beams
@@ -141,7 +141,7 @@ public class ComposablePointcutTests {
pc1.intersection(GETTER_METHOD_MATCHER);
assertThat(pc1).isNotEqualTo(pc2);
assertThat(pc1.equals(pc2)).isFalse();
assertThat(pc1.hashCode()).isNotEqualTo(pc2.hashCode());
pc2.intersection(GETTER_METHOD_MATCHER);
@@ -110,8 +110,8 @@ public class MethodMatchersTests {
public void testUnionEquals() {
MethodMatcher first = MethodMatchers.union(MethodMatcher.TRUE, MethodMatcher.TRUE);
MethodMatcher second = new ComposablePointcut(MethodMatcher.TRUE).union(new ComposablePointcut(MethodMatcher.TRUE)).getMethodMatcher();
assertThat(first).isEqualTo(second);
assertThat(second).isEqualTo(first);
assertThat(first.equals(second)).isTrue();
assertThat(second.equals(first)).isTrue();
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* 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.
@@ -24,7 +24,7 @@ import org.springframework.beans.testfixture.beans.ITestBean;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link RootClassFilter}.
* Unit tests for {@link RootClassFilter}.
*
* @author Sam Brannen
* @since 5.1.10
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* 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.
@@ -25,7 +25,7 @@ import org.springframework.beans.factory.annotation.Qualifier;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link AnnotationMatchingPointcut}.
* Unit tests for {@link AnnotationMatchingPointcut}.
*
* @author Sam Brannen
* @since 5.1.10
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* 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.
@@ -34,7 +34,7 @@ import static org.springframework.core.testfixture.io.ResourceTestUtils.qualifie
* @author Chris Beams
* @since 07.01.2005
*/
class LazyInitTargetSourceTests {
public class LazyInitTargetSourceTests {
private static final Class<?> CLASS = LazyInitTargetSourceTests.class;
@@ -42,11 +42,9 @@ class LazyInitTargetSourceTests {
private static final Resource CUSTOM_TARGET_CONTEXT = qualifiedResource(CLASS, "customTarget.xml");
private static final Resource FACTORY_BEAN_CONTEXT = qualifiedResource(CLASS, "factoryBean.xml");
private final DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
@Test
void lazyInitSingletonTargetSource() {
public void testLazyInitSingletonTargetSource() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(SINGLETON_CONTEXT);
bf.preInstantiateSingletons();
@@ -57,7 +55,8 @@ class LazyInitTargetSourceTests {
}
@Test
void customLazyInitSingletonTargetSource() {
public void testCustomLazyInitSingletonTargetSource() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(CUSTOM_TARGET_CONTEXT);
bf.preInstantiateSingletons();
@@ -68,25 +67,25 @@ class LazyInitTargetSourceTests {
}
@Test
@SuppressWarnings("unchecked")
void lazyInitFactoryBeanTargetSource() {
public void testLazyInitFactoryBeanTargetSource() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(FACTORY_BEAN_CONTEXT);
bf.preInstantiateSingletons();
Set<Object> set1 = (Set<Object>) bf.getBean("proxy1");
Set<?> set1 = (Set<?>) bf.getBean("proxy1");
assertThat(bf.containsSingleton("target1")).isFalse();
assertThat(set1).contains("10");
assertThat(set1.contains("10")).isTrue();
assertThat(bf.containsSingleton("target1")).isTrue();
Set<Object> set2 = (Set<Object>) bf.getBean("proxy2");
Set<?> set2 = (Set<?>) bf.getBean("proxy2");
assertThat(bf.containsSingleton("target2")).isFalse();
assertThat(set2).contains("20");
assertThat(set2.contains("20")).isTrue();
assertThat(bf.containsSingleton("target2")).isTrue();
}
@SuppressWarnings("serial")
static class CustomLazyInitTargetSource extends LazyInitTargetSource {
public static class CustomLazyInitTargetSource extends LazyInitTargetSource {
@Override
protected void postProcessTargetObject(Object targetObject) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2022 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.
@@ -75,9 +75,14 @@ public class PrototypeBasedTargetSourceTests {
private TestBean thisFieldIsNotSerializable = new TestBean();
@Override
public Object getTarget() {
public Object getTarget() throws Exception {
return newPrototypeInstance();
}
@Override
public void releaseTarget(Object target) throws Exception {
// Do nothing
}
}
}
@@ -1,75 +0,0 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.aop.framework
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.runBlocking
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import kotlin.coroutines.Continuation
/**
* Tests for [CoroutinesUtils].
*
* @author Sebastien Deleuze
*/
class CoroutinesUtilsTests {
@Test
fun awaitSingleNonNullValue() {
val value = "foo"
val continuation = Continuation<Any>(CoroutineName("test")) { }
runBlocking {
assertThat(CoroutinesUtils.awaitSingleOrNull(value, continuation)).isEqualTo(value)
}
}
@Test
fun awaitSingleNullValue() {
val value = null
val continuation = Continuation<Any>(CoroutineName("test")) { }
runBlocking {
assertThat(CoroutinesUtils.awaitSingleOrNull(value, continuation)).isNull()
}
}
@Test
fun awaitSingleMonoValue() {
val value = "foo"
val continuation = Continuation<Any>(CoroutineName("test")) { }
runBlocking {
assertThat(CoroutinesUtils.awaitSingleOrNull(Mono.just(value), continuation)).isEqualTo(value)
}
}
@Test
@Suppress("UNCHECKED_CAST")
fun flow() {
val value1 = "foo"
val value2 = "bar"
val values = Flux.just(value1, value2)
val flow = CoroutinesUtils.asFlow(values) as Flow<String>
runBlocking {
assertThat(flow.toList()).containsExactly(value1, value2)
}
}
}
@@ -46,7 +46,7 @@ class AopUtilsKotlinTests {
@Suppress("unused")
suspend fun suspendingFunction(value: String): String {
delay(1)
return value
return value;
}
}

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