mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
409 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b19f98bfd2 | |||
| 50fad9ed05 | |||
| 2b4ffe0391 | |||
| e7eaaaded1 | |||
| f6e52900a2 | |||
| 62b5e42769 | |||
| 1631be5660 | |||
| 9ccc72a9fb | |||
| 01b2856114 | |||
| 6dca7b28cc | |||
| 864b1c95cd | |||
| 168c60c18a | |||
| 1e49334209 | |||
| 79cc0ec4aa | |||
| fdfa4284de | |||
| 49d3ec58fc | |||
| c4405104a8 | |||
| 4b0443090a | |||
| b1bf1b0c82 | |||
| 5d45b94e93 | |||
| 598c972a78 | |||
| 2593b60f2b | |||
| e6f638132c | |||
| b823c46aae | |||
| 4d11307b84 | |||
| 03b6e51225 | |||
| 785598629a | |||
| 3452354a11 | |||
| bb1cdb6b48 | |||
| 37fa82c578 | |||
| 1f2d29ee08 | |||
| cffc8835c6 | |||
| 515c654a46 | |||
| aacb7e1604 | |||
| c86642dfee | |||
| f4a9b12340 | |||
| bd66763f26 | |||
| 2d3b02a89d | |||
| 8552e149b5 | |||
| e0d6b69195 | |||
| a3532bfccc | |||
| 6697f01d05 | |||
| 01c62f86b3 | |||
| 9912a52bb8 | |||
| 419e34e571 | |||
| f0e16bd31b | |||
| 43107e7eb1 | |||
| 81bd6be1c0 | |||
| d6c84c43ec | |||
| 70247c4a94 | |||
| 87b35e7d8e | |||
| a51c22b266 | |||
| 318d460256 | |||
| d7cfdc633a | |||
| 085af10afd | |||
| a8273a3009 | |||
| 580d9f81e2 | |||
| 79b0d71514 | |||
| f6b36a689a | |||
| 4b6126c057 | |||
| a108e701bc | |||
| 0ad561d379 | |||
| 1372265bd9 | |||
| 476ef0c3ca | |||
| 534d3229fe | |||
| 07097976ef | |||
| fb4fbeab50 | |||
| b169dc50ad | |||
| 02e32baa80 | |||
| 549f6c1e80 | |||
| 16b4c25f7d | |||
| af2e13e211 | |||
| 7c9307e970 | |||
| 4f599b7396 | |||
| 2784f6008e | |||
| be9ee9112c | |||
| 19a87e968e | |||
| 207b9a14f4 | |||
| 05ebca8677 | |||
| f846d9484c | |||
| 50069ef029 | |||
| 777ff3f1a8 | |||
| 89466cb33c | |||
| bf3a478990 | |||
| efb97cca82 | |||
| b692c0ed03 | |||
| 6eed2b0aee | |||
| 7876db03c6 | |||
| ffddbb586e | |||
| a3c11fc033 | |||
| 321de9ab9b | |||
| ec5f566ba5 | |||
| 2d6b77336b | |||
| 9a1ee48d73 | |||
| e22d1efdc0 | |||
| ff8097d37c | |||
| 7d44a4dcad | |||
| fdb454b9a4 | |||
| b4174377c2 | |||
| 174eae377f | |||
| 243ec88e95 | |||
| 9f3fd103ef | |||
| 0ad3800f54 | |||
| 473efb6d4f | |||
| 153f8895cb | |||
| f5b4f7d9e8 | |||
| 989625d2d4 | |||
| 28e5468162 | |||
| b1c0b65666 | |||
| 490aaa1ed8 | |||
| c7d2d6716d | |||
| eefe65d95a | |||
| 3c5d46166e | |||
| cfa3aa001f | |||
| ea5ef098cf | |||
| adcf236a3d | |||
| 72a9864788 | |||
| a6e87b40c7 | |||
| 094479b55f | |||
| db2c532c07 | |||
| 4a450c6fab | |||
| ed1bfb8177 | |||
| a35384fd57 | |||
| 9d31537ae5 | |||
| dee1b726f9 | |||
| 7cfff4049d | |||
| 45080e3724 | |||
| c3b5f5bf90 | |||
| 7e5afc8bbb | |||
| 7474af4f09 | |||
| 28a7b6103a | |||
| 3cddb0434d | |||
| 36a72115f9 | |||
| 3c0b5459be | |||
| 088be2d017 | |||
| a155a6b3e2 | |||
| a338a16b29 | |||
| 7613bdfdf9 | |||
| 57f27fa42f | |||
| 55d9d151fb | |||
| 70f31dee45 | |||
| 699f93fed7 | |||
| 4c6ca05af5 | |||
| 75c7596259 | |||
| 17d362fa85 | |||
| a428955438 | |||
| 4afac17e58 | |||
| 8bd8c4f627 | |||
| 0390709577 | |||
| 9f2970bc5c | |||
| 232225b2aa | |||
| 5caf714ff4 | |||
| cd11219fa7 | |||
| 44c652ec98 | |||
| cd8bc2f82a | |||
| 12f6330fae | |||
| fc0ea465e1 | |||
| 459338f6fd | |||
| f0add920f5 | |||
| 33c149077a | |||
| e00a882333 | |||
| 3ed5a90b7c | |||
| 3476402a75 | |||
| b04803de99 | |||
| f443cf965a | |||
| c9292f8e09 | |||
| bd65a19d71 | |||
| 85cb6cc5fb | |||
| 5f8a031c22 | |||
| dc564f3ef2 | |||
| d7ce13c763 | |||
| 7b9037b054 | |||
| 3162afbf16 | |||
| 1bd523f6b6 | |||
| 4c0d0ba5b3 | |||
| 212346a86d | |||
| 564803f56a | |||
| df708d16e4 | |||
| 848dedb576 | |||
| 4516e0d413 | |||
| a23375c49d | |||
| 53b937976d | |||
| 12f01f9b5f | |||
| 917978cbc2 | |||
| 3f5c3b1747 | |||
| bec7210b4b | |||
| 0a94dce41d | |||
| 9b3afcdac7 | |||
| 8eb524dc4d | |||
| dee8108bbc | |||
| 045c5dc1b4 | |||
| 24f8eac12a | |||
| eaf7a28250 | |||
| 7965c1969f | |||
| d0574197ea | |||
| 63b2787da6 | |||
| 1ff683b259 | |||
| 22bf4df290 | |||
| b56fc50c27 | |||
| d2aa6a98f2 | |||
| bf0819390f | |||
| 503ccb577c | |||
| 2f7e650122 | |||
| 68931a2091 | |||
| 7f79ccbec0 | |||
| 43c2e51d6e | |||
| 66b8f369dc | |||
| d4406507d0 | |||
| a612518f96 | |||
| ec0ec7a0d6 | |||
| 0970b1dc7a | |||
| a01c6d57bb | |||
| 8d4deca2a6 | |||
| cd64e6676c | |||
| 2acc7c609f | |||
| 409cecfff9 | |||
| 3c2c9ca186 | |||
| 75da9c3c47 | |||
| 952223dcf9 | |||
| 125e2902be | |||
| 4d838c1092 | |||
| c75c0ae2d5 | |||
| c0683cd30b | |||
| 7471fd1dd0 | |||
| 1c58511cb2 | |||
| 8de0fadd09 | |||
| da01e0c6e2 | |||
| b87852612b | |||
| 7adc2f0779 | |||
| 33b28fe5bf | |||
| b85fcb6aec | |||
| 134bb6e31f | |||
| 240a75f313 | |||
| 6dcba4de2c | |||
| 6bb9775309 | |||
| eae53560e4 | |||
| f0abdf2264 | |||
| c7f24efc27 | |||
| 07d2571e0b | |||
| 8c51315cd6 | |||
| e8cd26bbf0 | |||
| 9aded3fcad | |||
| aabe4d0b07 | |||
| 570074259d | |||
| f962211e0a | |||
| 6077c998d1 | |||
| 57b8100a06 | |||
| 7432a96b48 | |||
| a01384068a | |||
| d75a7c3818 | |||
| e2c2268c39 | |||
| b510bc3bab | |||
| b6364e3665 | |||
| f29bfd9769 | |||
| 361dfd1ae4 | |||
| 8704ad98a7 | |||
| c57b7e8418 | |||
| 77b0382a6c | |||
| a7f9da1670 | |||
| b782747472 | |||
| 69bc4e2828 | |||
| e4e2224449 | |||
| d919930d83 | |||
| afc1f37616 | |||
| 748dd94dab | |||
| b3a3b79b44 | |||
| 91b9a75371 | |||
| b78aed99ea | |||
| 2eba3510f7 | |||
| 56afd38148 | |||
| 33e4129155 | |||
| 0717ea5ca5 | |||
| 3b4c7a8906 | |||
| 2e07f9ab33 | |||
| dd23b1d156 | |||
| 753409083d | |||
| e36d035f58 | |||
| 302cdeeee6 | |||
| 61dd9fce73 | |||
| 7a221eb581 | |||
| 2e5d1daeff | |||
| bf6cb7cd89 | |||
| 2c053b34f0 | |||
| 438c3818cc | |||
| f14b122c9c | |||
| 9f305bfaab | |||
| d410872e4f | |||
| 8fe2c780df | |||
| b53ffa3855 | |||
| 25537938d6 | |||
| 9704b809b1 | |||
| 0e6c17f518 | |||
| 1afea0b144 | |||
| 7b95bd72f7 | |||
| fdcea58a53 | |||
| ef4ffa0005 | |||
| 2e3d13331a | |||
| 448e753184 | |||
| 6b53f37030 | |||
| afcd03bddc | |||
| 7b16ef90f1 | |||
| e2852e7355 | |||
| 1a63257b12 | |||
| 66e405525b | |||
| 59815cefce | |||
| 785ad399e9 | |||
| 3f9a809c32 | |||
| c74d60b9fe | |||
| db48813181 | |||
| a596c0e226 | |||
| 462ef95904 | |||
| 52d4b83dba | |||
| aa347e5fe6 | |||
| ceba4162bb | |||
| 21560bccd3 | |||
| 1da40b84e7 | |||
| 6f11716b6f | |||
| 47fe61ef79 | |||
| 8a82da43c9 | |||
| cb60f74556 | |||
| 62b3d7a963 | |||
| b69e5acfe3 | |||
| d71853f105 | |||
| 490b5c77fc | |||
| 8ed04b5dd1 | |||
| 87d37a21aa | |||
| eee2569bff | |||
| dbec3f1fa1 | |||
| e870912fa2 | |||
| 99f50ebeb4 | |||
| cd62dfe3a9 | |||
| 7cdacf3083 | |||
| 0bec8125a4 | |||
| da32e3d39e | |||
| 47cdc7c5f0 | |||
| decb22a93d | |||
| d59b2924d3 | |||
| c05b4ce776 | |||
| 3a53446a2b | |||
| d204dd2dbe | |||
| 0dbb0f5c14 | |||
| e452c2e89c | |||
| 6ea9fdbf77 | |||
| 8ff687b68c | |||
| bf1c179b7f | |||
| 16ac495084 | |||
| a506238ef6 | |||
| 33af98b6d6 | |||
| edfe179291 | |||
| 5f9702b2a4 | |||
| c56c304536 | |||
| 8090a52f5c | |||
| 19bca03aa2 | |||
| 0e6e225fb9 | |||
| 8ca82120e0 | |||
| 9ade52dbe2 | |||
| feef98b73c | |||
| 4a6c3e8f5d | |||
| f77713b7e0 | |||
| dc5bef16b4 | |||
| f3b1f37000 | |||
| df00aafdff | |||
| 7cf124b696 | |||
| 35fcbae8c6 | |||
| c8e6315a67 | |||
| 61be452402 | |||
| 0cbbd3a0d5 | |||
| c92a0bd493 | |||
| 755fd75512 | |||
| f8a40555af | |||
| 64d5e904e8 | |||
| 4e2d357318 | |||
| 264ec517f2 | |||
| b2e3be10d4 | |||
| 246833329f | |||
| 43700302c6 | |||
| 54ecbb2bc8 | |||
| 9eb2f29d4a | |||
| 97264c77dd | |||
| b17f3bc07b | |||
| a15f472898 | |||
| 0785256b2f | |||
| 95e250d4bd | |||
| dbad9fd208 | |||
| 657b1c6455 | |||
| 23dc5696d9 | |||
| a56bf87476 | |||
| c2745de60c | |||
| 710373d286 | |||
| 0599320bd8 | |||
| 8921be18de | |||
| 6ff75f157b | |||
| 15d9d9d06a | |||
| 894dce7cd8 | |||
| 34031ebea9 | |||
| b2757d9a21 | |||
| 655b4aacb7 | |||
| fb4455b396 | |||
| 677b03c36d | |||
| 85aa4b65dc | |||
| 0b61755ea3 | |||
| 3bcd30e811 | |||
| 121e3540cc | |||
| 0d8dee2878 | |||
| 3a6d0c1d5b | |||
| 16c6376b3f | |||
| 487dbf8140 | |||
| e95f8d2922 | |||
| 52b40ffb03 |
@@ -1,36 +0,0 @@
|
||||
Juergen Hoeller <jhoeller@vmware.com>
|
||||
Juergen Hoeller <jhoeller@vmware.com> <jhoeller@pivotal.io>
|
||||
Juergen Hoeller <jhoeller@vmware.com> <jhoeller@gopivotal.com>
|
||||
Rossen Stoyanchev <rstoyanchev@vmware.com>
|
||||
Rossen Stoyanchev <rstoyanchev@vmware.com> <rstoyanchev@pivotal.io>
|
||||
Rossen Stoyanchev <rstoyanchev@vmware.com> <rstoyanchev@gopivotal.com>
|
||||
Phillip Webb <pwebb@vmware.com>
|
||||
Phillip Webb <pwebb@vmware.com> <pwebb@pivotal.io>
|
||||
Phillip Webb <pwebb@vmware.com> <pwebb@gopivotal.com>
|
||||
Chris Beams <cbeams@vmware.com>
|
||||
Chris Beams <cbeams@vmware.com> <cbeams@pivotal.io>
|
||||
Chris Beams <cbeams@vmware.com> <cbeams@gopivotal.com>
|
||||
Arjen Poutsma <poutsmaa@vmware.com>
|
||||
Arjen Poutsma <poutsmaa@vmware.com> <apoutsma@pivotal.io>
|
||||
Arjen Poutsma <poutsmaa@vmware.com> <apoutsma@gopivotal.com>
|
||||
Arjen Poutsma <poutsmaa@vmware.com> <poutsma@mac.com>
|
||||
Arjen Poutsma <poutsmaa@vmware.com> <apoutsma@vmware.com>
|
||||
Oliver Drotbohm <odrotbohm@vmware.com>
|
||||
Oliver Drotbohm <odrotbohm@vmware.com> <ogierke@vmware.com>
|
||||
Oliver Drotbohm <odrotbohm@vmware.com> <ogierke@pivotal.io>
|
||||
Oliver Drotbohm <odrotbohm@vmware.com> <ogierke@gopivotal.com>
|
||||
Dave Syer <dsyer@vmware.com>
|
||||
Dave Syer <dsyer@vmware.com> <dsyer@pivotal.io>
|
||||
Dave Syer <dsyer@vmware.com> <dsyer@gopivotal.com>
|
||||
Dave Syer <dsyer@vmware.com> <david_syer@hotmail.com>
|
||||
Andy Clement <aclement@vmware.com>
|
||||
Andy Clement <aclement@vmware.com> <aclement@pivotal.io>
|
||||
Andy Clement <aclement@vmware.com> <aclement@gopivotal.com>
|
||||
Andy Clement <aclement@vmware.com> <andrew.clement@gmail.com>
|
||||
Sam Brannen <sbrannen@vmware.com>
|
||||
Sam Brannen <sbrannen@vmware.com> <sbrannen@pivotal.io>
|
||||
Sam Brannen <sbrannen@vmware.com> <sam@sambrannen.com>
|
||||
Simon Basle <sbasle@vmware.com>
|
||||
Simon Baslé <sbasle@vmware.com>
|
||||
<dmitry.katsubo@gmail.com> <dmitry.katsubo@gmai.com>
|
||||
Nick Williams <nicholas@nicholaswilliams.net>
|
||||
@@ -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.8.1-librca
|
||||
java=17.0.9-librca
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [](https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-6.0.x?groups=Build") [](https://ge.spring.io/scans?search.rootProjectNames=spring)
|
||||
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [](https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-6.1.x?groups=Build") [](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 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).
|
||||
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).
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+3
-6
@@ -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.49.0'
|
||||
id 'com.github.ben-manes.versions' version '0.50.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,6 +16,8 @@ ext {
|
||||
javaProjects = subprojects.findAll { !it.name.startsWith("framework-") }
|
||||
}
|
||||
|
||||
description = "Spring Framework"
|
||||
|
||||
configure(allprojects) { project ->
|
||||
apply plugin: "org.springframework.build.localdev"
|
||||
group = "org.springframework"
|
||||
@@ -116,8 +118,3 @@ 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'
|
||||
}
|
||||
|
||||
@@ -22,21 +22,6 @@ but doesn't affect the classpath of dependent projects.
|
||||
This plugin does not provide a `provided` configuration, as the native `compileOnly` and `testCompileOnly`
|
||||
configurations are preferred.
|
||||
|
||||
### API Diff
|
||||
|
||||
This plugin uses the [Gradle JApiCmp](https://github.com/melix/japicmp-gradle-plugin) plugin
|
||||
to generate API Diff reports for each Spring Framework module. This plugin is applied once on the root
|
||||
project and creates tasks in each framework module. Unlike previous versions of this part of the build,
|
||||
there is no need for checking out a specific tag. The plugin will fetch the JARs we want to compare the
|
||||
current working version with. You can generate the reports for all modules or a single module:
|
||||
|
||||
```
|
||||
./gradlew apiDiff -PbaselineVersion=5.1.0.RELEASE
|
||||
./gradlew :spring-core:apiDiff -PbaselineVersion=5.1.0.RELEASE
|
||||
```
|
||||
|
||||
The reports are located under `build/reports/api-diff/$OLDVERSION_to_$NEWVERSION/`.
|
||||
|
||||
|
||||
### RuntimeHints Java Agent
|
||||
|
||||
|
||||
@@ -21,18 +21,13 @@ 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 "me.champeau.gradle:japicmp-gradle-plugin:0.3.0"
|
||||
implementation "org.gradle:test-retry-gradle-plugin:1.4.1"
|
||||
implementation "org.gradle:test-retry-gradle-plugin:1.5.6"
|
||||
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,2 +1,2 @@
|
||||
org.gradle.caching=true
|
||||
javaFormatVersion=0.0.39
|
||||
javaFormatVersion=0.0.41
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -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.5");
|
||||
checkstyle.setToolVersion("10.12.7");
|
||||
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));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,142 +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.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,4 +1,4 @@
|
||||
FROM ubuntu:jammy-20231004
|
||||
FROM ubuntu:jammy-20231211.1
|
||||
|
||||
ADD setup.sh /setup.sh
|
||||
ADD get-jdk-url.sh /get-jdk-url.sh
|
||||
|
||||
@@ -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/19/GPL/openjdk-22-ea+19_linux-x64_bin.tar.gz"
|
||||
echo "https://download.java.net/java/early_access/jdk22/28/GPL/openjdk-22-ea+28_linux-x64_bin.tar.gz"
|
||||
;;
|
||||
*)
|
||||
echo $"Unknown java version"
|
||||
|
||||
@@ -255,6 +255,8 @@ 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:
|
||||
|
||||
@@ -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 \
|
||||
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Porg.gradle.java.installations.fromEnv=JDK17,JDK21,JDK22 \
|
||||
-PmainToolchain=${MAIN_TOOLCHAIN} -PtestToolchain=${TEST_TOOLCHAIN} --no-daemon --max-workers=4 check antora
|
||||
popd > /dev/null
|
||||
|
||||
@@ -27,8 +27,8 @@ javadoc {
|
||||
author = true
|
||||
header = rootProject.description
|
||||
use = true
|
||||
overview = "$rootProject.rootDir/framework-docs/src/docs/api/overview.html"
|
||||
destinationDir = file("${project.buildDir}/docs/javadoc-api")
|
||||
overview = project.relativePath("$rootProject.rootDir/framework-docs/src/docs/api/overview.html")
|
||||
destinationDir = file("$project.docsDir/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(project.file("$buildDir/docs/kdoc-api"))
|
||||
outputDirectory.set(file("$docsDir/kdoc-api"))
|
||||
includes.from("$rootProject.rootDir/framework-docs/src/docs/api/dokka-overview.md")
|
||||
}
|
||||
|
||||
|
||||
@@ -19,15 +19,53 @@ 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,6 +291,8 @@ 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,6 +533,11 @@ 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
|
||||
@@ -714,10 +719,29 @@ 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,6 +156,7 @@ Java::
|
||||
/**
|
||||
* Bean definitions for {@link DataSourceConfiguration}
|
||||
*/
|
||||
@Generated
|
||||
public class DataSourceConfiguration__BeanDefinitions {
|
||||
/**
|
||||
* Get the bean definition for 'dataSourceConfiguration'
|
||||
@@ -190,6 +191,9 @@ 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.
|
||||
|
||||
+2
@@ -155,6 +155,8 @@ 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 by default. Consider the
|
||||
following example:
|
||||
The default profile represents the profile that is enabled if no profile is active. Consider
|
||||
the following example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -558,9 +558,9 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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,6 +592,40 @@ 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,7 +324,6 @@ Kotlin::
|
||||
|
||||
|
||||
|
||||
|
||||
[[beans-factory-scopes-application]]
|
||||
=== Application Scope
|
||||
|
||||
@@ -374,7 +373,6 @@ Kotlin::
|
||||
|
||||
|
||||
|
||||
|
||||
[[beans-factory-scopes-websocket]]
|
||||
=== WebSocket Scope
|
||||
|
||||
@@ -384,7 +382,6 @@ xref:web/websocket/stomp/scope.adoc[WebSocket scope] for more details.
|
||||
|
||||
|
||||
|
||||
|
||||
[[beans-factory-scopes-other-injection]]
|
||||
=== Scoped Beans as Dependencies
|
||||
|
||||
@@ -544,6 +541,19 @@ 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. 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
|
||||
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
|
||||
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,17 +3,5 @@
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
This section covers how to use annotations in your Java code to configure the Spring
|
||||
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]
|
||||
|
||||
|
||||
container.
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
[[expressions-evaluation]]
|
||||
= Evaluation
|
||||
|
||||
This section introduces the simple use of SpEL interfaces and its expression language.
|
||||
The complete language reference can be found in
|
||||
This section introduces programmatic use of SpEL's interfaces and its expression language.
|
||||
The complete language reference can be found in the
|
||||
xref:core/expressions/language-ref.adoc[Language Reference].
|
||||
|
||||
The following code introduces the SpEL API to evaluate the literal string expression,
|
||||
`Hello World`.
|
||||
The following code demonstrates how to use 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 previously defined
|
||||
expression string. Two exceptions that can be thrown, `ParseException` and
|
||||
`EvaluationException`, when calling `parser.parseExpression` and `exp.getValue`,
|
||||
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
|
||||
defined expression string. The two types of exceptions that can be thrown when calling
|
||||
`parser.parseExpression(...)` and `exp.getValue(...)` are `ParseException` and
|
||||
`EvaluationException`, 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 example of method invocation, we call the `concat` method on the string literal:
|
||||
In the following method invocation example, we call the `concat` method on the string
|
||||
literal, `Hello World`.
|
||||
|
||||
[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,10 +67,11 @@ 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 of calling a JavaBean property calls the `String` property `Bytes`:
|
||||
The following example demonstrates how to access the `Bytes` JavaBean property of the
|
||||
string literal, `Hello World`.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -100,10 +101,10 @@ Kotlin::
|
||||
======
|
||||
|
||||
SpEL also supports nested properties by using the standard dot notation (such as
|
||||
`prop1.prop2.prop3`) and also the corresponding setting of property values.
|
||||
`prop1.prop2.prop3`) as well as 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 literal:
|
||||
The following example shows how to use dot notation to get the length of a string literal.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -133,7 +134,7 @@ Kotlin::
|
||||
======
|
||||
|
||||
The String's constructor can be called instead of using a string literal, as the following
|
||||
example shows:
|
||||
example shows.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -145,7 +146,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 make it be upper case.
|
||||
<1> Construct a new `String` from the literal and convert it to upper case.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
@@ -155,10 +156,9 @@ 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 make it be upper case.
|
||||
<1> Construct a new `String` from the literal and convert it to 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 or
|
||||
create a boolean condition:
|
||||
how to retrieve the `name` property from an instance of the `Inventor` class and how to
|
||||
reference the `name` property in a boolean expression.
|
||||
|
||||
[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,16 +252,15 @@ 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]
|
||||
======
|
||||
@@ -325,7 +324,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]
|
||||
======
|
||||
@@ -380,16 +379,25 @@ 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 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.
|
||||
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.
|
||||
|
||||
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
|
||||
@@ -402,16 +410,17 @@ 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 50000 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 50,000 iterations, it took 75ms to evaluate by using the interpreter and
|
||||
only 3ms using the compiled version of the expression.
|
||||
|
||||
|
||||
[[expressions-compiler-configuration]]
|
||||
@@ -419,33 +428,34 @@ interpreter and 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 running twice.
|
||||
since part of the expression may be run 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]
|
||||
======
|
||||
@@ -482,15 +492,16 @@ 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 these cases, it is possible to set the `spring.expression.compiler.mode`
|
||||
object. In such 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`).
|
||||
@@ -499,18 +510,14 @@ xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism) to
|
||||
[[expressions-compiler-limitations]]
|
||||
=== Compiler Limitations
|
||||
|
||||
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:
|
||||
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.
|
||||
|
||||
* Expressions involving assignment
|
||||
* Expressions relying on the conversion service
|
||||
* Expressions using custom resolvers or accessors
|
||||
* Expressions using selection or projection
|
||||
|
||||
More types of expressions will be compilable in the future.
|
||||
|
||||
|
||||
|
||||
Compilation of additional kinds of expressions may be supported in the future.
|
||||
|
||||
|
||||
@@ -2,24 +2,4 @@
|
||||
= Language Reference
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
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]
|
||||
|
||||
|
||||
|
||||
This section describes how the Spring Expression Language works.
|
||||
|
||||
+1
-1
@@ -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 the expression by using the `#variableName` syntax. Variables
|
||||
are set by using the `setVariable` method on `EvaluationContext` implementations.
|
||||
You can reference variables in an expression by using the `#variableName` syntax. Variables
|
||||
are set by using the `setVariable()` method in `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,8 +53,10 @@ 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 examples
|
||||
show how to use the `#this` and `#root` variables:
|
||||
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].
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -62,40 +64,95 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// create an array of integers
|
||||
List<Integer> primes = new ArrayList<>();
|
||||
primes.addAll(Arrays.asList(2,3,5,7,11,13,17));
|
||||
// Create a list of prime integers.
|
||||
List<Integer> primes = List.of(2, 3, 5, 7, 11, 13, 17);
|
||||
|
||||
// create parser and set variable 'primes' as the array of integers
|
||||
// Create parser and set variable 'primes' as the list of integers.
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataAccess();
|
||||
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
|
||||
context.setVariable("primes", primes);
|
||||
|
||||
// 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);
|
||||
// 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);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// create an array of integers
|
||||
val primes = ArrayList<Int>()
|
||||
primes.addAll(listOf(2, 3, 5, 7, 11, 13, 17))
|
||||
// Create a list of prime integers.
|
||||
val primes = listOf(2, 3, 5, 7, 11, 13, 17)
|
||||
|
||||
// create parser and set variable 'primes' as the array of integers
|
||||
// Create parser and set variable 'primes' as the list of integers.
|
||||
val parser = SpelExpressionParser()
|
||||
val context = SimpleEvaluationContext.forReadOnlyDataAccess()
|
||||
val context = SimpleEvaluationContext.forReadWriteDataBinding().build()
|
||||
context.setVariable("primes", primes)
|
||||
|
||||
// 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>
|
||||
// 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>
|
||||
----
|
||||
======
|
||||
|
||||
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,6 +230,19 @@ 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,6 +407,12 @@ 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,13 +506,20 @@ 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 (no XA requirement).
|
||||
read-only optimizations) against any regular JDBC connection pool, without requiring
|
||||
a JTA transaction coordinator and XA-capable resources.
|
||||
|
||||
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 the `JpaDialect` mechanism.
|
||||
`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.
|
||||
|
||||
|
||||
[[orm-jpa-dialect]]
|
||||
|
||||
@@ -440,12 +440,8 @@ 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("tuples", arrayOf(35, 50))
|
||||
.bind("ages", arrayOf(35, 50))
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
+5
@@ -38,6 +38,11 @@ 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:
|
||||
|
||||
@@ -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 - 2023 VMware, Inc. All Rights Reserved.
|
||||
Copyright © 2002 - 2024 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,8 +15,7 @@ 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 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.
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
@@ -29,6 +28,11 @@ 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=app-cds.jsa`: creates the CDS archive on exit
|
||||
* `-XX:ArchiveClassesAtExit=application.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=app-cds.jsa` to your startup
|
||||
script to use it, assuming a `app-cds.jsa` file in the working directory.
|
||||
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.
|
||||
|
||||
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.
|
||||
|`code.namespace` _(required)_|Canonical name of the class of the bean instance that holds the scheduled method, or `"ANONYMOUS"` for anonymous classes.
|
||||
|`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 client = WebClient.builder().baseUrl("https://api.github.com/").build();
|
||||
WebClientAdapter adapter = WebClientAdapter.forClient(webClient)
|
||||
WebClient webClient = WebClient.builder().baseUrl("https://api.github.com/").build();
|
||||
WebClientAdapter adapter = WebClientAdapter.create(webClient);
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
|
||||
|
||||
RepositoryService service = factory.createClient(RepositoryService.class);
|
||||
@@ -1078,11 +1078,34 @@ underlying HTTP client, which operates at a lower level and provides more contro
|
||||
|
||||
|
||||
[[rest-http-interface-exceptions]]
|
||||
=== Exception Handling
|
||||
=== Error Handling
|
||||
|
||||
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:
|
||||
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:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -1090,10 +1113,28 @@ responses performed through the client:
|
||||
.defaultStatusHandler(HttpStatusCode::isError, resp -> ...)
|
||||
.build();
|
||||
|
||||
WebClientAdapter clientAdapter = WebClientAdapter.forClient(webClient);
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory
|
||||
.builder(clientAdapter).build();
|
||||
WebClientAdapter adapter = WebClientAdapter.create(webClient);
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(adapter).build();
|
||||
----
|
||||
|
||||
For more details and options, such as suppressing error status codes, see the Javadoc of
|
||||
`defaultStatusHandler` in `WebClient.Builder`.
|
||||
|
||||
For `RestTemplate`:
|
||||
|
||||
By default, `RestTemplate` raises `RestClientException` for 4xx and 5xx HTTP status codes.
|
||||
To customize this, register an error handler that applies to all responses
|
||||
performed through the client:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
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,7 +252,9 @@ 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.
|
||||
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).
|
||||
|
||||
|
||||
|
||||
@@ -477,12 +479,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}/spring-fu[Spring Fu incubator].
|
||||
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].
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -14,8 +14,10 @@ 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,22 +10,21 @@ 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 2 project on https://start.spring.io/#!language=kotlin&type=gradle-project[start.spring.io].
|
||||
Boot project on https://start.spring.io/#!language=kotlin&type=gradle-project-kotlin[start.spring.io].
|
||||
|
||||
|
||||
|
||||
[[choosing-the-web-flavor]]
|
||||
== Choosing the Web Flavor
|
||||
|
||||
Spring Framework now comes with two different web stacks: xref:web/webmvc.adoc#mvc[Spring MVC] and
|
||||
Spring Framework 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, streaming scenarios or if you want to use the web functional
|
||||
Kotlin DSL.
|
||||
long-lived connections or streaming scenarios.
|
||||
|
||||
For other use cases, especially if you are using blocking technologies such as JPA, Spring
|
||||
MVC and its annotation-based programming model is the recommended choice.
|
||||
MVC is the recommended choice.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,15 +2,12 @@
|
||||
= Requirements
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
Spring Framework supports Kotlin 1.3+ and requires
|
||||
Spring Framework supports Kotlin 1.7+ 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,28 +18,9 @@ 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 2, WebFlux, and Reactive Spring Data MongoDB
|
||||
* https://github.com/mixitconf/mixit[mixit]: Spring Boot, 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,6 +97,9 @@ 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:
|
||||
@@ -130,7 +133,41 @@ 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:
|
||||
|
||||
* 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 { }]
|
||||
* 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 { }]
|
||||
|
||||
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
|
||||
|
||||
As of Spring Framework 5.3, {kotlin-github-org}/kotlinx.serialization[Kotlin multiplatform serialization] is
|
||||
{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 currently not supported in AOT mode.
|
||||
The `@ContextHierarchy` annotation is not supported in AOT mode.
|
||||
====
|
||||
|
||||
To provide test-specific runtime hints for use within a GraalVM native image, you have
|
||||
|
||||
+7
-5
@@ -42,13 +42,15 @@ 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.
|
||||
|
||||
Specifically, the modules that make up the testing support in Spring Framework and Spring
|
||||
For example, 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. 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.
|
||||
`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.
|
||||
|
||||
|
||||
[[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.
|
||||
|
||||
Specifically, the `spring-test` module declares all core default `TestExecutionListener`
|
||||
For example, the `spring-test` module declares all core default `TestExecutionListener`
|
||||
implementations under the `org.springframework.test.context.TestExecutionListener` key in
|
||||
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.
|
||||
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.
|
||||
|
||||
[[testcontext-tel-config-ordering]]
|
||||
== Ordering `TestExecutionListener` Implementations
|
||||
@@ -207,4 +207,3 @@ Kotlin::
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
+5
-2
@@ -198,6 +198,9 @@ 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 `@ModelAttribute`.
|
||||
|
||||
_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.
|
||||
|
||||
@@ -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 Reactive stack]#
|
||||
[.small]#xref:web/webmvc/mvc-controller/ann-requestmapping.adoc#mvc-ann-httpexchange-annotation[See equivalent in the Servlet stack]#
|
||||
|
||||
As an alternative to `@RequestMapping`, you can also handle requests with `@HttpExchange`
|
||||
methods. Such methods are declared on an
|
||||
|
||||
@@ -78,8 +78,9 @@ it does the same, but it also compares the computed value against the `If-None-M
|
||||
request header and, if the two are equal, returns a 304 (NOT_MODIFIED).
|
||||
|
||||
This strategy saves network bandwidth but not CPU, as the full response must be computed for each request.
|
||||
State-changing HTTP methods and other HTTP conditional request headers such as `If-Match` and `If-Unmodified-Since` are outside the scope of this filter.
|
||||
Other strategies at the controller level can avoid the computation and have a broader support for HTTP conditional requests.
|
||||
State-changing HTTP methods and other HTTP conditional request headers such as `If-Match` and
|
||||
`If-Unmodified-Since` are outside the scope of this filter. Other strategies at the controller level
|
||||
can avoid the computation and have a broader support for HTTP conditional requests.
|
||||
See xref:web/webmvc/mvc-caching.adoc[HTTP Caching].
|
||||
|
||||
This filter has a `writeWeakETag` parameter that configures the filter to write weak ETags
|
||||
|
||||
+6
-1
@@ -243,4 +243,9 @@ 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 `@ModelAttribute`.
|
||||
_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.
|
||||
|
||||
+1
-1
@@ -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 register a custom authentication interceptor:
|
||||
example shows how to register a custom authentication interceptor:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
|
||||
+3
-3
@@ -103,9 +103,9 @@ You can also use the WebSocket transport configuration shown earlier to configur
|
||||
maximum allowed size for incoming STOMP messages. In theory, a WebSocket
|
||||
message can be almost unlimited in size. In practice, WebSocket servers impose
|
||||
limits -- for example, 8K on Tomcat and 64K on Jetty. For this reason, STOMP clients
|
||||
(such as the JavaScript https://github.com/JSteunou/webstomp-client[webstomp-client]
|
||||
and others) split larger STOMP messages at 16K boundaries and send them as multiple
|
||||
WebSocket messages, which requires the server to buffer and re-assemble.
|
||||
such as https://github.com/stomp-js/stompjs[`stomp-js/stompjs`] and others split larger
|
||||
STOMP messages at 16K boundaries and send them as multiple WebSocket messages,
|
||||
which requires the server to buffer and re-assemble.
|
||||
|
||||
Spring's STOMP-over-WebSocket support does this ,so applications can configure the
|
||||
maximum size for STOMP messages irrespective of WebSocket server-specific message
|
||||
|
||||
@@ -7,30 +7,31 @@ javaPlatform {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
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("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("io.netty:netty5-bom:5.0.0.Alpha5"))
|
||||
api(platform("io.projectreactor:reactor-bom:2023.0.0"))
|
||||
api(platform("io.projectreactor:reactor-bom:2023.0.2"))
|
||||
api(platform("io.rsocket:rsocket-bom:1.1.3"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.15"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.17"))
|
||||
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
|
||||
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.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.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.7.0"))
|
||||
api(platform("org.mockito:mockito-bom:5.8.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.33")
|
||||
api("com.github.librepdf:openpdf:1.3.36")
|
||||
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.0")
|
||||
api("com.google.protobuf:protobuf-java-util:3.25.1")
|
||||
api("com.h2database:h2:2.2.224")
|
||||
api("com.jayway.jsonpath:json-path:2.8.0")
|
||||
api("com.rometools:rome:1.19.0")
|
||||
@@ -99,17 +100,16 @@ 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.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.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.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.4")
|
||||
api("org.junit.support:testng-engine:1.0.5")
|
||||
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.9")
|
||||
api("org.testng:testng:7.8.0")
|
||||
api("org.slf4j:slf4j-api:2.0.11")
|
||||
api("org.testng:testng:7.9.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
@@ -1,10 +1,10 @@
|
||||
version=6.1.1-SNAPSHOT
|
||||
version=6.1.3
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
org.gradle.parallel=true
|
||||
|
||||
kotlinVersion=1.9.20
|
||||
kotlinVersion=1.9.22
|
||||
|
||||
kotlin.jvm.target.validation.mode=ignore
|
||||
kotlin.stdlib.default.dependency=false
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
+18
-2
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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
|
||||
@@ -94,12 +110,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-2018 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -49,10 +49,13 @@ 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
|
||||
*/
|
||||
boolean isStatic();
|
||||
default boolean isStatic() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a target instance. Invoked immediately before the
|
||||
@@ -67,9 +70,11 @@ 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
|
||||
*/
|
||||
void releaseTarget(Object target) throws Exception;
|
||||
default void releaseTarget(Object target) throws Exception {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+17
-9
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -78,6 +78,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Juergen Hoeller
|
||||
* @author Ramnivas Laddad
|
||||
* @author Dave Syer
|
||||
* @author Yanming Zhou
|
||||
* @since 2.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
@@ -333,13 +334,15 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
||||
Object targetObject = null;
|
||||
Object thisObject = null;
|
||||
try {
|
||||
MethodInvocation mi = ExposeInvocationInterceptor.currentInvocation();
|
||||
targetObject = mi.getThis();
|
||||
if (!(mi instanceof ProxyMethodInvocation _pmi)) {
|
||||
throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
|
||||
MethodInvocation curr = ExposeInvocationInterceptor.currentInvocation();
|
||||
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();
|
||||
}
|
||||
pmi = _pmi;
|
||||
thisObject = pmi.getProxy();
|
||||
}
|
||||
catch (IllegalStateException ex) {
|
||||
// No current invocation...
|
||||
@@ -471,10 +474,11 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
||||
}
|
||||
}
|
||||
if (targetMethod != originalMethod && (shadowMatch == null ||
|
||||
(shadowMatch.neverMatches() && Proxy.isProxyClass(targetMethod.getDeclaringClass())))) {
|
||||
(Proxy.isProxyClass(targetMethod.getDeclaringClass()) &&
|
||||
(shadowMatch.neverMatches() || containsAnnotationPointcut())))) {
|
||||
// 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).
|
||||
// redeclared methods), as well as for annotation pointcuts.
|
||||
methodToMatch = originalMethod;
|
||||
try {
|
||||
shadowMatch = obtainPointcutExpression().matchesMethodExecution(methodToMatch);
|
||||
@@ -513,6 +517,10 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
||||
return shadowMatch;
|
||||
}
|
||||
|
||||
private boolean containsAnnotationPointcut() {
|
||||
return resolveExpression().contains("@annotation");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -293,7 +293,7 @@ final class InstantiationModelAwarePointcutAdvisorImpl
|
||||
@Override
|
||||
public boolean matches(Method method, Class<?> targetClass, Object... args) {
|
||||
// This can match only on declared pointcut.
|
||||
return (isAspectMaterialized() && this.declaredPointcut.matches(method, targetClass));
|
||||
return (isAspectMaterialized() && this.declaredPointcut.matches(method, targetClass, args));
|
||||
}
|
||||
|
||||
private boolean isAspectMaterialized() {
|
||||
|
||||
+10
-21
@@ -201,9 +201,8 @@ class ConfigBeanDefinitionParser implements BeanDefinitionParser {
|
||||
List<BeanReference> beanReferences = new ArrayList<>();
|
||||
|
||||
List<Element> declareParents = DomUtils.getChildElementsByTagName(aspectElement, DECLARE_PARENTS);
|
||||
for (int i = METHOD_INDEX; i < declareParents.size(); i++) {
|
||||
Element declareParentsElement = declareParents.get(i);
|
||||
beanDefinitions.add(parseDeclareParents(declareParentsElement, parserContext));
|
||||
for (Element declareParent : declareParents) {
|
||||
beanDefinitions.add(parseDeclareParents(declareParent, parserContext));
|
||||
}
|
||||
|
||||
// We have to parse "advice" and all the advice kinds in one loop, to get the
|
||||
@@ -405,24 +404,14 @@ class ConfigBeanDefinitionParser implements BeanDefinitionParser {
|
||||
*/
|
||||
private Class<?> getAdviceClass(Element adviceElement, ParserContext parserContext) {
|
||||
String elementName = parserContext.getDelegate().getLocalName(adviceElement);
|
||||
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 + "].");
|
||||
}
|
||||
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 + "].");
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+3
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -42,10 +42,8 @@ class ScopedProxyBeanDefinitionDecorator implements BeanDefinitionDecorator {
|
||||
@Override
|
||||
public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
|
||||
boolean proxyTargetClass = true;
|
||||
if (node instanceof Element ele) {
|
||||
if (ele.hasAttribute(PROXY_TARGET_CLASS)) {
|
||||
proxyTargetClass = Boolean.parseBoolean(ele.getAttribute(PROXY_TARGET_CLASS));
|
||||
}
|
||||
if (node instanceof Element ele && 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,8 +102,23 @@ 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.
|
||||
@@ -491,6 +506,7 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
*/
|
||||
protected void adviceChanged() {
|
||||
this.methodCache.clear();
|
||||
this.proxyMetadataCache = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -551,18 +567,6 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Serialization support
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
|
||||
// Rely on default serialization; just initialize state after deserialization.
|
||||
ois.defaultReadObject();
|
||||
|
||||
// Initialize transient fields.
|
||||
this.methodCache = new ConcurrentHashMap<>(32);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toProxyConfigString() {
|
||||
return toString();
|
||||
@@ -584,6 +588,19 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Serialization support
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
|
||||
// Rely on default serialization; just initialize state after deserialization.
|
||||
ois.defaultReadObject();
|
||||
|
||||
// Initialize transient fields.
|
||||
this.methodCache = new ConcurrentHashMap<>(32);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Simple wrapper class around a Method. Used as the key when
|
||||
* caching methods, for efficient equals and hashCode comparisons.
|
||||
@@ -633,7 +650,7 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
* @see #getConfigurationOnlyCopy()
|
||||
* @see #getAdvisorKey()
|
||||
*/
|
||||
private static class AdvisorKeyEntry implements Advisor {
|
||||
private static final class AdvisorKeyEntry implements Advisor {
|
||||
|
||||
private final Class<?> adviceType;
|
||||
|
||||
@@ -643,7 +660,6 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
@Nullable
|
||||
private final String methodMatcherKey;
|
||||
|
||||
|
||||
public AdvisorKeyEntry(Advisor advisor) {
|
||||
this.adviceType = advisor.getAdvice().getClass();
|
||||
if (advisor instanceof PointcutAdvisor pointcutAdvisor) {
|
||||
|
||||
@@ -38,8 +38,8 @@ abstract class CoroutinesUtils {
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
static Object awaitSingleOrNull(Object value, Object continuation) {
|
||||
return MonoKt.awaitSingleOrNull(value instanceof Mono mono ? mono : Mono.just(value),
|
||||
static Object awaitSingleOrNull(@Nullable Object value, Object continuation) {
|
||||
return MonoKt.awaitSingleOrNull(value instanceof Mono mono ? mono : Mono.justOrEmpty(value),
|
||||
(Continuation<Object>) continuation);
|
||||
}
|
||||
|
||||
|
||||
+79
-49
@@ -16,6 +16,8 @@
|
||||
|
||||
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;
|
||||
@@ -71,34 +73,16 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
|
||||
private static final long serialVersionUID = 5531744639992436476L;
|
||||
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
private static final String COROUTINES_FLOW_CLASS_NAME = "kotlinx.coroutines.flow.Flow";
|
||||
|
||||
/** 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;
|
||||
|
||||
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;
|
||||
/** Cached in {@link AdvisedSupport#proxyMetadataCache}. */
|
||||
private transient ProxiedInterfacesCache cache;
|
||||
|
||||
|
||||
/**
|
||||
@@ -110,8 +94,17 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
|
||||
public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {
|
||||
Assert.notNull(config, "AdvisedSupport must not be null");
|
||||
this.advised = config;
|
||||
this.proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
|
||||
findDefinedEqualsAndHashCodeMethods(this.proxiedInterfaces);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -125,13 +118,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.proxiedInterfaces, this);
|
||||
return Proxy.newProxyInstance(determineClassLoader(classLoader), this.cache.proxiedInterfaces, this);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public Class<?> getProxyClass(@Nullable ClassLoader classLoader) {
|
||||
return Proxy.getProxyClass(determineClassLoader(classLoader), this.proxiedInterfaces);
|
||||
return Proxy.getProxyClass(determineClassLoader(classLoader), this.cache.proxiedInterfaces);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -160,28 +153,6 @@ 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}.
|
||||
@@ -198,11 +169,11 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
|
||||
Object target = null;
|
||||
|
||||
try {
|
||||
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
|
||||
if (!this.cache.equalsDefined && AopUtils.isEqualsMethod(method)) {
|
||||
// The target does not implement the equals(Object) method itself.
|
||||
return equals(args[0]);
|
||||
}
|
||||
else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
|
||||
else if (!this.cache.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
|
||||
// The target does not implement the hashCode() method itself.
|
||||
return hashCode();
|
||||
}
|
||||
@@ -324,4 +295,63 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+7
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -58,6 +58,7 @@ import org.springframework.util.function.SingletonSupplier;
|
||||
* @author Juergen Hoeller
|
||||
* @author Stephane Nicoll
|
||||
* @author He Bo
|
||||
* @author Sebastien Deleuze
|
||||
* @since 3.1.2
|
||||
*/
|
||||
public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
|
||||
@@ -73,8 +74,6 @@ 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;
|
||||
@@ -85,6 +84,9 @@ 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}
|
||||
@@ -157,6 +159,7 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
|
||||
if (beanFactory instanceof ConfigurableBeanFactory configurableBeanFactory) {
|
||||
this.embeddedValueResolver = new EmbeddedValueResolver(configurableBeanFactory);
|
||||
}
|
||||
this.executors.clear();
|
||||
}
|
||||
|
||||
|
||||
@@ -290,7 +293,7 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
|
||||
else if (Future.class.isAssignableFrom(returnType)) {
|
||||
return executor.submit(task);
|
||||
}
|
||||
else if (void.class == returnType) {
|
||||
else if (void.class == returnType || "kotlin.Unit".equals(returnType.getName())) {
|
||||
executor.submit(task);
|
||||
return null;
|
||||
}
|
||||
|
||||
+5
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -32,7 +32,6 @@ 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
|
||||
@@ -101,10 +100,9 @@ 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);
|
||||
Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);
|
||||
final Method userDeclaredMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
|
||||
final Method userMethod = BridgeMethodResolver.getMostSpecificMethod(invocation.getMethod(), targetClass);
|
||||
|
||||
AsyncTaskExecutor executor = determineAsyncExecutor(userDeclaredMethod);
|
||||
AsyncTaskExecutor executor = determineAsyncExecutor(userMethod);
|
||||
if (executor == null) {
|
||||
throw new IllegalStateException(
|
||||
"No executor specified and no default executor set on AsyncExecutionInterceptor either");
|
||||
@@ -118,10 +116,10 @@ public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport imple
|
||||
}
|
||||
}
|
||||
catch (ExecutionException ex) {
|
||||
handleError(ex.getCause(), userDeclaredMethod, invocation.getArguments());
|
||||
handleError(ex.getCause(), userMethod, invocation.getArguments());
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
handleError(ex, userDeclaredMethod, invocation.getArguments());
|
||||
handleError(ex, userMethod, invocation.getArguments());
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -199,12 +199,11 @@ 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);
|
||||
Method resolvedMethod = ClassUtils.getMostSpecificMethod(method, specificTargetClass);
|
||||
// If we are dealing with method with generic parameters, find the original method.
|
||||
return BridgeMethodResolver.findBridgedMethod(resolvedMethod);
|
||||
return BridgeMethodResolver.getMostSpecificMethod(method, specificTargetClass);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -52,7 +52,7 @@ public class ControlFlowPointcut implements Pointcut, ClassFilter, MethodMatcher
|
||||
|
||||
/**
|
||||
* The class against which to match.
|
||||
* <p>Available for use in subclasses since 6.1.
|
||||
* @since 6.1
|
||||
*/
|
||||
protected final Class<?> clazz;
|
||||
|
||||
|
||||
-10
@@ -153,16 +153,6 @@ 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.
|
||||
|
||||
-10
@@ -72,11 +72,6 @@ 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.
|
||||
@@ -91,11 +86,6 @@ 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,13 +116,6 @@ 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,21 +66,11 @@ 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,11 +67,6 @@ 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
-16
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -73,14 +73,6 @@ 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() {
|
||||
@@ -90,13 +82,6 @@ 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() {
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -29,7 +29,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link AspectJAdviceParameterNameDiscoverer}.
|
||||
* Tests for {@link AspectJAdviceParameterNameDiscoverer}.
|
||||
*
|
||||
* @author Adrian Colyer
|
||||
* @author Chris Beams
|
||||
|
||||
+49
-25
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,7 +23,6 @@ import java.util.Map;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.aspectj.weaver.tools.PointcutExpression;
|
||||
import org.aspectj.weaver.tools.PointcutPrimitive;
|
||||
import org.aspectj.weaver.tools.UnsupportedPointcutPrimitiveException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -50,6 +49,7 @@ 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,14 +246,13 @@ public class AspectJExpressionPointcutTests {
|
||||
@Test
|
||||
public void testInvalidExpression() {
|
||||
String expression = "execution(void org.springframework.beans.testfixture.beans.TestBean.setSomeNumber(Number) && args(Double)";
|
||||
assertThatIllegalArgumentException().isThrownBy(
|
||||
getPointcut(expression)::getClassFilter); // call to getClassFilter forces resolution
|
||||
assertThatIllegalArgumentException().isThrownBy(getPointcut(expression)::getClassFilter); // call to getClassFilter forces resolution
|
||||
}
|
||||
|
||||
private TestBean getAdvisedProxy(String pointcutExpression, CallCountingInterceptor interceptor) {
|
||||
TestBean target = new TestBean();
|
||||
|
||||
Pointcut pointcut = getPointcut(pointcutExpression);
|
||||
AspectJExpressionPointcut pointcut = getPointcut(pointcutExpression);
|
||||
|
||||
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
|
||||
advisor.setAdvice(interceptor);
|
||||
@@ -277,40 +276,31 @@ public class AspectJExpressionPointcutTests {
|
||||
@Test
|
||||
public void testWithUnsupportedPointcutPrimitive() {
|
||||
String expression = "call(int org.springframework.beans.testfixture.beans.TestBean.getAge())";
|
||||
assertThatExceptionOfType(UnsupportedPointcutPrimitiveException.class).isThrownBy(() ->
|
||||
getPointcut(expression).getClassFilter()) // call to getClassFilter forces resolution...
|
||||
.satisfies(ex -> assertThat(ex.getUnsupportedPrimitive()).isEqualTo(PointcutPrimitive.CALL));
|
||||
assertThatExceptionOfType(UnsupportedPointcutPrimitiveException.class)
|
||||
.isThrownBy(() -> getPointcut(expression).getClassFilter()) // call to getClassFilter forces resolution...
|
||||
.satisfies(ex -> assertThat(ex.getUnsupportedPrimitive()).isEqualTo(PointcutPrimitive.CALL));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAndSubstitution() {
|
||||
Pointcut pc = getPointcut("execution(* *(..)) and args(String)");
|
||||
PointcutExpression expr = ((AspectJExpressionPointcut) pc).getPointcutExpression();
|
||||
assertThat(expr.getPointcutExpression()).isEqualTo("execution(* *(..)) && args(String)");
|
||||
AspectJExpressionPointcut pc = getPointcut("execution(* *(..)) and args(String)");
|
||||
String expr = pc.getPointcutExpression().getPointcutExpression();
|
||||
assertThat(expr).isEqualTo("execution(* *(..)) && args(String)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleAndSubstitutions() {
|
||||
Pointcut pc = getPointcut("execution(* *(..)) and args(String) and this(Object)");
|
||||
PointcutExpression expr = ((AspectJExpressionPointcut) pc).getPointcutExpression();
|
||||
assertThat(expr.getPointcutExpression()).isEqualTo("execution(* *(..)) && args(String) && this(Object)");
|
||||
AspectJExpressionPointcut pc = getPointcut("execution(* *(..)) and args(String) and this(Object)");
|
||||
String expr = pc.getPointcutExpression().getPointcutExpression();
|
||||
assertThat(expr).isEqualTo("execution(* *(..)) && args(String) && this(Object)");
|
||||
}
|
||||
|
||||
private Pointcut getPointcut(String expression) {
|
||||
private AspectJExpressionPointcut getPointcut(String expression) {
|
||||
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
|
||||
pointcut.setExpression(expression);
|
||||
return pointcut;
|
||||
}
|
||||
|
||||
|
||||
public static class OtherIOther implements IOther {
|
||||
|
||||
@Override
|
||||
public void absquatulate() {
|
||||
// Empty
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchGenericArgument() {
|
||||
String expression = "execution(* set*(java.util.List<org.springframework.beans.testfixture.beans.TestBean>) )";
|
||||
@@ -424,6 +414,19 @@ 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)";
|
||||
@@ -436,6 +439,18 @@ 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..*) * *(..))";
|
||||
@@ -505,6 +520,15 @@ public class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
|
||||
public static class OtherIOther implements IOther {
|
||||
|
||||
@Override
|
||||
public void absquatulate() {
|
||||
// Empty
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class HasGeneric {
|
||||
|
||||
public void setFriends(List<TestBean> friends) {
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -31,7 +31,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Unit tests for the {@link TypePatternClassFilter} class.
|
||||
* Tests for {@link TypePatternClassFilter}.
|
||||
*
|
||||
* @author Rod Johnson
|
||||
* @author Rick Evans
|
||||
|
||||
+40
-17
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -42,26 +42,38 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
*/
|
||||
class ArgumentBindingTests {
|
||||
|
||||
@Test
|
||||
void bindingInPointcutUsedByAdvice() {
|
||||
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new TestBean());
|
||||
proxyFactory.addAspect(NamedPointcutWithArgs.class);
|
||||
|
||||
ITestBean proxiedTestBean = proxyFactory.getProxy();
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> proxiedTestBean.setName("enigma"))
|
||||
.withMessage("enigma");
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationArgumentNameBinding() {
|
||||
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new TransactionalBean());
|
||||
proxyFactory.addAspect(PointcutWithAnnotationArgument.class);
|
||||
|
||||
ITransactionalBean proxiedTestBean = proxyFactory.getProxy();
|
||||
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(proxiedTestBean::doInTransaction)
|
||||
.withMessage("Invoked with @Transactional");
|
||||
.isThrownBy(proxiedTestBean::doInTransaction)
|
||||
.withMessage("Invoked with @Transactional");
|
||||
}
|
||||
|
||||
@Test
|
||||
void bindingInPointcutUsedByAdvice() {
|
||||
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new TestBean());
|
||||
proxyFactory.addAspect(NamedPointcutWithArgs.class);
|
||||
ITestBean proxiedTestBean = proxyFactory.getProxy();
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> proxiedTestBean.setName("enigma"))
|
||||
.withMessage("enigma");
|
||||
}
|
||||
|
||||
@Test
|
||||
void bindingWithDynamicAdvice() {
|
||||
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new TestBean());
|
||||
proxyFactory.addAspect(DynamicPointcutWithArgs.class);
|
||||
ITestBean proxiedTestBean = proxyFactory.getProxy();
|
||||
|
||||
proxiedTestBean.applyName(1);
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> proxiedTestBean.applyName("enigma"))
|
||||
.withMessage("enigma");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -94,6 +106,7 @@ class ArgumentBindingTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Mimics Spring's @Transactional annotation without actually introducing the dependency.
|
||||
*/
|
||||
@@ -101,16 +114,17 @@ class ArgumentBindingTests {
|
||||
@interface Transactional {
|
||||
}
|
||||
|
||||
|
||||
@Aspect
|
||||
static class PointcutWithAnnotationArgument {
|
||||
|
||||
@Around(value = "execution(* org.springframework..*.*(..)) && @annotation(transactional)")
|
||||
@Around("execution(* org.springframework..*.*(..)) && @annotation(transactional)")
|
||||
public Object around(ProceedingJoinPoint pjp, Transactional transactional) throws Throwable {
|
||||
throw new IllegalStateException("Invoked with @Transactional");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Aspect
|
||||
static class NamedPointcutWithArgs {
|
||||
|
||||
@@ -121,7 +135,16 @@ class ArgumentBindingTests {
|
||||
public Object doAround(ProceedingJoinPoint pjp, String aString) throws Throwable {
|
||||
throw new IllegalArgumentException(aString);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Aspect("pertarget(execution(* *(..)))")
|
||||
static class DynamicPointcutWithArgs {
|
||||
|
||||
@Around("execution(* *(..)) && args(java.lang.String)")
|
||||
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
|
||||
throw new IllegalArgumentException(String.valueOf(pjp.getArgs()[0]));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -354,8 +354,8 @@ public class ProxyFactoryTests {
|
||||
list.add(proxy1);
|
||||
list.add(proxy2);
|
||||
AnnotationAwareOrderComparator.sort(list);
|
||||
assertThat(list.get(0)).isSameAs(proxy2);
|
||||
assertThat(list.get(1)).isSameAs(proxy1);
|
||||
assertThat(list).element(0).isSameAs(proxy2);
|
||||
assertThat(list).element(1).isSameAs(proxy1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -370,8 +370,8 @@ public class ProxyFactoryTests {
|
||||
list.add(proxy1);
|
||||
list.add(proxy2);
|
||||
AnnotationAwareOrderComparator.sort(list);
|
||||
assertThat(list.get(0)).isSameAs(proxy2);
|
||||
assertThat(list.get(1)).isSameAs(proxy1);
|
||||
assertThat(list).element(0).isSameAs(proxy2);
|
||||
assertThat(list).element(1).isSameAs(proxy1);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -30,7 +30,7 @@ import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Unit tests for the {@link DebugInterceptor} class.
|
||||
* Tests for {@link DebugInterceptor}.
|
||||
*
|
||||
* @author Rick Evans
|
||||
* @author Chris Beams
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -29,7 +29,7 @@ import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Unit tests for the {@link SimpleTraceInterceptor} class.
|
||||
* Tests for {@link SimpleTraceInterceptor}.
|
||||
*
|
||||
* @author Rick Evans
|
||||
* @author Chris Beams
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -24,7 +24,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Unit tests for the {@link DefaultScopedObject} class.
|
||||
* Tests for {@link DefaultScopedObject}.
|
||||
*
|
||||
* @author Rick Evans
|
||||
* @author Chris Beams
|
||||
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 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.
|
||||
@@ -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")).isTrue();
|
||||
assertThat(Arrays.asList(bf.getBeanNamesForType(TestBean.class, true, false)).contains("scoped")).isTrue();
|
||||
assertThat(Arrays.asList(bf.getBeanNamesForType(TestBean.class, false, false))).contains("scoped");
|
||||
assertThat(Arrays.asList(bf.getBeanNamesForType(TestBean.class, true, false))).contains("scoped");
|
||||
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")).isTrue();
|
||||
assertThat(Arrays.asList(bf.getBeanNamesForType(TestBean.class, false, false)).contains("scoped")).isTrue();
|
||||
assertThat(Arrays.asList(bf.getBeanNamesForType(TestBean.class, true, false))).contains("scoped");
|
||||
assertThat(Arrays.asList(bf.getBeanNamesForType(TestBean.class, false, false))).contains("scoped");
|
||||
assertThat(bf.containsSingleton("scoped")).isFalse();
|
||||
TestBean autowired = (TestBean) bf.getBean("autowired");
|
||||
TestBean scoped = (TestBean) bf.getBean("scoped");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,7 +22,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ScopedProxyUtils}.
|
||||
* Tests for {@link ScopedProxyUtils}.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 5.1.10
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -29,7 +29,7 @@ import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ClassFilters}.
|
||||
* Tests for {@link ClassFilters}.
|
||||
*
|
||||
* @author Rod Johnson
|
||||
* @author Chris Beams
|
||||
|
||||
+1
-1
@@ -141,7 +141,7 @@ public class ComposablePointcutTests {
|
||||
|
||||
pc1.intersection(GETTER_METHOD_MATCHER);
|
||||
|
||||
assertThat(pc1.equals(pc2)).isFalse();
|
||||
assertThat(pc1).isNotEqualTo(pc2);
|
||||
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.equals(second)).isTrue();
|
||||
assertThat(second.equals(first)).isTrue();
|
||||
assertThat(first).isEqualTo(second);
|
||||
assertThat(second).isEqualTo(first);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -24,7 +24,7 @@ import org.springframework.beans.testfixture.beans.ITestBean;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link RootClassFilter}.
|
||||
* Tests for {@link RootClassFilter}.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 5.1.10
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -25,7 +25,7 @@ import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link AnnotationMatchingPointcut}.
|
||||
* Tests for {@link AnnotationMatchingPointcut}.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 5.1.10
|
||||
|
||||
+14
-13
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 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.
|
||||
@@ -34,7 +34,7 @@ import static org.springframework.core.testfixture.io.ResourceTestUtils.qualifie
|
||||
* @author Chris Beams
|
||||
* @since 07.01.2005
|
||||
*/
|
||||
public class LazyInitTargetSourceTests {
|
||||
class LazyInitTargetSourceTests {
|
||||
|
||||
private static final Class<?> CLASS = LazyInitTargetSourceTests.class;
|
||||
|
||||
@@ -42,9 +42,11 @@ public 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
|
||||
public void testLazyInitSingletonTargetSource() {
|
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||
void lazyInitSingletonTargetSource() {
|
||||
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(SINGLETON_CONTEXT);
|
||||
bf.preInstantiateSingletons();
|
||||
|
||||
@@ -55,8 +57,7 @@ public class LazyInitTargetSourceTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomLazyInitSingletonTargetSource() {
|
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||
void customLazyInitSingletonTargetSource() {
|
||||
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(CUSTOM_TARGET_CONTEXT);
|
||||
bf.preInstantiateSingletons();
|
||||
|
||||
@@ -67,25 +68,25 @@ public class LazyInitTargetSourceTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLazyInitFactoryBeanTargetSource() {
|
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||
@SuppressWarnings("unchecked")
|
||||
void lazyInitFactoryBeanTargetSource() {
|
||||
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(FACTORY_BEAN_CONTEXT);
|
||||
bf.preInstantiateSingletons();
|
||||
|
||||
Set<?> set1 = (Set<?>) bf.getBean("proxy1");
|
||||
Set<Object> set1 = (Set<Object>) bf.getBean("proxy1");
|
||||
assertThat(bf.containsSingleton("target1")).isFalse();
|
||||
assertThat(set1.contains("10")).isTrue();
|
||||
assertThat(set1).contains("10");
|
||||
assertThat(bf.containsSingleton("target1")).isTrue();
|
||||
|
||||
Set<?> set2 = (Set<?>) bf.getBean("proxy2");
|
||||
Set<Object> set2 = (Set<Object>) bf.getBean("proxy2");
|
||||
assertThat(bf.containsSingleton("target2")).isFalse();
|
||||
assertThat(set2.contains("20")).isTrue();
|
||||
assertThat(set2).contains("20");
|
||||
assertThat(bf.containsSingleton("target2")).isTrue();
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public static class CustomLazyInitTargetSource extends LazyInitTargetSource {
|
||||
static class CustomLazyInitTargetSource extends LazyInitTargetSource {
|
||||
|
||||
@Override
|
||||
protected void postProcessTargetObject(Object targetObject) {
|
||||
|
||||
+2
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -75,14 +75,9 @@ public class PrototypeBasedTargetSourceTests {
|
||||
private TestBean thisFieldIsNotSerializable = new TestBean();
|
||||
|
||||
@Override
|
||||
public Object getTarget() throws Exception {
|
||||
public Object getTarget() {
|
||||
return newPrototypeInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releaseTarget(Object target) throws Exception {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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
Reference in New Issue
Block a user