mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
249 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ba590ac9e4 | |||
| ee62701f56 | |||
| fa168ca78a | |||
| 3c228a5c1d | |||
| 9bf6b8cddf | |||
| 37ecdd1437 | |||
| 73f1c5a189 | |||
| 4d296fb4ca | |||
| 6a9444473f | |||
| 03ae97b2eb | |||
| e34cdc2a55 | |||
| 8b1b9ef9e5 | |||
| efdaae02e0 | |||
| 49ffb833a3 | |||
| 065e50a444 | |||
| 7135527765 | |||
| 65e30132f3 | |||
| ebb44a8368 | |||
| 3096bb6d0c | |||
| 2b2a57b4a3 | |||
| 3295289e17 | |||
| 457e876303 | |||
| 021bf6e77d | |||
| 90453643cc | |||
| 5a2cbc1ab3 | |||
| 348b4cd067 | |||
| 9b52cfd7d5 | |||
| dd6eede243 | |||
| de97e35189 | |||
| 45a2c51fe1 | |||
| b98c3257af | |||
| ea8ae09cb7 | |||
| 0abfad870c | |||
| 5a9af9e024 | |||
| 0867dfca33 | |||
| b4355dc955 | |||
| bc91e0ea96 | |||
| 8599ee6c2b | |||
| bc466022b1 | |||
| 6f11711e27 | |||
| c067919173 | |||
| ac773d97e9 | |||
| bd7007227c | |||
| e8f873a349 | |||
| b943817f3e | |||
| 4466548f53 | |||
| 9c183f9e77 | |||
| 03620fc530 | |||
| 1c108054ee | |||
| d0b186a1c7 | |||
| 4172581f1b | |||
| c88ba6c90e | |||
| d7c13d6518 | |||
| 5013d6d771 | |||
| 9cd7b6a91e | |||
| e9dcd64068 | |||
| ce7f47c962 | |||
| ef34464c94 | |||
| ef11a00c0b | |||
| 176b0b09bf | |||
| c48ff357dc | |||
| 190dabb8e1 | |||
| d15abd58b4 | |||
| 44500cf868 | |||
| e384389790 | |||
| 49e5c84928 | |||
| 56eb135608 | |||
| 5c5cf73e11 | |||
| 858c2bd270 | |||
| 124582d910 | |||
| 253f321e8b | |||
| 0252e39409 | |||
| 2f60083cd5 | |||
| b83e07ff8c | |||
| 1841ad3472 | |||
| 0477ba4de4 | |||
| 8c376e9cc5 | |||
| 018d3c9ef2 | |||
| c70741f60d | |||
| f40d98668d | |||
| 9c13c6b695 | |||
| b49924ba37 | |||
| 7b8c104077 | |||
| 8f62a8f579 | |||
| d0966dfb58 | |||
| bb45a3ae69 | |||
| 7095f4cb66 | |||
| a22d204681 | |||
| f27382cfb6 | |||
| 6bc196883a | |||
| c4f66b776f | |||
| 87e04df983 | |||
| 4d648f8b5d | |||
| 6ea9f66fd7 | |||
| eea6addd26 | |||
| 7f2c1f447f | |||
| 26869b0e4c | |||
| cd987fc104 | |||
| 3f9402a56b | |||
| c168e1c297 | |||
| 3afd551174 | |||
| ffd15155ee | |||
| 74ab5e4e25 | |||
| 463541967a | |||
| 4510b78dfd | |||
| 63f4ba4b2a | |||
| 0c7bc232d6 | |||
| 470bf3b0bb | |||
| 2ca9f6f064 | |||
| ecd8cd797e | |||
| cc5ae23915 | |||
| dbd47ff4f9 | |||
| 381bc4c405 | |||
| 0f2308e85f | |||
| ee804ee8fb | |||
| 4e5979c75a | |||
| e7db15b325 | |||
| 8f9cbcd86d | |||
| 6bb964e2d0 | |||
| a946fe2bf8 | |||
| 671d972454 | |||
| d9047d39e6 | |||
| 4db12806d1 | |||
| 290c9c4a19 | |||
| e01ad5a08d | |||
| b8158df3d6 | |||
| c4e25a1162 | |||
| 48009c8534 | |||
| 203ca30a64 | |||
| 34ea0461c7 | |||
| fbaeaf12bd | |||
| 7b08feeb6d | |||
| 743f32675d | |||
| 3ddc607b3e | |||
| f68fb97e7e | |||
| 044258f085 | |||
| 36d9357f94 | |||
| dcb9383ba1 | |||
| f8a3077da9 | |||
| 9fd1d0c6a3 | |||
| 30fcaef813 | |||
| 75e5a75da5 | |||
| 9bf01df230 | |||
| 8d2166139f | |||
| 374c3b4545 | |||
| d7e470d3e0 | |||
| 2862c87601 | |||
| aa56b5001a | |||
| 84430a8db2 | |||
| 6905dff660 | |||
| 20736bd06f | |||
| 37fb79e8ff | |||
| d8f8e76791 | |||
| dc41ff569e | |||
| 47651350f3 | |||
| 15c20c3e65 | |||
| 5455c645f0 | |||
| adb4b675fc | |||
| 208d52d852 | |||
| 18c3b637e4 | |||
| 34c69bfc67 | |||
| 37d7af42ac | |||
| cc986cd2e8 | |||
| 0141725638 | |||
| 0c2ba4e38e | |||
| c6a9aa59a3 | |||
| ad949a7450 | |||
| 86b2617c7f | |||
| 760376c318 | |||
| 5b6abe4c13 | |||
| 7a839e988a | |||
| 46859d6391 | |||
| 573e74b8bd | |||
| 6c231804a0 | |||
| 7c3913050a | |||
| ec488282a8 | |||
| 911cdb2ad0 | |||
| 0f83c483bb | |||
| c9050607bc | |||
| 5e82ee6bd7 | |||
| 387677eae8 | |||
| 4a314867d7 | |||
| 020f556841 | |||
| 6ea3b5a0e8 | |||
| d764087dbf | |||
| 9ab43b138a | |||
| 09ae080b99 | |||
| 70a1b2fae3 | |||
| 90ddb40d7a | |||
| 143985e862 | |||
| 4bd280b87e | |||
| 5877a38fa1 | |||
| 94d29bac9f | |||
| a6f3f18d89 | |||
| 8334cb1abb | |||
| 2d88f18bf1 | |||
| 15a6641677 | |||
| 63c8e7cb5d | |||
| c5ecc50bfe | |||
| 0fd94f1b9f | |||
| 2472126475 | |||
| f3b50c9a58 | |||
| 0a6e666857 | |||
| 403362fe3c | |||
| 534f123fe8 | |||
| e421104cf3 | |||
| adfeba23e0 | |||
| 7789e12575 | |||
| 30d793cefe | |||
| 78cc5df748 | |||
| 5ffaea7a43 | |||
| 1633ad24f3 | |||
| 5faf2ed7f4 | |||
| d2733cea36 | |||
| 108caea385 | |||
| 7e9ac120ac | |||
| c64dae3623 | |||
| 559ea6c480 | |||
| 34315fc20f | |||
| 3bb4795d43 | |||
| aae2952a32 | |||
| f62251aebd | |||
| f92f9c1d5b | |||
| 2b38c00656 | |||
| 06721ba6c0 | |||
| b6a5402d88 | |||
| aff9ac72ec | |||
| f895d762cd | |||
| 634d1dd20b | |||
| 725b02a66d | |||
| 5a0bd9e5d4 | |||
| 6dd73ae3e4 | |||
| 4606337180 | |||
| f45a08d4f5 | |||
| d62ce2969a | |||
| ed3fd12210 | |||
| 6f596e2e84 | |||
| 6786e1c3e5 | |||
| 93134fd4d1 | |||
| d0ceefedc6 | |||
| e230ea537c | |||
| 94eb6006e8 | |||
| 2576702cda | |||
| 2099e046d3 | |||
| 2ee7a8e77a | |||
| 3c40e5e501 | |||
| 9e45178202 | |||
| d82e70e345 | |||
| 1109892134 |
@@ -15,7 +15,7 @@ runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Generate Changelog
|
||||
uses: spring-io/github-changelog-generator@185319ad7eaa75b0e8e72e4b6db19c8b2cb8c4c1 #v0.0.11
|
||||
uses: spring-io/github-changelog-generator@86958813a62af8fb223b3fd3b5152035504bcb83 #v0.0.12
|
||||
with:
|
||||
config-file: .github/actions/create-github-release/changelog-generator.yml
|
||||
milestone: ${{ inputs.milestone }}
|
||||
|
||||
+2
-1
@@ -42,7 +42,8 @@ spring-*/src/main/java/META-INF/MANIFEST.MF
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea
|
||||
.idea/*
|
||||
!.idea/icon.svg
|
||||
out
|
||||
test-output
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
Generated
+52
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 510 510" style="enable-background:new 0 0 510 510;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#6DB33F;}
|
||||
</style>
|
||||
<title>icon-framework</title>
|
||||
<g id="Layer_2_1_">
|
||||
<g id="Layer_1-2">
|
||||
<path class="st0" d="M479.2,162.5c-5.6-34.3-20.5-64.4-43.9-87.8S382,36.5,347.6,30.9C320.1,11.2,288.5,0,255,0
|
||||
s-65.1,11.2-92.6,30.9C128,36.5,98,51.3,74.7,74.7C50.9,98.4,36.5,128.7,31,162.2C11.2,189.8,0,221.4,0,255s11.2,65.2,31,92.8
|
||||
c5.5,33.5,19.9,63.8,43.7,87.5c23.3,23.3,53.3,38.2,87.7,43.8C190,498.8,221.5,510,255,510s65-11.2,92.6-30.9
|
||||
c34.4-5.6,64.4-20.5,87.7-43.8c23.8-23.8,38.1-54.1,43.7-87.5c19.8-27.6,31-59.2,31-92.8S498.8,190,479.2,162.5z M473,224.2
|
||||
c-0.8,6-1.9,11.9-3.2,17.9c-7.2-22.4-17.1-43.8-29.4-63.8c-5.5-22.8-13.6-44.9-24.4-65.8c22.9,14.8,42.2,32.6,56.7,52.6
|
||||
C475.7,184.6,475.8,204.6,473,224.2z M444.7,199.6c9.3,17.7,16.6,36.3,21.9,55.6c-5.3,19.3-12.7,37.9-22,55.6
|
||||
C450.9,274,451,236.4,444.7,199.6L444.7,199.6z M175.4,341.1c6.5-4.5,13.7-7.8,21.3-9.8c32.2-7.5,46.7-9.9,66.3-18.4
|
||||
c36.8-15.9,74.8-53.3,84.6-94c-16.1,38.8-59.5,70.2-97.9,81.6c-26.2,7.8-79.8,13.6-80.1,13.6l-1.4-1.2
|
||||
c-30.6-17.4-27-85.8,31.8-104.1c25.7-8,55.9-0.7,83.5-5.4c29.4-5,64.4-24.5,79.9-52c12.3,46.4,26.1,118.5-9.7,160.2
|
||||
C309.4,363,211.8,318,175.4,341.1z M310.8,444.6c-17.7,9.4-36.5,16.8-55.8,22.1c-19.4-5.3-38.1-12.7-55.8-22.1
|
||||
C236.1,450.9,273.9,450.9,310.8,444.6z M117.6,392.4c13.1,13.1,27.3,25.1,42.3,35.8c-19-5.9-37.2-13.8-54.5-23.7
|
||||
c-9.9-17.3-17.8-35.7-23.7-54.7C92.4,365.1,104.5,379.3,117.6,392.4z M81.7,160.1c5.9-19,13.9-37.4,23.7-54.7
|
||||
c17.3-9.8,35.5-17.8,54.5-23.7c-15.1,10.8-29.2,22.7-42.3,35.8C104.5,130.7,92.4,144.9,81.7,160.1z M149.1,346.4
|
||||
c-8.5,0.2-15.5-6.5-15.7-15c-0.2-8.5,6.5-15.5,15-15.7c8.5-0.2,15.5,6.5,15.7,15c0,0,0,0.1,0,0.1
|
||||
C164.2,339.3,157.5,346.2,149.1,346.4L149.1,346.4z M199.2,65.4c17.7-9.4,36.5-16.8,55.8-22.1c19.4,5.3,38.1,12.7,55.8,22.1
|
||||
C273.9,59.1,236.1,59.1,199.2,65.4L199.2,65.4z M392.4,392.4c13.1-13.1,25.1-27.3,35.9-42.4c-5.9,19-13.9,37.3-23.7,54.5
|
||||
c-17.3,9.8-35.5,17.7-54.5,23.7C365.2,417.5,379.3,405.5,392.4,392.4L392.4,392.4z M392.4,117.6c-13.1-13.1-27.2-25.1-42.3-35.8
|
||||
c19,5.9,37.2,13.9,54.5,23.7c9.8,17.3,17.8,35.6,23.7,54.7C417.6,144.9,405.5,130.7,392.4,117.6L392.4,117.6z M430.5,79.5
|
||||
c19.4,19.3,32.6,43.6,39.3,71.1c-16.2-19.2-36.6-36.2-60.4-50c-13.8-23.8-30.8-44.2-50-60.4C387.1,46.9,411.2,60.1,430.5,79.5
|
||||
L430.5,79.5z M397.6,94c-20.8-10.7-42.8-18.8-65.6-24.2c-20-12.4-41.5-22.4-63.9-29.6c15.5-3.4,31.3-5.1,47.2-5.2
|
||||
c10,0,19.9,0.8,29.7,2.3C365.1,51.8,382.8,71.1,397.6,94z M255,6.8c27.6,0,53.8,8,77.4,22.2c-5.6-0.5-11.3-0.7-17.1-0.7
|
||||
c-20.4,0.1-40.6,2.8-60.3,8.1c-19.7-5.3-39.9-8-60.3-8.1c-5.8,0-11.5,0.2-17.1,0.7C201.2,14.7,227.4,6.8,255,6.8z M165,37.3
|
||||
c9.8-1.5,19.8-2.3,29.7-2.3c15.9,0.1,31.7,1.8,47.2,5.2C219.5,47.4,198,57.3,178,69.8c-22.7,5.4-44.8,13.6-65.6,24.2
|
||||
C127.2,71.1,144.9,51.8,165,37.3z M79.5,79.5c19.3-19.3,43.5-32.6,71.1-39.3c-19.2,16.2-36.2,36.6-50,60.4
|
||||
c-23.7,13.8-44,30.6-60.2,49.8C47,123.4,59.8,99.1,79.5,79.5z M37.4,164.9c14.6-20,33.8-37.7,56.6-52.4
|
||||
c-10.7,20.9-18.8,43-24.3,65.8c-12.3,20-22.2,41.4-29.4,63.8C34.6,215.4,33.5,189.3,37.4,164.9z M65.4,310.4
|
||||
c-9.3-17.6-16.6-36.2-21.9-55.4c5.3-19.2,12.6-37.8,21.9-55.4C59.1,236.3,59.1,273.7,65.4,310.4L65.4,310.4z M29.2,332.8
|
||||
c-14.4-23.7-22.4-50-22.4-77.8s8.1-54.1,22.4-77.8c-2.1,25,0.4,51.3,7.4,77.8C29.5,281.5,27,307.8,29.2,332.8z M40.3,268
|
||||
c7.2,22.3,17.1,43.7,29.4,63.7c5.5,22.8,13.6,44.9,24.3,65.8c-22.8-14.7-42-32.5-56.6-52.4C33.5,320.7,34.6,294.6,40.3,268z
|
||||
M79.5,430.5c-19.6-19.6-32.5-43.9-39.1-70.9c16.1,19.1,36.5,36,60.2,49.8c13.8,23.7,30.8,44.2,50,60.4
|
||||
C122.9,463.1,98.8,449.9,79.5,430.5z M112.5,416c20.8,10.7,42.8,18.8,65.5,24.2c20,12.4,41.5,22.4,63.9,29.6
|
||||
c-15.5,3.4-31.3,5.1-47.2,5.1c-10,0-19.9-0.8-29.7-2.3C144.9,458.1,127.2,438.9,112.5,416L112.5,416z M255,503.2
|
||||
c-27.6,0-53.8-8-77.4-22.2c5.6,0.5,11.3,0.7,17.1,0.7c20.4-0.1,40.6-2.8,60.3-8.1c19.7,5.3,39.9,8,60.3,8.1
|
||||
c5.8,0,11.5-0.2,17.1-0.7C308.8,495.3,282.6,503.2,255,503.2z M345,472.7c-9.8,1.5-19.8,2.3-29.7,2.3c-15.9-0.1-31.7-1.8-47.2-5.1
|
||||
c22.4-7.2,43.9-17.2,63.9-29.6c22.7-5.4,44.8-13.6,65.6-24.2C382.8,438.9,365.1,458.1,345,472.7z M430.5,430.5
|
||||
c-19.3,19.3-43.5,32.6-71.1,39.3c19.2-16.2,36.2-36.6,50-60.4c23.7-13.8,44-30.6,60.2-49.8C463.1,386.6,450.1,410.9,430.5,430.5
|
||||
L430.5,430.5z M472.6,345.1c-14.6,20-33.8,37.7-56.6,52.4c10.7-20.8,18.8-42.8,24.3-65.6c12.3-20,22.2-41.3,29.5-63.7
|
||||
C475.4,294.7,476.5,320.8,472.6,345.1L472.6,345.1z M473.5,255.3c2.6-9.9,4.7-20,6.1-30.1c2.2-15.7,2.7-31.7,1.4-47.5
|
||||
c14.2,23.6,22.2,49.8,22.2,77.4s-8.1,54.1-22.4,77.8C482.9,307.9,480.5,281.6,473.5,255.3L473.5,255.3z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
+2
-2
@@ -9,7 +9,7 @@ plugins {
|
||||
id 'io.github.goooler.shadow' version '8.1.8' apply false
|
||||
id 'me.champeau.jmh' version '0.7.2' apply false
|
||||
id 'me.champeau.mrjar' version '0.1.1'
|
||||
id "net.ltgt.errorprone" version "3.1.0" apply false
|
||||
id "net.ltgt.errorprone" version "4.1.0" apply false
|
||||
}
|
||||
|
||||
ext {
|
||||
@@ -102,7 +102,7 @@ configure([rootProject] + javaProjects) { project ->
|
||||
// TODO Uncomment link to JUnit 5 docs once we execute Gradle with Java 18+.
|
||||
// See https://github.com/spring-projects/spring-framework/issues/27497
|
||||
//
|
||||
// "https://junit.org/junit5/docs/5.11.4/api/",
|
||||
// "https://junit.org/junit5/docs/5.12.2/api/",
|
||||
"https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/",
|
||||
//"https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/",
|
||||
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
org.gradle.caching=true
|
||||
javaFormatVersion=0.0.42
|
||||
javaFormatVersion=0.0.43
|
||||
|
||||
@@ -50,7 +50,7 @@ public class CheckstyleConventions {
|
||||
project.getPlugins().apply(CheckstylePlugin.class);
|
||||
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g"));
|
||||
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
|
||||
checkstyle.setToolVersion("10.21.2");
|
||||
checkstyle.setToolVersion("10.23.1");
|
||||
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
|
||||
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
|
||||
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
|
||||
@@ -64,7 +64,7 @@ public class CheckstyleConventions {
|
||||
NoHttpExtension noHttp = project.getExtensions().getByType(NoHttpExtension.class);
|
||||
noHttp.setAllowlistFile(project.file("src/nohttp/allowlist.lines"));
|
||||
noHttp.getSource().exclude("**/test-output/**", "**/.settings/**",
|
||||
"**/.classpath", "**/.project", "**/.gradle/**", "**/node_modules/**");
|
||||
"**/.classpath", "**/.project", "**/.gradle/**", "**/node_modules/**", "buildSrc/build/**");
|
||||
List<String> buildFolders = List.of("bin", "build", "out");
|
||||
project.allprojects(subproject -> {
|
||||
Path rootPath = project.getRootDir().toPath();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -21,6 +21,8 @@ import java.util.Map;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.plugins.JavaBasePlugin;
|
||||
import org.gradle.api.tasks.testing.Test;
|
||||
import org.gradle.api.tasks.testing.TestFrameworkOptions;
|
||||
import org.gradle.api.tasks.testing.junitplatform.JUnitPlatformOptions;
|
||||
import org.gradle.testretry.TestRetryPlugin;
|
||||
import org.gradle.testretry.TestRetryTaskExtension;
|
||||
|
||||
@@ -34,6 +36,7 @@ import org.gradle.testretry.TestRetryTaskExtension;
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Andy Wilkinson
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
class TestConventions {
|
||||
|
||||
@@ -50,7 +53,12 @@ class TestConventions {
|
||||
}
|
||||
|
||||
private void configureTests(Project project, Test test) {
|
||||
test.useJUnitPlatform();
|
||||
TestFrameworkOptions existingOptions = test.getOptions();
|
||||
test.useJUnitPlatform(options -> {
|
||||
if (existingOptions instanceof JUnitPlatformOptions junitPlatformOptions) {
|
||||
options.copyFrom(junitPlatformOptions);
|
||||
}
|
||||
});
|
||||
test.include("**/*Tests.class", "**/*Test.class");
|
||||
test.setSystemProperties(Map.of(
|
||||
"java.awt.headless", "true",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
plugins {
|
||||
id 'java-platform'
|
||||
id 'io.freefair.aggregate-javadoc' version '8.3'
|
||||
id 'io.freefair.aggregate-javadoc' version '8.13.1'
|
||||
}
|
||||
|
||||
description = "Spring Framework API Docs"
|
||||
@@ -21,6 +21,7 @@ dependencies {
|
||||
|
||||
javadoc {
|
||||
title = "${rootProject.description} ${version} API"
|
||||
failOnError = true
|
||||
options {
|
||||
encoding = "UTF-8"
|
||||
memberLevel = JavadocMemberLevel.PROTECTED
|
||||
@@ -31,8 +32,9 @@ javadoc {
|
||||
destinationDir = project.java.docsDir.dir("javadoc-api").get().asFile
|
||||
splitIndex = true
|
||||
links(rootProject.ext.javadocLinks)
|
||||
addBooleanOption('Xdoclint:syntax,reference', true) // only check syntax and reference with doclint
|
||||
addBooleanOption('Werror', true) // fail build on Javadoc warnings
|
||||
// Check for 'syntax' and 'reference' during linting.
|
||||
addBooleanOption('Xdoclint:syntax,reference', true)
|
||||
addBooleanOption('Werror', true) // fail build on Javadoc warnings
|
||||
}
|
||||
maxMemory = "1024m"
|
||||
doFirst {
|
||||
|
||||
@@ -42,33 +42,34 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(project(":spring-aspects"))
|
||||
api(project(":spring-context"))
|
||||
api(project(":spring-context-support"))
|
||||
api(project(":spring-jdbc"))
|
||||
api(project(":spring-jms"))
|
||||
api(project(":spring-test"))
|
||||
api(project(":spring-web"))
|
||||
api(project(":spring-webflux"))
|
||||
api(project(":spring-webmvc"))
|
||||
api(project(":spring-websocket"))
|
||||
|
||||
api("com.fasterxml.jackson.core:jackson-databind")
|
||||
api("com.fasterxml.jackson.module:jackson-module-parameter-names")
|
||||
api("com.mchange:c3p0:0.9.5.5")
|
||||
api("com.oracle.database.jdbc:ojdbc11")
|
||||
api("io.projectreactor.netty:reactor-netty-http")
|
||||
api("jakarta.jms:jakarta.jms-api")
|
||||
api("jakarta.servlet:jakarta.servlet-api")
|
||||
api("jakarta.resource:jakarta.resource-api")
|
||||
api("jakarta.validation:jakarta.validation-api")
|
||||
api("javax.cache:cache-api")
|
||||
api("org.apache.activemq:activemq-ra:6.1.2")
|
||||
api("org.apache.commons:commons-dbcp2:2.11.0")
|
||||
api("org.aspectj:aspectjweaver")
|
||||
api("org.eclipse.jetty.websocket:jetty-websocket-jetty-api")
|
||||
api("org.jetbrains.kotlin:kotlin-stdlib")
|
||||
|
||||
implementation(project(":spring-aspects"))
|
||||
implementation(project(":spring-context"))
|
||||
implementation(project(":spring-context-support"))
|
||||
implementation(project(":spring-core-test"))
|
||||
implementation(project(":spring-jdbc"))
|
||||
implementation(project(":spring-jms"))
|
||||
implementation(project(":spring-test"))
|
||||
implementation(project(":spring-web"))
|
||||
implementation(project(":spring-webflux"))
|
||||
implementation(project(":spring-webmvc"))
|
||||
implementation(project(":spring-websocket"))
|
||||
|
||||
implementation("com.fasterxml.jackson.core:jackson-databind")
|
||||
implementation("com.fasterxml.jackson.module:jackson-module-parameter-names")
|
||||
implementation("com.mchange:c3p0:0.9.5.5")
|
||||
implementation("com.oracle.database.jdbc:ojdbc11")
|
||||
implementation("io.projectreactor.netty:reactor-netty-http")
|
||||
implementation("jakarta.jms:jakarta.jms-api")
|
||||
implementation("jakarta.servlet:jakarta.servlet-api")
|
||||
implementation("jakarta.resource:jakarta.resource-api")
|
||||
implementation("jakarta.validation:jakarta.validation-api")
|
||||
implementation("jakarta.websocket:jakarta.websocket-client-api")
|
||||
implementation("javax.cache:cache-api")
|
||||
implementation("org.apache.activemq:activemq-ra:6.1.2")
|
||||
implementation("org.apache.commons:commons-dbcp2:2.11.0")
|
||||
implementation("org.aspectj:aspectjweaver")
|
||||
implementation("org.assertj:assertj-core")
|
||||
implementation("org.eclipse.jetty.websocket:jetty-websocket-jetty-api")
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib")
|
||||
implementation("org.junit.jupiter:junit-jupiter-api")
|
||||
}
|
||||
|
||||
@@ -92,11 +92,25 @@ the repeated JNDI lookup overhead. See
|
||||
{spring-framework-api}++/jndi/JndiLocatorDelegate.html#IGNORE_JNDI_PROPERTY_NAME++[`JndiLocatorDelegate`]
|
||||
for details.
|
||||
|
||||
| `spring.locking.strict`
|
||||
| Instructs Spring to enforce strict locking during bean creation, rather than the mix of
|
||||
strict and lenient locking that 6.2 applies by default. See
|
||||
{spring-framework-api}++/beans/factory/support/DefaultListableBeanFactory.html#STRICT_LOCKING_PROPERTY_NAME++[`DefaultListableBeanFactory`]
|
||||
for details.
|
||||
|
||||
| `spring.objenesis.ignore`
|
||||
| Instructs Spring to ignore Objenesis, not even attempting to use it. See
|
||||
{spring-framework-api}++/objenesis/SpringObjenesis.html#IGNORE_OBJENESIS_PROPERTY_NAME++[`SpringObjenesis`]
|
||||
for details.
|
||||
|
||||
| `spring.placeholder.escapeCharacter.default`
|
||||
| The default escape character for property placeholder support. If not set, `'\'` will
|
||||
be used. Can be set to a custom escape character or an empty string to disable support
|
||||
for an escape character. The default escape character be explicitly overridden in
|
||||
`PropertySourcesPlaceholderConfigurer` and subclasses of `AbstractPropertyResolver`. See
|
||||
{spring-framework-api}++/core/env/AbstractPropertyResolver.html#DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME++[`AbstractPropertyResolver`]
|
||||
for details.
|
||||
|
||||
| `spring.test.aot.processing.failOnError`
|
||||
| A boolean flag that controls whether errors encountered during AOT processing in the
|
||||
_Spring TestContext Framework_ should result in an exception that fails the overall process.
|
||||
|
||||
@@ -469,20 +469,20 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(ClientFactoryBean.class);
|
||||
beanDefinition.setTargetType(ResolvableType.forClassWithGenerics(ClientFactoryBean.class, MyClient.class));
|
||||
// ...
|
||||
registry.registerBeanDefinition("myClient", beanDefinition);
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(ClientFactoryBean.class);
|
||||
beanDefinition.setTargetType(ResolvableType.forClassWithGenerics(ClientFactoryBean.class, MyClient.class));
|
||||
// ...
|
||||
registry.registerBeanDefinition("myClient", beanDefinition);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val beanDefinition = RootBeanDefinition(ClientFactoryBean::class.java)
|
||||
beanDefinition.setTargetType(ResolvableType.forClassWithGenerics(ClientFactoryBean::class.java, MyClient::class.java));
|
||||
// ...
|
||||
registry.registerBeanDefinition("myClient", beanDefinition)
|
||||
val beanDefinition = RootBeanDefinition(ClientFactoryBean::class.java)
|
||||
beanDefinition.setTargetType(ResolvableType.forClassWithGenerics(ClientFactoryBean::class.java, MyClient::class.java));
|
||||
// ...
|
||||
registry.registerBeanDefinition("myClient", beanDefinition)
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -444,9 +444,9 @@ through Java 8's `java.util.Optional`, as the following example shows:
|
||||
}
|
||||
----
|
||||
|
||||
You can also use a `@Nullable` annotation (of any kind in any package -- for example,
|
||||
`javax.annotation.Nullable` from JSR-305) or just leverage Kotlin built-in null-safety
|
||||
support:
|
||||
You can also use a parameter-level `@Nullable` annotation (of any kind in any package --
|
||||
for example, `javax.annotation.Nullable` from JSR-305) or just leverage Kotlin built-in
|
||||
null-safety support:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -477,6 +477,13 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
A type-level `@Nullable` annotation such as from JSpecify is not supported in Spring
|
||||
Framework 6.2 yet. You need to upgrade to Spring Framework 7.0 where the framework
|
||||
detects type-level annotations and consistently declares JSpecify in its own codebase.
|
||||
====
|
||||
|
||||
You can also use `@Autowired` for interfaces that are well-known resolvable
|
||||
dependencies: `BeanFactory`, `ApplicationContext`, `Environment`, `ResourceLoader`,
|
||||
`ApplicationEventPublisher`, and `MessageSource`. These interfaces and their extended
|
||||
|
||||
+47
-44
@@ -9,15 +9,15 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
public class MovieRecommender {
|
||||
@Component
|
||||
public class MovieRecommender {
|
||||
|
||||
private final String catalog;
|
||||
private final String catalog;
|
||||
|
||||
public MovieRecommender(@Value("${catalog.name}") String catalog) {
|
||||
this.catalog = catalog;
|
||||
}
|
||||
}
|
||||
public MovieRecommender(@Value("${catalog.name}") String catalog) {
|
||||
this.catalog = catalog;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
@@ -37,9 +37,9 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@PropertySource("classpath:application.properties")
|
||||
public class AppConfig { }
|
||||
@Configuration
|
||||
@PropertySource("classpath:application.properties")
|
||||
public class AppConfig { }
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
@@ -56,7 +56,7 @@ And the following `application.properties` file:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
catalog.name=MovieCatalog
|
||||
catalog.name=MovieCatalog
|
||||
----
|
||||
|
||||
In that case, the `catalog` parameter and field will be equal to the `MovieCatalog` value.
|
||||
@@ -101,8 +101,11 @@ NOTE: When configuring a `PropertySourcesPlaceholderConfigurer` using JavaConfig
|
||||
|
||||
Using the above configuration ensures Spring initialization failure if any `${}`
|
||||
placeholder could not be resolved. It is also possible to use methods like
|
||||
`setPlaceholderPrefix`, `setPlaceholderSuffix`, `setValueSeparator`, or
|
||||
`setEscapeCharacter` to customize placeholders.
|
||||
`setPlaceholderPrefix()`, `setPlaceholderSuffix()`, `setValueSeparator()`, or
|
||||
`setEscapeCharacter()` to customize the placeholder syntax. In addition, the default
|
||||
escape character can be changed or disabled globally by setting the
|
||||
`spring.placeholder.escapeCharacter.default` property via a JVM system property (or via
|
||||
the xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism).
|
||||
|
||||
NOTE: Spring Boot configures by default a `PropertySourcesPlaceholderConfigurer` bean that
|
||||
will get properties from `application.properties` and `application.yml` files.
|
||||
@@ -119,15 +122,15 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
public class MovieRecommender {
|
||||
@Component
|
||||
public class MovieRecommender {
|
||||
|
||||
private final String catalog;
|
||||
private final String catalog;
|
||||
|
||||
public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
|
||||
this.catalog = catalog;
|
||||
}
|
||||
}
|
||||
public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
|
||||
this.catalog = catalog;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
@@ -150,16 +153,16 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
|
||||
@Bean
|
||||
public ConversionService conversionService() {
|
||||
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
|
||||
conversionService.addConverter(new MyCustomConverter());
|
||||
return conversionService;
|
||||
}
|
||||
}
|
||||
@Bean
|
||||
public ConversionService conversionService() {
|
||||
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
|
||||
conversionService.addConverter(new MyCustomConverter());
|
||||
return conversionService;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
@@ -188,15 +191,15 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
public class MovieRecommender {
|
||||
@Component
|
||||
public class MovieRecommender {
|
||||
|
||||
private final String catalog;
|
||||
private final String catalog;
|
||||
|
||||
public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
|
||||
this.catalog = catalog;
|
||||
}
|
||||
}
|
||||
public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
|
||||
this.catalog = catalog;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
@@ -217,16 +220,16 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Component
|
||||
public class MovieRecommender {
|
||||
@Component
|
||||
public class MovieRecommender {
|
||||
|
||||
private final Map<String, Integer> countOfMoviesPerCatalog;
|
||||
private final Map<String, Integer> countOfMoviesPerCatalog;
|
||||
|
||||
public MovieRecommender(
|
||||
@Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
|
||||
this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
|
||||
}
|
||||
}
|
||||
public MovieRecommender(
|
||||
@Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
|
||||
this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
|
||||
@@ -119,7 +119,7 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val context = ClassPathXmlApplicationContext("services.xml", "daos.xml")
|
||||
val context = ClassPathXmlApplicationContext("services.xml", "daos.xml")
|
||||
----
|
||||
======
|
||||
|
||||
@@ -310,16 +310,16 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import org.springframework.beans.factory.getBean
|
||||
import org.springframework.beans.factory.getBean
|
||||
|
||||
// create and configure beans
|
||||
val context = ClassPathXmlApplicationContext("services.xml", "daos.xml")
|
||||
val context = ClassPathXmlApplicationContext("services.xml", "daos.xml")
|
||||
|
||||
// retrieve configured instance
|
||||
val service = context.getBean<PetStoreService>("petStore")
|
||||
// retrieve configured instance
|
||||
val service = context.getBean<PetStoreService>("petStore")
|
||||
|
||||
// use configured instance
|
||||
var userList = service.getUsernameList()
|
||||
// use configured instance
|
||||
var userList = service.getUsernameList()
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -513,7 +513,7 @@ the classes above:
|
||||
<property name="notificationAddress" value="blockedlist@example.org"/>
|
||||
</bean>
|
||||
|
||||
<!-- optional: a custom ApplicationEventMulticaster definition -->
|
||||
<!-- optional: a custom ApplicationEventMulticaster definition -->
|
||||
<bean id="applicationEventMulticaster" class="org.springframework.context.event.SimpleApplicationEventMulticaster">
|
||||
<property name="taskExecutor" ref="..."/>
|
||||
<property name="errorHandler" ref="..."/>
|
||||
|
||||
@@ -226,7 +226,7 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import org.springframework.beans.factory.getBean
|
||||
import org.springframework.beans.factory.getBean
|
||||
|
||||
fun main() {
|
||||
val ctx = ClassPathXmlApplicationContext("scripting/beans.xml")
|
||||
@@ -314,7 +314,7 @@ Thus, marking it for lazy initialization will be ignored, and the
|
||||
|
||||
|
||||
[[beans-factory-placeholderconfigurer]]
|
||||
=== Example: The Class Name Substitution `PropertySourcesPlaceholderConfigurer`
|
||||
=== Example: Property Placeholder Substitution with `PropertySourcesPlaceholderConfigurer`
|
||||
|
||||
You can use the `PropertySourcesPlaceholderConfigurer` to externalize property values
|
||||
from a bean definition in a separate file by using the standard Java `Properties` format.
|
||||
@@ -341,8 +341,8 @@ with placeholder values is defined:
|
||||
|
||||
The example shows properties configured from an external `Properties` file. At runtime,
|
||||
a `PropertySourcesPlaceholderConfigurer` is applied to the metadata that replaces some
|
||||
properties of the DataSource. The values to replace are specified as placeholders of the
|
||||
form pass:q[`${property-name}`], which follows the Ant and log4j and JSP EL style.
|
||||
properties of the `DataSource`. The values to replace are specified as placeholders of the
|
||||
form pass:q[`${property-name}`], which follows the Ant, log4j, and JSP EL style.
|
||||
|
||||
The actual values come from another file in the standard Java `Properties` format:
|
||||
|
||||
@@ -355,11 +355,15 @@ jdbc.password=root
|
||||
----
|
||||
|
||||
Therefore, the `${jdbc.username}` string is replaced at runtime with the value, 'sa', and
|
||||
the same applies for other placeholder values that match keys in the properties file.
|
||||
The `PropertySourcesPlaceholderConfigurer` checks for placeholders in most properties and
|
||||
attributes of a bean definition. Furthermore, you can customize the placeholder prefix and suffix.
|
||||
the same applies for other placeholder values that match keys in the properties file. The
|
||||
`PropertySourcesPlaceholderConfigurer` checks for placeholders in most properties and
|
||||
attributes of a bean definition. Furthermore, you can customize the placeholder prefix,
|
||||
suffix, default value separator, and escape character. In addition, the default escape
|
||||
character can be changed or disabled globally by setting the
|
||||
`spring.placeholder.escapeCharacter.default` property via a JVM system property (or via
|
||||
the xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism).
|
||||
|
||||
With the `context` namespace introduced in Spring 2.5, you can configure property placeholders
|
||||
With the `context` namespace, you can configure property placeholders
|
||||
with a dedicated configuration element. You can provide one or more locations as a
|
||||
comma-separated list in the `location` attribute, as the following example shows:
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ Java::
|
||||
----
|
||||
public class MyBean {
|
||||
private final Log log = LogFactory.getLog(getClass());
|
||||
// ...
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
@@ -40,8 +40,8 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
class MyBean {
|
||||
private val log = LogFactory.getLog(javaClass)
|
||||
// ...
|
||||
private val log = LogFactory.getLog(javaClass)
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
@@ -399,7 +399,7 @@ A `ConstraintViolation` on the `degrees` method parameter is adapted to a
|
||||
`MessageSourceResolvable` with the following:
|
||||
|
||||
- Error codes `"Max.myService#addStudent.degrees"`, `"Max.degrees"`, `"Max.int"`, `"Max"`
|
||||
- Message arguments "degrees2 and 2 (the field name and the constraint attribute)
|
||||
- Message arguments "degrees" and 2 (the field name and the constraint attribute)
|
||||
- Default message "must be less than or equal to 2"
|
||||
|
||||
To customize the above default message, you can add a property such as:
|
||||
|
||||
@@ -78,27 +78,27 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
public class DataSourceConfig {
|
||||
@Configuration
|
||||
public class DataSourceConfig {
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
return new EmbeddedDatabaseBuilder()
|
||||
.setDatabaseConfigurer(EmbeddedDatabaseConfigurers
|
||||
.customizeConfigurer(H2, this::customize))
|
||||
.addScript("schema.sql")
|
||||
.build();
|
||||
}
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
return new EmbeddedDatabaseBuilder()
|
||||
.setDatabaseConfigurer(EmbeddedDatabaseConfigurers
|
||||
.customizeConfigurer(H2, this::customize))
|
||||
.addScript("schema.sql")
|
||||
.build();
|
||||
}
|
||||
|
||||
private EmbeddedDatabaseConfigurer customize(EmbeddedDatabaseConfigurer defaultConfigurer) {
|
||||
return new EmbeddedDatabaseConfigurerDelegate(defaultConfigurer) {
|
||||
@Override
|
||||
public void configureConnectionProperties(ConnectionProperties properties, String databaseName) {
|
||||
super.configureConnectionProperties(properties, databaseName);
|
||||
properties.setDriverClass(CustomDriver.class);
|
||||
}
|
||||
};
|
||||
}
|
||||
private EmbeddedDatabaseConfigurer customize(EmbeddedDatabaseConfigurer defaultConfigurer) {
|
||||
return new EmbeddedDatabaseConfigurerDelegate(defaultConfigurer) {
|
||||
@Override
|
||||
public void configureConnectionProperties(ConnectionProperties properties, String databaseName) {
|
||||
super.configureConnectionProperties(properties, databaseName);
|
||||
properties.setDriverClass(CustomDriver.class);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
Mono<Void> completion = client.sql("CREATE TABLE person (id VARCHAR(255) PRIMARY KEY, name VARCHAR(255), age INTEGER);")
|
||||
.then();
|
||||
.then();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
@@ -144,7 +144,7 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
client.sql("CREATE TABLE person (id VARCHAR(255) PRIMARY KEY, name VARCHAR(255), age INTEGER);")
|
||||
.await()
|
||||
.await()
|
||||
----
|
||||
======
|
||||
|
||||
@@ -173,7 +173,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
Mono<Map<String, Object>> first = client.sql("SELECT id, name FROM person")
|
||||
.fetch().first();
|
||||
.fetch().first();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
@@ -181,7 +181,7 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val first = client.sql("SELECT id, name FROM person")
|
||||
.fetch().awaitSingle()
|
||||
.fetch().awaitSingle()
|
||||
----
|
||||
======
|
||||
|
||||
@@ -194,8 +194,8 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
Mono<Map<String, Object>> first = client.sql("SELECT id, name FROM person WHERE first_name = :fn")
|
||||
.bind("fn", "Joe")
|
||||
.fetch().first();
|
||||
.bind("fn", "Joe")
|
||||
.fetch().first();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
@@ -203,8 +203,8 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val first = client.sql("SELECT id, name FROM person WHERE first_name = :fn")
|
||||
.bind("fn", "Joe")
|
||||
.fetch().awaitSingle()
|
||||
.bind("fn", "Joe")
|
||||
.fetch().awaitSingle()
|
||||
----
|
||||
======
|
||||
|
||||
@@ -240,8 +240,8 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
Flux<String> names = client.sql("SELECT name FROM person")
|
||||
.map(row -> row.get("name", String.class))
|
||||
.all();
|
||||
.map(row -> row.get("name", String.class))
|
||||
.all();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
@@ -249,8 +249,8 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val names = client.sql("SELECT name FROM person")
|
||||
.map{ row: Row -> row.get("name", String.class) }
|
||||
.flow()
|
||||
.map{ row: Row -> row.get("name", String.class) }
|
||||
.flow()
|
||||
----
|
||||
======
|
||||
|
||||
@@ -301,8 +301,8 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
Mono<Integer> affectedRows = client.sql("UPDATE person SET first_name = :fn")
|
||||
.bind("fn", "Joe")
|
||||
.fetch().rowsUpdated();
|
||||
.bind("fn", "Joe")
|
||||
.fetch().rowsUpdated();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
@@ -310,8 +310,8 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val affectedRows = client.sql("UPDATE person SET first_name = :fn")
|
||||
.bind("fn", "Joe")
|
||||
.fetch().awaitRowsUpdated()
|
||||
.bind("fn", "Joe")
|
||||
.fetch().awaitRowsUpdated()
|
||||
----
|
||||
======
|
||||
|
||||
@@ -337,9 +337,9 @@ The following example shows parameter binding for a query:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
|
||||
.bind("id", "joe")
|
||||
.bind("name", "Joe")
|
||||
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
|
||||
.bind("id", "joe")
|
||||
.bind("name", "Joe")
|
||||
.bind("age", 34);
|
||||
----
|
||||
|
||||
@@ -369,9 +369,9 @@ Indices are zero based.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
|
||||
.bind(0, "joe")
|
||||
.bind(1, "Joe")
|
||||
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
|
||||
.bind(0, "joe")
|
||||
.bind(1, "Joe")
|
||||
.bind(2, 34);
|
||||
----
|
||||
|
||||
@@ -379,9 +379,9 @@ In case your application is binding to many parameters, the same can be achieved
|
||||
|
||||
[source,java]
|
||||
----
|
||||
List<?> values = List.of("joe", "Joe", 34);
|
||||
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
|
||||
.bindValues(values);
|
||||
List<?> values = List.of("joe", "Joe", 34);
|
||||
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
|
||||
.bindValues(values);
|
||||
----
|
||||
|
||||
|
||||
@@ -428,7 +428,7 @@ Java::
|
||||
tuples.add(new Object[] {"Ann", 50});
|
||||
|
||||
client.sql("SELECT id, name, state FROM table WHERE (name, age) IN (:tuples)")
|
||||
.bind("tuples", tuples);
|
||||
.bind("tuples", tuples);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
@@ -440,7 +440,7 @@ Kotlin::
|
||||
tuples.add(arrayOf("Ann", 50))
|
||||
|
||||
client.sql("SELECT id, name, state FROM table WHERE (name, age) IN (:tuples)")
|
||||
.bind("tuples", tuples)
|
||||
.bind("tuples", tuples)
|
||||
----
|
||||
======
|
||||
|
||||
@@ -455,7 +455,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
client.sql("SELECT id, name, state FROM table WHERE age IN (:ages)")
|
||||
.bind("ages", Arrays.asList(35, 50));
|
||||
.bind("ages", Arrays.asList(35, 50));
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
@@ -463,7 +463,7 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
client.sql("SELECT id, name, state FROM table WHERE age IN (:ages)")
|
||||
.bind("ages", arrayOf(35, 50))
|
||||
.bind("ages", arrayOf(35, 50))
|
||||
----
|
||||
======
|
||||
|
||||
@@ -490,9 +490,9 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
|
||||
.filter((s, next) -> next.execute(s.returnGeneratedValues("id")))
|
||||
.bind("name", …)
|
||||
.bind("state", …);
|
||||
.filter((s, next) -> next.execute(s.returnGeneratedValues("id")))
|
||||
.bind("name", …)
|
||||
.bind("state", …);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
@@ -516,10 +516,10 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
|
||||
.filter(statement -> s.returnGeneratedValues("id"));
|
||||
.filter(statement -> s.returnGeneratedValues("id"));
|
||||
|
||||
client.sql("SELECT id, name, state FROM table")
|
||||
.filter(statement -> s.fetchSize(25));
|
||||
.filter(statement -> s.fetchSize(25));
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
@@ -527,10 +527,10 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
|
||||
.filter { statement -> s.returnGeneratedValues("id") }
|
||||
.filter { statement -> s.returnGeneratedValues("id") }
|
||||
|
||||
client.sql("SELECT id, name, state FROM table")
|
||||
.filter { statement -> s.fetchSize(25) }
|
||||
.filter { statement -> s.fetchSize(25) }
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -55,11 +55,11 @@ a "shared objects file" source, as shown in the following example:
|
||||
|
||||
[source,shell,indent=0,subs="verbatim"]
|
||||
----
|
||||
[0.064s][info][class,load] org.springframework.core.env.EnvironmentCapable source: shared objects file (top)
|
||||
[0.064s][info][class,load] org.springframework.beans.factory.BeanFactory source: shared objects file (top)
|
||||
[0.064s][info][class,load] org.springframework.beans.factory.ListableBeanFactory source: shared objects file (top)
|
||||
[0.064s][info][class,load] org.springframework.beans.factory.HierarchicalBeanFactory source: shared objects file (top)
|
||||
[0.065s][info][class,load] org.springframework.context.MessageSource source: shared objects file (top)
|
||||
[0.064s][info][class,load] org.springframework.core.env.EnvironmentCapable source: shared objects file (top)
|
||||
[0.064s][info][class,load] org.springframework.beans.factory.BeanFactory source: shared objects file (top)
|
||||
[0.064s][info][class,load] org.springframework.beans.factory.ListableBeanFactory source: shared objects file (top)
|
||||
[0.064s][info][class,load] org.springframework.beans.factory.HierarchicalBeanFactory source: shared objects file (top)
|
||||
[0.065s][info][class,load] org.springframework.context.MessageSource source: shared objects file (top)
|
||||
----
|
||||
|
||||
If CDS can't be enabled or if you have a large number of classes that are not loaded from the cache, make sure that
|
||||
|
||||
@@ -30,36 +30,36 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
RestClient defaultClient = RestClient.create();
|
||||
|
||||
RestClient customClient = RestClient.builder()
|
||||
.requestFactory(new HttpComponentsClientHttpRequestFactory())
|
||||
.messageConverters(converters -> converters.add(new MyCustomMessageConverter()))
|
||||
.baseUrl("https://example.com")
|
||||
.defaultUriVariables(Map.of("variable", "foo"))
|
||||
.defaultHeader("My-Header", "Foo")
|
||||
.defaultCookie("My-Cookie", "Bar")
|
||||
.requestInterceptor(myCustomInterceptor)
|
||||
.requestInitializer(myCustomInitializer)
|
||||
.build();
|
||||
RestClient defaultClient = RestClient.create();
|
||||
|
||||
RestClient customClient = RestClient.builder()
|
||||
.requestFactory(new HttpComponentsClientHttpRequestFactory())
|
||||
.messageConverters(converters -> converters.add(new MyCustomMessageConverter()))
|
||||
.baseUrl("https://example.com")
|
||||
.defaultUriVariables(Map.of("variable", "foo"))
|
||||
.defaultHeader("My-Header", "Foo")
|
||||
.defaultCookie("My-Cookie", "Bar")
|
||||
.requestInterceptor(myCustomInterceptor)
|
||||
.requestInitializer(myCustomInitializer)
|
||||
.build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
val defaultClient = RestClient.create()
|
||||
|
||||
val customClient = RestClient.builder()
|
||||
.requestFactory(HttpComponentsClientHttpRequestFactory())
|
||||
.messageConverters { converters -> converters.add(MyCustomMessageConverter()) }
|
||||
.baseUrl("https://example.com")
|
||||
.defaultUriVariables(mapOf("variable" to "foo"))
|
||||
.defaultHeader("My-Header", "Foo")
|
||||
.defaultCookie("My-Cookie", "Bar")
|
||||
.requestInterceptor(myCustomInterceptor)
|
||||
.requestInitializer(myCustomInitializer)
|
||||
.build()
|
||||
val defaultClient = RestClient.create()
|
||||
|
||||
val customClient = RestClient.builder()
|
||||
.requestFactory(HttpComponentsClientHttpRequestFactory())
|
||||
.messageConverters { converters -> converters.add(MyCustomMessageConverter()) }
|
||||
.baseUrl("https://example.com")
|
||||
.defaultUriVariables(mapOf("variable" to "foo"))
|
||||
.defaultHeader("My-Header", "Foo")
|
||||
.defaultCookie("My-Cookie", "Bar")
|
||||
.requestInterceptor(myCustomInterceptor)
|
||||
.requestInitializer(myCustomInitializer)
|
||||
.build()
|
||||
----
|
||||
======
|
||||
|
||||
@@ -81,20 +81,20 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
int id = 42;
|
||||
restClient.get()
|
||||
.uri("https://example.com/orders/{id}", id)
|
||||
....
|
||||
int id = 42;
|
||||
restClient.get()
|
||||
.uri("https://example.com/orders/{id}", id)
|
||||
// ...
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val id = 42
|
||||
restClient.get()
|
||||
.uri("https://example.com/orders/{id}", id)
|
||||
...
|
||||
val id = 42
|
||||
restClient.get()
|
||||
.uri("https://example.com/orders/{id}", id)
|
||||
// ...
|
||||
----
|
||||
======
|
||||
|
||||
@@ -133,12 +133,12 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
String result = restClient.get() <1>
|
||||
.uri("https://example.com") <2>
|
||||
.retrieve() <3>
|
||||
.body(String.class); <4>
|
||||
|
||||
System.out.println(result); <5>
|
||||
String result = restClient.get() <1>
|
||||
.uri("https://example.com") <2>
|
||||
.retrieve() <3>
|
||||
.body(String.class); <4>
|
||||
|
||||
System.out.println(result); <5>
|
||||
----
|
||||
<1> Set up a GET request
|
||||
<2> Specify the URL to connect to
|
||||
@@ -150,12 +150,12 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val result= restClient.get() <1>
|
||||
.uri("https://example.com") <2>
|
||||
.retrieve() <3>
|
||||
.body<String>() <4>
|
||||
|
||||
println(result) <5>
|
||||
val result= restClient.get() <1>
|
||||
.uri("https://example.com") <2>
|
||||
.retrieve() <3>
|
||||
.body<String>() <4>
|
||||
|
||||
println(result) <5>
|
||||
----
|
||||
<1> Set up a GET request
|
||||
<2> Specify the URL to connect to
|
||||
@@ -172,14 +172,14 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
ResponseEntity<String> result = restClient.get() <1>
|
||||
.uri("https://example.com") <1>
|
||||
.retrieve()
|
||||
.toEntity(String.class); <2>
|
||||
|
||||
System.out.println("Response status: " + result.getStatusCode()); <3>
|
||||
System.out.println("Response headers: " + result.getHeaders()); <3>
|
||||
System.out.println("Contents: " + result.getBody()); <3>
|
||||
ResponseEntity<String> result = restClient.get() <1>
|
||||
.uri("https://example.com") <1>
|
||||
.retrieve()
|
||||
.toEntity(String.class); <2>
|
||||
|
||||
System.out.println("Response status: " + result.getStatusCode()); <3>
|
||||
System.out.println("Response headers: " + result.getHeaders()); <3>
|
||||
System.out.println("Contents: " + result.getBody()); <3>
|
||||
----
|
||||
<1> Set up a GET request for the specified URL
|
||||
<2> Convert the response into a `ResponseEntity`
|
||||
@@ -189,14 +189,14 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val result = restClient.get() <1>
|
||||
.uri("https://example.com") <1>
|
||||
.retrieve()
|
||||
.toEntity<String>() <2>
|
||||
|
||||
println("Response status: " + result.statusCode) <3>
|
||||
println("Response headers: " + result.headers) <3>
|
||||
println("Contents: " + result.body) <3>
|
||||
val result = restClient.get() <1>
|
||||
.uri("https://example.com") <1>
|
||||
.retrieve()
|
||||
.toEntity<String>() <2>
|
||||
|
||||
println("Response status: " + result.statusCode) <3>
|
||||
println("Response headers: " + result.headers) <3>
|
||||
println("Contents: " + result.body) <3>
|
||||
----
|
||||
<1> Set up a GET request for the specified URL
|
||||
<2> Convert the response into a `ResponseEntity`
|
||||
@@ -212,12 +212,12 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
int id = ...;
|
||||
Pet pet = restClient.get()
|
||||
.uri("https://petclinic.example.com/pets/{id}", id) <1>
|
||||
.accept(APPLICATION_JSON) <2>
|
||||
.retrieve()
|
||||
.body(Pet.class); <3>
|
||||
int id = ...;
|
||||
Pet pet = restClient.get()
|
||||
.uri("https://petclinic.example.com/pets/{id}", id) <1>
|
||||
.accept(APPLICATION_JSON) <2>
|
||||
.retrieve()
|
||||
.body(Pet.class); <3>
|
||||
----
|
||||
<1> Using URI variables
|
||||
<2> Set the `Accept` header to `application/json`
|
||||
@@ -227,12 +227,12 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val id = ...
|
||||
val pet = restClient.get()
|
||||
.uri("https://petclinic.example.com/pets/{id}", id) <1>
|
||||
.accept(APPLICATION_JSON) <2>
|
||||
.retrieve()
|
||||
.body<Pet>() <3>
|
||||
val id = ...
|
||||
val pet = restClient.get()
|
||||
.uri("https://petclinic.example.com/pets/{id}", id) <1>
|
||||
.accept(APPLICATION_JSON) <2>
|
||||
.retrieve()
|
||||
.body<Pet>() <3>
|
||||
----
|
||||
<1> Using URI variables
|
||||
<2> Set the `Accept` header to `application/json`
|
||||
@@ -247,13 +247,13 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
Pet pet = ... <1>
|
||||
ResponseEntity<Void> response = restClient.post() <2>
|
||||
.uri("https://petclinic.example.com/pets/new") <2>
|
||||
.contentType(APPLICATION_JSON) <3>
|
||||
.body(pet) <4>
|
||||
.retrieve()
|
||||
.toBodilessEntity(); <5>
|
||||
Pet pet = ... <1>
|
||||
ResponseEntity<Void> response = restClient.post() <2>
|
||||
.uri("https://petclinic.example.com/pets/new") <2>
|
||||
.contentType(APPLICATION_JSON) <3>
|
||||
.body(pet) <4>
|
||||
.retrieve()
|
||||
.toBodilessEntity(); <5>
|
||||
----
|
||||
<1> Create a `Pet` domain object
|
||||
<2> Set up a POST request, and the URL to connect to
|
||||
@@ -265,13 +265,13 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val pet: Pet = ... <1>
|
||||
val response = restClient.post() <2>
|
||||
.uri("https://petclinic.example.com/pets/new") <2>
|
||||
.contentType(APPLICATION_JSON) <3>
|
||||
.body(pet) <4>
|
||||
.retrieve()
|
||||
.toBodilessEntity() <5>
|
||||
val pet: Pet = ... <1>
|
||||
val response = restClient.post() <2>
|
||||
.uri("https://petclinic.example.com/pets/new") <2>
|
||||
.contentType(APPLICATION_JSON) <3>
|
||||
.body(pet) <4>
|
||||
.retrieve()
|
||||
.toBodilessEntity() <5>
|
||||
----
|
||||
<1> Create a `Pet` domain object
|
||||
<2> Set up a POST request, and the URL to connect to
|
||||
@@ -291,13 +291,13 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
String result = restClient.get() <1>
|
||||
.uri("https://example.com/this-url-does-not-exist") <1>
|
||||
.retrieve()
|
||||
.onStatus(HttpStatusCode::is4xxClientError, (request, response) -> { <2>
|
||||
throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()); <3>
|
||||
})
|
||||
.body(String.class);
|
||||
String result = restClient.get() <1>
|
||||
.uri("https://example.com/this-url-does-not-exist") <1>
|
||||
.retrieve()
|
||||
.onStatus(HttpStatusCode::is4xxClientError, (request, response) -> { <2>
|
||||
throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()); <3>
|
||||
})
|
||||
.body(String.class);
|
||||
----
|
||||
<1> Create a GET request for a URL that returns a 404 status code
|
||||
<2> Set up a status handler for all 4xx status codes
|
||||
@@ -307,12 +307,12 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val result = restClient.get() <1>
|
||||
.uri("https://example.com/this-url-does-not-exist") <1>
|
||||
.retrieve()
|
||||
.onStatus(HttpStatusCode::is4xxClientError) { _, response -> <2>
|
||||
throw MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()) } <3>
|
||||
.body<String>()
|
||||
val result = restClient.get() <1>
|
||||
.uri("https://example.com/this-url-does-not-exist") <1>
|
||||
.retrieve()
|
||||
.onStatus(HttpStatusCode::is4xxClientError) { _, response -> <2>
|
||||
throw MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()) } <3>
|
||||
.body<String>()
|
||||
----
|
||||
<1> Create a GET request for a URL that returns a 404 status code
|
||||
<2> Set up a status handler for all 4xx status codes
|
||||
@@ -330,18 +330,18 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
Pet result = restClient.get()
|
||||
.uri("https://petclinic.example.com/pets/{id}", id)
|
||||
.accept(APPLICATION_JSON)
|
||||
.exchange((request, response) -> { <1>
|
||||
if (response.getStatusCode().is4xxClientError()) { <2>
|
||||
throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()); <2>
|
||||
}
|
||||
else {
|
||||
Pet pet = convertResponse(response); <3>
|
||||
return pet;
|
||||
}
|
||||
});
|
||||
Pet result = restClient.get()
|
||||
.uri("https://petclinic.example.com/pets/{id}", id)
|
||||
.accept(APPLICATION_JSON)
|
||||
.exchange((request, response) -> { <1>
|
||||
if (response.getStatusCode().is4xxClientError()) { <2>
|
||||
throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()); <2>
|
||||
}
|
||||
else {
|
||||
Pet pet = convertResponse(response); <3>
|
||||
return pet;
|
||||
}
|
||||
});
|
||||
----
|
||||
<1> `exchange` provides the request and response
|
||||
<2> Throw an exception when the response has a 4xx status code
|
||||
@@ -351,17 +351,17 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val result = restClient.get()
|
||||
.uri("https://petclinic.example.com/pets/{id}", id)
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.exchange { request, response -> <1>
|
||||
if (response.getStatusCode().is4xxClientError()) { <2>
|
||||
throw MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()) <2>
|
||||
} else {
|
||||
val pet: Pet = convertResponse(response) <3>
|
||||
pet
|
||||
}
|
||||
}
|
||||
val result = restClient.get()
|
||||
.uri("https://petclinic.example.com/pets/{id}", id)
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.exchange { request, response -> <1>
|
||||
if (response.getStatusCode().is4xxClientError()) { <2>
|
||||
throw MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()) <2>
|
||||
} else {
|
||||
val pet: Pet = convertResponse(response) <3>
|
||||
pet
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> `exchange` provides the request and response
|
||||
<2> Throw an exception when the response has a 4xx status code
|
||||
@@ -380,15 +380,14 @@ To serialize only a subset of the object properties, you can specify a {baeldung
|
||||
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
MappingJacksonValue value = new MappingJacksonValue(new User("eric", "7!jd#h23"));
|
||||
value.setSerializationView(User.WithoutPasswordView.class);
|
||||
|
||||
ResponseEntity<Void> response = restClient.post() // or RestTemplate.postForEntity
|
||||
.contentType(APPLICATION_JSON)
|
||||
.body(value)
|
||||
.retrieve()
|
||||
.toBodilessEntity();
|
||||
|
||||
MappingJacksonValue value = new MappingJacksonValue(new User("eric", "7!jd#h23"));
|
||||
value.setSerializationView(User.WithoutPasswordView.class);
|
||||
|
||||
ResponseEntity<Void> response = restClient.post() // or RestTemplate.postForEntity
|
||||
.contentType(APPLICATION_JSON)
|
||||
.body(value)
|
||||
.retrieve()
|
||||
.toBodilessEntity();
|
||||
----
|
||||
|
||||
==== Multipart
|
||||
@@ -398,24 +397,24 @@ For example:
|
||||
|
||||
[source,java,indent=0,subs="verbatim"]
|
||||
----
|
||||
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
|
||||
|
||||
parts.add("fieldPart", "fieldValue");
|
||||
parts.add("filePart", new FileSystemResource("...logo.png"));
|
||||
parts.add("jsonPart", new Person("Jason"));
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_XML);
|
||||
parts.add("xmlPart", new HttpEntity<>(myBean, headers));
|
||||
|
||||
// send using RestClient.post or RestTemplate.postForEntity
|
||||
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
|
||||
|
||||
parts.add("fieldPart", "fieldValue");
|
||||
parts.add("filePart", new FileSystemResource("...logo.png"));
|
||||
parts.add("jsonPart", new Person("Jason"));
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_XML);
|
||||
parts.add("xmlPart", new HttpEntity<>(myBean, headers));
|
||||
|
||||
// send using RestClient.post or RestTemplate.postForEntity
|
||||
----
|
||||
|
||||
In most cases, you do not have to specify the `Content-Type` for each part.
|
||||
The content type is determined automatically based on the `HttpMessageConverter` chosen to serialize it or, in the case of a `Resource`, based on the file extension.
|
||||
If necessary, you can explicitly provide the `MediaType` with an `HttpEntity` wrapper.
|
||||
|
||||
Once the `MultiValueMap` is ready, you can use it as the body of a `POST` request, using `RestClient.post().body(parts)` (or `RestTemplate.postForObject`).
|
||||
Once the `MultiValueMap` is ready, you can use it as the body of a `POST` request, using `RestClient.post().body(parts)` (or `RestTemplate.postForObject`).
|
||||
|
||||
If the `MultiValueMap` contains at least one non-`String` value, the `Content-Type` is set to `multipart/form-data` by the `FormHttpMessageConverter`.
|
||||
If the `MultiValueMap` has `String` values, the `Content-Type` defaults to `application/x-www-form-urlencoded`.
|
||||
@@ -857,7 +856,7 @@ Start by creating the interface with `@HttpExchange` methods:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
interface RepositoryService {
|
||||
public interface RepositoryService {
|
||||
|
||||
@GetExchange("/repos/{owner}/{repo}")
|
||||
Repository getRepository(@PathVariable String owner, @PathVariable String repo);
|
||||
@@ -908,7 +907,7 @@ For `RestTemplate`:
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@HttpExchange(url = "/repos/{owner}/{repo}", accept = "application/vnd.github.v3+json")
|
||||
interface RepositoryService {
|
||||
public interface RepositoryService {
|
||||
|
||||
@GetExchange
|
||||
Repository getRepository(@PathVariable String owner, @PathVariable String repo);
|
||||
@@ -1137,11 +1136,11 @@ 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();
|
||||
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
|
||||
|
||||
@@ -215,45 +215,43 @@ For suspending functions, a `TransactionalOperator.executeAndAwait` extension is
|
||||
|
||||
[source,kotlin,indent=0]
|
||||
----
|
||||
import org.springframework.transaction.reactive.executeAndAwait
|
||||
import org.springframework.transaction.reactive.executeAndAwait
|
||||
|
||||
class PersonRepository(private val operator: TransactionalOperator) {
|
||||
class PersonRepository(private val operator: TransactionalOperator) {
|
||||
|
||||
suspend fun initDatabase() = operator.executeAndAwait {
|
||||
insertPerson1()
|
||||
insertPerson2()
|
||||
}
|
||||
suspend fun initDatabase() = operator.executeAndAwait {
|
||||
insertPerson1()
|
||||
insertPerson2()
|
||||
}
|
||||
|
||||
private suspend fun insertPerson1() {
|
||||
// INSERT SQL statement
|
||||
}
|
||||
private suspend fun insertPerson1() {
|
||||
// INSERT SQL statement
|
||||
}
|
||||
|
||||
private suspend fun insertPerson2() {
|
||||
// INSERT SQL statement
|
||||
}
|
||||
}
|
||||
private suspend fun insertPerson2() {
|
||||
// INSERT SQL statement
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
For Kotlin `Flow`, a `Flow<T>.transactional` extension is provided.
|
||||
|
||||
[source,kotlin,indent=0]
|
||||
----
|
||||
import org.springframework.transaction.reactive.transactional
|
||||
import org.springframework.transaction.reactive.transactional
|
||||
|
||||
class PersonRepository(private val operator: TransactionalOperator) {
|
||||
class PersonRepository(private val operator: TransactionalOperator) {
|
||||
|
||||
fun updatePeople() = findPeople().map(::updatePerson).transactional(operator)
|
||||
fun updatePeople() = findPeople().map(::updatePerson).transactional(operator)
|
||||
|
||||
private fun findPeople(): Flow<Person> {
|
||||
// SELECT SQL statement
|
||||
}
|
||||
private fun findPeople(): Flow<Person> {
|
||||
// SELECT SQL statement
|
||||
}
|
||||
|
||||
private suspend fun updatePerson(person: Person): Person {
|
||||
// UPDATE SQL statement
|
||||
}
|
||||
}
|
||||
private suspend fun updatePerson(person: Person): Person {
|
||||
// UPDATE SQL statement
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -190,7 +190,7 @@ NOTE: If you use Spring Boot, you should probably use
|
||||
instead of `@Value` annotations.
|
||||
|
||||
As an alternative, you can customize the property placeholder prefix by declaring the
|
||||
following configuration beans:
|
||||
following `PropertySourcesPlaceholderConfigurer` bean:
|
||||
|
||||
[source,kotlin,indent=0]
|
||||
----
|
||||
@@ -200,8 +200,10 @@ following configuration beans:
|
||||
}
|
||||
----
|
||||
|
||||
You can customize existing code (such as Spring Boot actuators or `@LocalServerPort`)
|
||||
that uses the `${...}` syntax, with configuration beans, as the following example shows:
|
||||
You can support components (such as Spring Boot actuators or `@LocalServerPort`) that use
|
||||
the standard `${...}` syntax alongside components that use the custom `%{...}` syntax by
|
||||
declaring multiple `PropertySourcesPlaceholderConfigurer` beans, as the following example
|
||||
shows:
|
||||
|
||||
[source,kotlin,indent=0]
|
||||
----
|
||||
@@ -215,6 +217,9 @@ that uses the `${...}` syntax, with configuration beans, as the following exampl
|
||||
fun defaultPropertyConfigurer() = PropertySourcesPlaceholderConfigurer()
|
||||
----
|
||||
|
||||
In addition, the default escape character can be changed or disabled globally by setting
|
||||
the `spring.placeholder.escapeCharacter.default` property via a JVM system property (or
|
||||
via the xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism).
|
||||
|
||||
|
||||
[[checked-exceptions]]
|
||||
@@ -296,17 +301,17 @@ for example when writing a `org.springframework.core.convert.converter.Converter
|
||||
|
||||
[source,kotlin,indent=0]
|
||||
----
|
||||
class ListOfFooConverter : Converter<List<Foo>, CustomJavaList<out Foo>> {
|
||||
// ...
|
||||
}
|
||||
class ListOfFooConverter : Converter<List<Foo>, CustomJavaList<out Foo>> {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
When converting any kind of objects, star projection with `*` can be used instead of `out Any`.
|
||||
[source,kotlin,indent=0]
|
||||
----
|
||||
class ListOfAnyConverter : Converter<List<*>, CustomJavaList<*>> {
|
||||
// ...
|
||||
}
|
||||
class ListOfAnyConverter : Converter<List<*>, CustomJavaList<*>> {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
NOTE: Spring Framework does not leverage yet declaration-site variance type information for injecting beans,
|
||||
@@ -340,13 +345,14 @@ file with a `spring.test.constructor.autowire.mode = all` property.
|
||||
|
||||
[source,kotlin,indent=0]
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig::class)
|
||||
@TestConstructor(autowireMode = AutowireMode.ALL)
|
||||
class OrderServiceIntegrationTests(val orderService: OrderService,
|
||||
val customerService: CustomerService) {
|
||||
|
||||
// tests that use the injected OrderService and CustomerService
|
||||
}
|
||||
@SpringJUnitConfig(TestConfig::class)
|
||||
@TestConstructor(autowireMode = AutowireMode.ALL)
|
||||
class OrderServiceIntegrationTests(
|
||||
val orderService: OrderService,
|
||||
val customerService: CustomerService) {
|
||||
|
||||
// tests that use the injected OrderService and CustomerService
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
@@ -368,29 +374,29 @@ The following example demonstrates `@BeforeAll` and `@AfterAll` annotations on n
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class IntegrationTests {
|
||||
|
||||
val application = Application(8181)
|
||||
val client = WebClient.create("http://localhost:8181")
|
||||
val application = Application(8181)
|
||||
val client = WebClient.create("http://localhost:8181")
|
||||
|
||||
@BeforeAll
|
||||
fun beforeAll() {
|
||||
application.start()
|
||||
}
|
||||
@BeforeAll
|
||||
fun beforeAll() {
|
||||
application.start()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Find all users on HTML page`() {
|
||||
client.get().uri("/users")
|
||||
.accept(TEXT_HTML)
|
||||
.retrieve()
|
||||
.bodyToMono<String>()
|
||||
.test()
|
||||
.expectNextMatches { it.contains("Foo") }
|
||||
.verifyComplete()
|
||||
}
|
||||
@Test
|
||||
fun `Find all users on HTML page`() {
|
||||
client.get().uri("/users")
|
||||
.accept(TEXT_HTML)
|
||||
.retrieve()
|
||||
.bodyToMono<String>()
|
||||
.test()
|
||||
.expectNextMatches { it.contains("Foo") }
|
||||
.verifyComplete()
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
fun afterAll() {
|
||||
application.stop()
|
||||
}
|
||||
@AfterAll
|
||||
fun afterAll() {
|
||||
application.stop()
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
@@ -403,26 +409,27 @@ The following example shows how to do so:
|
||||
|
||||
[source,kotlin,indent=0]
|
||||
----
|
||||
class SpecificationLikeTests {
|
||||
class SpecificationLikeTests {
|
||||
|
||||
@Nested
|
||||
@DisplayName("a calculator")
|
||||
inner class Calculator {
|
||||
|
||||
@Nested
|
||||
@DisplayName("a calculator")
|
||||
inner class Calculator {
|
||||
val calculator = SampleCalculator()
|
||||
|
||||
@Test
|
||||
fun `should return the result of adding the first number to the second number`() {
|
||||
val sum = calculator.sum(2, 4)
|
||||
assertEquals(6, sum)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return the result of subtracting the second number from the first number`() {
|
||||
val subtract = calculator.subtract(4, 2)
|
||||
assertEquals(2, subtract)
|
||||
}
|
||||
}
|
||||
}
|
||||
val calculator = SampleCalculator()
|
||||
|
||||
@Test
|
||||
fun `should return the result of adding the first number to the second number`() {
|
||||
val sum = calculator.sum(2, 4)
|
||||
assertEquals(6, sum)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return the result of subtracting the second number from the first number`() {
|
||||
val subtract = calculator.subtract(4, 2)
|
||||
assertEquals(2, subtract)
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
[[kotlin-web]]
|
||||
= Web
|
||||
|
||||
|
||||
|
||||
[[router-dsl]]
|
||||
== Router DSL
|
||||
|
||||
@@ -16,27 +14,27 @@ These DSL let you write clean and idiomatic Kotlin code to build a `RouterFuncti
|
||||
|
||||
[source,kotlin,indent=0]
|
||||
----
|
||||
@Configuration
|
||||
class RouterRouterConfiguration {
|
||||
|
||||
@Bean
|
||||
fun mainRouter(userHandler: UserHandler) = router {
|
||||
accept(TEXT_HTML).nest {
|
||||
GET("/") { ok().render("index") }
|
||||
GET("/sse") { ok().render("sse") }
|
||||
GET("/users", userHandler::findAllView)
|
||||
}
|
||||
"/api".nest {
|
||||
accept(APPLICATION_JSON).nest {
|
||||
GET("/users", userHandler::findAll)
|
||||
@Configuration
|
||||
class RouterRouterConfiguration {
|
||||
|
||||
@Bean
|
||||
fun mainRouter(userHandler: UserHandler) = router {
|
||||
accept(TEXT_HTML).nest {
|
||||
GET("/") { ok().render("index") }
|
||||
GET("/sse") { ok().render("sse") }
|
||||
GET("/users", userHandler::findAllView)
|
||||
}
|
||||
accept(TEXT_EVENT_STREAM).nest {
|
||||
GET("/users", userHandler::stream)
|
||||
"/api".nest {
|
||||
accept(APPLICATION_JSON).nest {
|
||||
GET("/users", userHandler::findAll)
|
||||
}
|
||||
accept(TEXT_EVENT_STREAM).nest {
|
||||
GET("/users", userHandler::stream)
|
||||
}
|
||||
}
|
||||
resources("/**", ClassPathResource("static/"))
|
||||
}
|
||||
resources("/**", ClassPathResource("static/"))
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
NOTE: This DSL is programmatic, meaning that it allows custom registration logic of beans
|
||||
@@ -55,22 +53,22 @@ idiomatic Kotlin API and to allow better discoverability (no usage of static met
|
||||
|
||||
[source,kotlin,indent=0]
|
||||
----
|
||||
val mockMvc: MockMvc = ...
|
||||
mockMvc.get("/person/{name}", "Lee") {
|
||||
secure = true
|
||||
accept = APPLICATION_JSON
|
||||
headers {
|
||||
contentLanguage = Locale.FRANCE
|
||||
val mockMvc: MockMvc = ...
|
||||
mockMvc.get("/person/{name}", "Lee") {
|
||||
secure = true
|
||||
accept = APPLICATION_JSON
|
||||
headers {
|
||||
contentLanguage = Locale.FRANCE
|
||||
}
|
||||
principal = Principal { "foo" }
|
||||
}.andExpect {
|
||||
status { isOk }
|
||||
content { contentType(APPLICATION_JSON) }
|
||||
jsonPath("$.name") { value("Lee") }
|
||||
content { json("""{"someBoolean": false}""", false) }
|
||||
}.andDo {
|
||||
print()
|
||||
}
|
||||
principal = Principal { "foo" }
|
||||
}.andExpect {
|
||||
status { isOk }
|
||||
content { contentType(APPLICATION_JSON) }
|
||||
jsonPath("$.name") { value("Lee") }
|
||||
content { json("""{"someBoolean": false}""", false) }
|
||||
}.andDo {
|
||||
print()
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
@@ -89,9 +87,9 @@ is possible to use such feature to render Kotlin-based templates with
|
||||
`build.gradle.kts`
|
||||
[source,kotlin,indent=0]
|
||||
----
|
||||
dependencies {
|
||||
runtime("org.jetbrains.kotlin:kotlin-scripting-jsr223:${kotlinVersion}")
|
||||
}
|
||||
dependencies {
|
||||
runtime("org.jetbrains.kotlin:kotlin-scripting-jsr223:${kotlinVersion}")
|
||||
}
|
||||
----
|
||||
|
||||
Configuration is usually done with `ScriptTemplateConfigurer` and `ScriptTemplateViewResolver` beans.
|
||||
@@ -99,23 +97,23 @@ Configuration is usually done with `ScriptTemplateConfigurer` and `ScriptTemplat
|
||||
`KotlinScriptConfiguration.kt`
|
||||
[source,kotlin,indent=0]
|
||||
----
|
||||
@Configuration
|
||||
class KotlinScriptConfiguration {
|
||||
|
||||
@Bean
|
||||
fun kotlinScriptConfigurer() = ScriptTemplateConfigurer().apply {
|
||||
engineName = "kotlin"
|
||||
setScripts("scripts/render.kts")
|
||||
renderFunction = "render"
|
||||
isSharedEngine = false
|
||||
@Configuration
|
||||
class KotlinScriptConfiguration {
|
||||
|
||||
@Bean
|
||||
fun kotlinScriptConfigurer() = ScriptTemplateConfigurer().apply {
|
||||
engineName = "kotlin"
|
||||
setScripts("scripts/render.kts")
|
||||
renderFunction = "render"
|
||||
isSharedEngine = false
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun kotlinScriptViewResolver() = ScriptTemplateViewResolver().apply {
|
||||
setPrefix("templates/")
|
||||
setSuffix(".kts")
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun kotlinScriptViewResolver() = ScriptTemplateViewResolver().apply {
|
||||
setPrefix("templates/")
|
||||
setSuffix(".kts")
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
See the https://github.com/sdeleuze/kotlin-script-templating[kotlin-script-templating] example
|
||||
@@ -127,7 +125,7 @@ project for more details.
|
||||
== Kotlin multiplatform serialization
|
||||
|
||||
{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.
|
||||
supported in Spring MVC, Spring WebFlux and Spring Messaging (RSocket). The built-in 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.
|
||||
With Spring MVC and WebFlux, both Kotlin serialization and Jackson will be configured by default if they are in the classpath since
|
||||
@@ -135,6 +133,3 @@ Kotlin serialization is designed to serialize only Kotlin classes annotated with
|
||||
With Spring Messaging (RSocket), make sure that neither Jackson, GSON or JSONB are in the classpath if you want automatic configuration,
|
||||
if Jackson is needed configure `KotlinSerializationJsonMessageConverter` manually.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
= Spring JUnit 4 Testing Annotations
|
||||
|
||||
The following annotations are supported only when used in conjunction with the
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-runner[SpringRunner], xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules[Spring's JUnit 4 rules]
|
||||
, or xref:testing/testcontext-framework/support-classes.adoc#testcontext-support-classes-junit4[Spring's JUnit 4 support classes]:
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-runner[SpringRunner],
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules[Spring's JUnit 4 rules], or
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-support-classes-junit4[Spring's JUnit 4 support classes]:
|
||||
|
||||
* xref:testing/annotations/integration-junit4.adoc#integration-testing-annotations-junit4-ifprofilevalue[`@IfProfileValue`]
|
||||
* xref:testing/annotations/integration-junit4.adoc#integration-testing-annotations-junit4-profilevaluesourceconfiguration[`@ProfileValueSourceConfiguration`]
|
||||
|
||||
+15
@@ -47,6 +47,21 @@ the same bean in several test classes, make sure to name the fields consistently
|
||||
creating unnecessary contexts.
|
||||
====
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
Using `@MockitoBean` or `@MockitoSpyBean` in conjunction with `@ContextHierarchy` can
|
||||
lead to undesirable results since each `@MockitoBean` or `@MockitoSpyBean` will be
|
||||
applied to all context hierarchy levels by default. To ensure that a particular
|
||||
`@MockitoBean` or `@MockitoSpyBean` is applied to a single context hierarchy level, set
|
||||
the `contextName` attribute to match a configured `@ContextConfiguration` name – for
|
||||
example, `@MockitoBean(contextName = "app-config")` or
|
||||
`@MockitoSpyBean(contextName = "app-config")`.
|
||||
|
||||
See
|
||||
xref:testing/testcontext-framework/ctx-management/hierarchies.adoc#testcontext-ctx-management-ctx-hierarchies-with-bean-overrides[context
|
||||
hierarchies with bean overrides] for further details and examples.
|
||||
====
|
||||
|
||||
Each annotation also defines Mockito-specific attributes to fine-tune the mocking behavior.
|
||||
|
||||
The `@MockitoBean` annotation uses the `REPLACE_OR_CREATE`
|
||||
|
||||
+13
@@ -31,6 +31,19 @@ same bean in several tests, make sure to name the field consistently to avoid cr
|
||||
unnecessary contexts.
|
||||
====
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
Using `@TestBean` in conjunction with `@ContextHierarchy` can lead to undesirable results
|
||||
since each `@TestBean` will be applied to all context hierarchy levels by default. To
|
||||
ensure that a particular `@TestBean` is applied to a single context hierarchy level, set
|
||||
the `contextName` attribute to match a configured `@ContextConfiguration` name – for
|
||||
example, `@TestBean(contextName = "app-config")`.
|
||||
|
||||
See
|
||||
xref:testing/testcontext-framework/ctx-management/hierarchies.adoc#testcontext-ctx-management-ctx-hierarchies-with-bean-overrides[context
|
||||
hierarchies with bean overrides] for further details and examples.
|
||||
====
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
There are no restrictions on the visibility of `@TestBean` fields or factory methods.
|
||||
|
||||
@@ -26,16 +26,16 @@ Java::
|
||||
|
||||
@Test
|
||||
void test() throws Exception {
|
||||
MvcResult mvcResult = this.mockMvc.perform(get("/path"))
|
||||
.andExpect(status().isOk()) <1>
|
||||
.andExpect(request().asyncStarted()) <2>
|
||||
.andExpect(request().asyncResult("body")) <3>
|
||||
.andReturn();
|
||||
MvcResult mvcResult = this.mockMvc.perform(get("/path"))
|
||||
.andExpect(status().isOk()) <1>
|
||||
.andExpect(request().asyncStarted()) <2>
|
||||
.andExpect(request().asyncResult("body")) <3>
|
||||
.andReturn();
|
||||
|
||||
this.mockMvc.perform(asyncDispatch(mvcResult)) <4>
|
||||
.andExpect(status().isOk()) <5>
|
||||
.andExpect(content().string("body"));
|
||||
}
|
||||
this.mockMvc.perform(asyncDispatch(mvcResult)) <4>
|
||||
.andExpect(status().isOk()) <5>
|
||||
.andExpect(content().string("body"));
|
||||
}
|
||||
----
|
||||
<1> Check response status is still unchanged
|
||||
<2> Async processing must have started
|
||||
|
||||
+3
-3
@@ -4,7 +4,7 @@
|
||||
Once the TestContext framework loads an `ApplicationContext` (or `WebApplicationContext`)
|
||||
for a test, that context is cached and reused for all subsequent tests that declare the
|
||||
same unique context configuration within the same test suite. To understand how caching
|
||||
works, it is important to understand what is meant by "`unique`" and "`test suite.`"
|
||||
works, it is important to understand what is meant by "unique" and "test suite."
|
||||
|
||||
An `ApplicationContext` can be uniquely identified by the combination of configuration
|
||||
parameters that is used to load it. Consequently, the unique combination of configuration
|
||||
@@ -15,8 +15,8 @@ framework uses the following configuration parameters to build the context cache
|
||||
* `classes` (from `@ContextConfiguration`)
|
||||
* `contextInitializerClasses` (from `@ContextConfiguration`)
|
||||
* `contextCustomizers` (from `ContextCustomizerFactory`) – this includes
|
||||
`@DynamicPropertySource` methods as well as various features from Spring Boot's
|
||||
testing support such as `@MockBean` and `@SpyBean`.
|
||||
`@DynamicPropertySource` methods, bean overrides (such as `@TestBean`, `@MockitoBean`,
|
||||
`@MockitoSpyBean` etc.), as well as various features from Spring Boot's testing support.
|
||||
* `contextLoader` (from `@ContextConfiguration`)
|
||||
* `parent` (from `@ContextHierarchy`)
|
||||
* `activeProfiles` (from `@ActiveProfiles`)
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
[[testcontext-context-customizers]]
|
||||
= Configuration Configuration with Context Customizers
|
||||
= Context Configuration with Context Customizers
|
||||
|
||||
A `ContextCustomizer` is responsible for customizing the supplied
|
||||
`ConfigurableApplicationContext` after bean definitions have been loaded into the context
|
||||
|
||||
+126
-9
@@ -22,8 +22,19 @@ given level in the hierarchy, the configuration resource type (that is, XML conf
|
||||
files or component classes) must be consistent. Otherwise, it is perfectly acceptable to
|
||||
have different levels in a context hierarchy configured using different resource types.
|
||||
|
||||
The remaining JUnit Jupiter based examples in this section show common configuration
|
||||
scenarios for integration tests that require the use of context hierarchies.
|
||||
[NOTE]
|
||||
====
|
||||
If you use `@DirtiesContext` in a test whose context is configured as part of a context
|
||||
hierarchy, you can use the `hierarchyMode` flag to control how the context cache is
|
||||
cleared.
|
||||
|
||||
For further details, see the discussion of `@DirtiesContext` in
|
||||
xref:testing/annotations/integration-spring/annotation-dirtiescontext.adoc[Spring Testing Annotations]
|
||||
and the {spring-framework-api}/test/annotation/DirtiesContext.html[`@DirtiesContext`] javadoc.
|
||||
====
|
||||
|
||||
The JUnit Jupiter based examples in this section show common configuration scenarios for
|
||||
integration tests that require the use of context hierarchies.
|
||||
|
||||
**Single test class with context hierarchy**
|
||||
--
|
||||
@@ -229,12 +240,118 @@ Kotlin::
|
||||
class ExtendedTests : BaseTests() {}
|
||||
----
|
||||
======
|
||||
|
||||
.Dirtying a context within a context hierarchy
|
||||
NOTE: If you use `@DirtiesContext` in a test whose context is configured as part of a
|
||||
context hierarchy, you can use the `hierarchyMode` flag to control how the context cache
|
||||
is cleared. For further details, see the discussion of `@DirtiesContext` in
|
||||
xref:testing/annotations/integration-spring/annotation-dirtiescontext.adoc[Spring Testing Annotations] and the
|
||||
{spring-framework-api}/test/annotation/DirtiesContext.html[`@DirtiesContext`] javadoc.
|
||||
--
|
||||
|
||||
[[testcontext-ctx-management-ctx-hierarchies-with-bean-overrides]]
|
||||
**Context hierarchies with bean overrides**
|
||||
--
|
||||
When `@ContextHierarchy` is used in conjunction with
|
||||
xref:testing/testcontext-framework/bean-overriding.adoc[bean overrides] such as
|
||||
`@TestBean`, `@MockitoBean`, or `@MockitoSpyBean`, it may be desirable or necessary to
|
||||
have the override applied to a single level in the context hierarchy. To achieve that,
|
||||
the bean override must specify a context name that matches a name configured via the
|
||||
`name` attribute in `@ContextConfiguration`.
|
||||
|
||||
The following test class configures the name of the second hierarchy level to be
|
||||
`"user-config"` and simultaneously specifies that the `UserService` should be wrapped in
|
||||
a Mockito spy in the context named `"user-config"`. Consequently, Spring will only
|
||||
attempt to create the spy in the `"user-config"` context and will not attempt to create
|
||||
the spy in the parent context.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextHierarchy({
|
||||
@ContextConfiguration(classes = AppConfig.class),
|
||||
@ContextConfiguration(classes = UserConfig.class, name = "user-config")
|
||||
})
|
||||
class IntegrationTests {
|
||||
|
||||
@MockitoSpyBean(contextName = "user-config")
|
||||
UserService userService;
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@ExtendWith(SpringExtension::class)
|
||||
@ContextHierarchy(
|
||||
ContextConfiguration(classes = [AppConfig::class]),
|
||||
ContextConfiguration(classes = [UserConfig::class], name = "user-config"))
|
||||
class IntegrationTests {
|
||||
|
||||
@MockitoSpyBean(contextName = "user-config")
|
||||
lateinit var userService: UserService
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
When applying bean overrides in different levels of the context hierarchy, you may need
|
||||
to have all of the bean override instances injected into the test class in order to
|
||||
interact with them — for example, to configure stubbing for mocks. However, `@Autowired`
|
||||
will always inject a matching bean found in the lowest level of the context hierarchy.
|
||||
Thus, to inject bean override instances from specific levels in the context hierarchy,
|
||||
you need to annotate fields with appropriate bean override annotations and configure the
|
||||
name of the context level.
|
||||
|
||||
The following test class configures the names of the hierarchy levels to be `"parent"`
|
||||
and `"child"`. It also declares two `PropertyService` fields that are configured to
|
||||
create or replace `PropertyService` beans with Mockito mocks in the respective contexts,
|
||||
named `"parent"` and `"child"`. Consequently, the mock from the `"parent"` context will
|
||||
be injected into the `propertyServiceInParent` field, and the mock from the `"child"`
|
||||
context will be injected into the `propertyServiceInChild` field.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextHierarchy({
|
||||
@ContextConfiguration(classes = ParentConfig.class, name = "parent"),
|
||||
@ContextConfiguration(classes = ChildConfig.class, name = "child")
|
||||
})
|
||||
class IntegrationTests {
|
||||
|
||||
@MockitoBean(contextName = "parent")
|
||||
PropertyService propertyServiceInParent;
|
||||
|
||||
@MockitoBean(contextName = "child")
|
||||
PropertyService propertyServiceInChild;
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@ExtendWith(SpringExtension::class)
|
||||
@ContextHierarchy(
|
||||
ContextConfiguration(classes = [ParentConfig::class], name = "parent"),
|
||||
ContextConfiguration(classes = [ChildConfig::class], name = "child"))
|
||||
class IntegrationTests {
|
||||
|
||||
@MockitoBean(contextName = "parent")
|
||||
lateinit var propertyServiceInParent: PropertyService
|
||||
|
||||
@MockitoBean(contextName = "child")
|
||||
lateinit var propertyServiceInChild: PropertyService
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
--
|
||||
|
||||
+3
-3
@@ -18,9 +18,9 @@ Do not run tests in parallel if the tests:
|
||||
* Use Spring Framework's `@DirtiesContext` support.
|
||||
* Use Spring Framework's `@MockitoBean` or `@MockitoSpyBean` support.
|
||||
* Use Spring Boot's `@MockBean` or `@SpyBean` support.
|
||||
* Use JUnit 4's `@FixMethodOrder` support or any testing framework feature
|
||||
that is designed to ensure that test methods run in a particular order. Note,
|
||||
however, that this does not apply if entire test classes are run in parallel.
|
||||
* Use JUnit Jupiter's `@TestMethodOrder` support or any testing framework feature that is
|
||||
designed to ensure that test methods run in a particular order. Note, however, that
|
||||
this does not apply if entire test classes are run in parallel.
|
||||
* Change the state of shared services or systems such as a database, message broker,
|
||||
filesystem, and others. This applies to both embedded and external systems.
|
||||
|
||||
|
||||
+195
-178
@@ -1,166 +1,9 @@
|
||||
[[testcontext-support-classes]]
|
||||
= TestContext Framework Support Classes
|
||||
|
||||
This section describes the various classes that support the Spring TestContext Framework.
|
||||
This section describes the various classes that support the Spring TestContext Framework
|
||||
in JUnit and TestNG.
|
||||
|
||||
[[testcontext-junit4-runner]]
|
||||
== Spring JUnit 4 Runner
|
||||
|
||||
The Spring TestContext Framework offers full integration with JUnit 4 through a custom
|
||||
runner (supported on JUnit 4.12 or higher). By annotating test classes with
|
||||
`@RunWith(SpringJUnit4ClassRunner.class)` or the shorter `@RunWith(SpringRunner.class)`
|
||||
variant, developers can implement standard JUnit 4-based unit and integration tests and
|
||||
simultaneously reap the benefits of the TestContext framework, such as support for
|
||||
loading application contexts, dependency injection of test instances, transactional test
|
||||
method execution, and so on. If you want to use the Spring TestContext Framework with an
|
||||
alternative runner (such as JUnit 4's `Parameterized` runner) or third-party runners
|
||||
(such as the `MockitoJUnitRunner`), you can, optionally, use
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules[Spring's support for JUnit rules] instead.
|
||||
|
||||
The following code listing shows the minimal requirements for configuring a test class to
|
||||
run with the custom Spring `Runner`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@RunWith(SpringRunner.class)
|
||||
@TestExecutionListeners({})
|
||||
public class SimpleTest {
|
||||
|
||||
@Test
|
||||
public void testMethod() {
|
||||
// test logic...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@RunWith(SpringRunner::class)
|
||||
@TestExecutionListeners
|
||||
class SimpleTest {
|
||||
|
||||
@Test
|
||||
fun testMethod() {
|
||||
// test logic...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
In the preceding example, `@TestExecutionListeners` is configured with an empty list, to
|
||||
disable the default listeners, which otherwise would require an `ApplicationContext` to
|
||||
be configured through `@ContextConfiguration`.
|
||||
|
||||
[[testcontext-junit4-rules]]
|
||||
== Spring JUnit 4 Rules
|
||||
|
||||
The `org.springframework.test.context.junit4.rules` package provides the following JUnit
|
||||
4 rules (supported on JUnit 4.12 or higher):
|
||||
|
||||
* `SpringClassRule`
|
||||
* `SpringMethodRule`
|
||||
|
||||
`SpringClassRule` is a JUnit `TestRule` that supports class-level features of the Spring
|
||||
TestContext Framework, whereas `SpringMethodRule` is a JUnit `MethodRule` that supports
|
||||
instance-level and method-level features of the Spring TestContext Framework.
|
||||
|
||||
In contrast to the `SpringRunner`, Spring's rule-based JUnit support has the advantage of
|
||||
being independent of any `org.junit.runner.Runner` implementation and can, therefore, be
|
||||
combined with existing alternative runners (such as JUnit 4's `Parameterized`) or
|
||||
third-party runners (such as the `MockitoJUnitRunner`).
|
||||
|
||||
To support the full functionality of the TestContext framework, you must combine a
|
||||
`SpringClassRule` with a `SpringMethodRule`. The following example shows the proper way
|
||||
to declare these rules in an integration test:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// Optionally specify a non-Spring Runner via @RunWith(...)
|
||||
@ContextConfiguration
|
||||
public class IntegrationTest {
|
||||
|
||||
@ClassRule
|
||||
public static final SpringClassRule springClassRule = new SpringClassRule();
|
||||
|
||||
@Rule
|
||||
public final SpringMethodRule springMethodRule = new SpringMethodRule();
|
||||
|
||||
@Test
|
||||
public void testMethod() {
|
||||
// test logic...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// Optionally specify a non-Spring Runner via @RunWith(...)
|
||||
@ContextConfiguration
|
||||
class IntegrationTest {
|
||||
|
||||
@Rule
|
||||
val springMethodRule = SpringMethodRule()
|
||||
|
||||
@Test
|
||||
fun testMethod() {
|
||||
// test logic...
|
||||
}
|
||||
|
||||
companion object {
|
||||
@ClassRule
|
||||
val springClassRule = SpringClassRule()
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[[testcontext-support-classes-junit4]]
|
||||
== JUnit 4 Support Classes
|
||||
|
||||
The `org.springframework.test.context.junit4` package provides the following support
|
||||
classes for JUnit 4-based test cases (supported on JUnit 4.12 or higher):
|
||||
|
||||
* `AbstractJUnit4SpringContextTests`
|
||||
* `AbstractTransactionalJUnit4SpringContextTests`
|
||||
|
||||
`AbstractJUnit4SpringContextTests` is an abstract base test class that integrates the
|
||||
Spring TestContext Framework with explicit `ApplicationContext` testing support in a
|
||||
JUnit 4 environment. When you extend `AbstractJUnit4SpringContextTests`, you can access a
|
||||
`protected` `applicationContext` instance variable that you can use to perform explicit
|
||||
bean lookups or to test the state of the context as a whole.
|
||||
|
||||
`AbstractTransactionalJUnit4SpringContextTests` is an abstract transactional extension of
|
||||
`AbstractJUnit4SpringContextTests` that adds some convenience functionality for JDBC
|
||||
access. This class expects a `javax.sql.DataSource` bean and a
|
||||
`PlatformTransactionManager` bean to be defined in the `ApplicationContext`. When you
|
||||
extend `AbstractTransactionalJUnit4SpringContextTests`, you can access a `protected`
|
||||
`jdbcTemplate` instance variable that you can use to run SQL statements to query the
|
||||
database. You can use such queries to confirm database state both before and after
|
||||
running database-related application code, and Spring ensures that such queries run in
|
||||
the scope of the same transaction as the application code. When used in conjunction with
|
||||
an ORM tool, be sure to avoid xref:testing/testcontext-framework/tx.adoc#testcontext-tx-false-positives[false positives].
|
||||
As mentioned in xref:testing/support-jdbc.adoc[JDBC Testing Support],
|
||||
`AbstractTransactionalJUnit4SpringContextTests` also provides convenience methods that
|
||||
delegate to methods in `JdbcTestUtils` by using the aforementioned `jdbcTemplate`.
|
||||
Furthermore, `AbstractTransactionalJUnit4SpringContextTests` provides an
|
||||
`executeSqlScript(..)` method for running SQL scripts against the configured `DataSource`.
|
||||
|
||||
TIP: These classes are a convenience for extension. If you do not want your test classes
|
||||
to be tied to a Spring-specific class hierarchy, you can configure your own custom test
|
||||
classes by using `@RunWith(SpringRunner.class)` or xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules[Spring's JUnit rules]
|
||||
.
|
||||
|
||||
[[testcontext-junit-jupiter-extension]]
|
||||
== SpringExtension for JUnit Jupiter
|
||||
@@ -177,14 +20,17 @@ following features above and beyond the feature set that Spring supports for JUn
|
||||
TestNG:
|
||||
|
||||
* Dependency injection for test constructors, test methods, and test lifecycle callback
|
||||
methods. See xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-di[Dependency Injection with the `SpringExtension`] for further details.
|
||||
methods. See xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-di[Dependency
|
||||
Injection with the `SpringExtension`] for further details.
|
||||
* Powerful support for link:https://junit.org/junit5/docs/current/user-guide/#extensions-conditions[conditional
|
||||
test execution] based on SpEL expressions, environment variables, system properties,
|
||||
and so on. See the documentation for `@EnabledIf` and `@DisabledIf` in
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc[Spring JUnit Jupiter Testing Annotations] for further details and examples.
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc[Spring JUnit Jupiter Testing Annotations]
|
||||
for further details and examples.
|
||||
* Custom composed annotations that combine annotations from Spring and JUnit Jupiter. See
|
||||
the `@TransactionalDevTestConfig` and `@TransactionalIntegrationTest` examples in
|
||||
xref:testing/annotations/integration-meta.adoc[Meta-Annotation Support for Testing] for further details.
|
||||
xref:testing/annotations/integration-meta.adoc[Meta-Annotation Support for Testing] for
|
||||
further details.
|
||||
|
||||
The following code listing shows how to configure a test class to use the
|
||||
`SpringExtension` in conjunction with `@ContextConfiguration`:
|
||||
@@ -307,7 +153,8 @@ Kotlin::
|
||||
======
|
||||
|
||||
See the documentation for `@SpringJUnitConfig` and `@SpringJUnitWebConfig` in
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc[Spring JUnit Jupiter Testing Annotations] for further details.
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc[Spring JUnit Jupiter Testing Annotations]
|
||||
for further details.
|
||||
|
||||
[[testcontext-junit-jupiter-di]]
|
||||
=== Dependency Injection with the `SpringExtension`
|
||||
@@ -318,10 +165,9 @@ extension API from JUnit Jupiter, which lets Spring provide dependency injection
|
||||
constructors, test methods, and test lifecycle callback methods.
|
||||
|
||||
Specifically, the `SpringExtension` can inject dependencies from the test's
|
||||
`ApplicationContext` into test constructors and methods that are annotated with
|
||||
Spring's `@BeforeTransaction` and `@AfterTransaction` or JUnit's `@BeforeAll`,
|
||||
`@AfterAll`, `@BeforeEach`, `@AfterEach`, `@Test`, `@RepeatedTest`, `@ParameterizedTest`,
|
||||
and others.
|
||||
`ApplicationContext` into test constructors and methods that are annotated with Spring's
|
||||
`@BeforeTransaction` and `@AfterTransaction` or JUnit's `@BeforeAll`, `@AfterAll`,
|
||||
`@BeforeEach`, `@AfterEach`, `@Test`, `@RepeatedTest`, `@ParameterizedTest`, and others.
|
||||
|
||||
|
||||
[[testcontext-junit-jupiter-di-constructor]]
|
||||
@@ -341,8 +187,9 @@ autowirable if one of the following conditions is met (in order of precedence).
|
||||
attribute set to `ALL`.
|
||||
* The default _test constructor autowire mode_ has been changed to `ALL`.
|
||||
|
||||
See xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-testconstructor[`@TestConstructor`] for details on the use of
|
||||
`@TestConstructor` and how to change the global _test constructor autowire mode_.
|
||||
See xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-testconstructor[`@TestConstructor`]
|
||||
for details on the use of `@TestConstructor` and how to change the global _test
|
||||
constructor autowire mode_.
|
||||
|
||||
WARNING: If the constructor for a test class is considered to be _autowirable_, Spring
|
||||
assumes the responsibility for resolving arguments for all parameters in the constructor.
|
||||
@@ -407,8 +254,9 @@ Kotlin::
|
||||
Note that this feature lets test dependencies be `final` and therefore immutable.
|
||||
|
||||
If the `spring.test.constructor.autowire.mode` property is to `all` (see
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-testconstructor[`@TestConstructor`]), we can omit the declaration of
|
||||
`@Autowired` on the constructor in the previous example, resulting in the following.
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-testconstructor[`@TestConstructor`]),
|
||||
we can omit the declaration of `@Autowired` on the constructor in the previous example,
|
||||
resulting in the following.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -553,17 +401,19 @@ honor `@NestedTestConfiguration` semantics.
|
||||
In order to allow development teams to change the default to `OVERRIDE` – for example,
|
||||
for compatibility with Spring Framework 5.0 through 5.2 – the default mode can be changed
|
||||
globally via a JVM system property or a `spring.properties` file in the root of the
|
||||
classpath. See the xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration["Changing the default enclosing configuration inheritance mode"]
|
||||
note for details.
|
||||
classpath. See the
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration["Changing the default enclosing configuration inheritance mode"]
|
||||
note for details.
|
||||
|
||||
Although the following "Hello World" example is very simplistic, it shows how to declare
|
||||
common configuration on a top-level class that is inherited by its `@Nested` test
|
||||
classes. In this particular example, only the `TestConfig` configuration class is
|
||||
inherited. Each nested test class provides its own set of active profiles, resulting in a
|
||||
distinct `ApplicationContext` for each nested test class (see
|
||||
xref:testing/testcontext-framework/ctx-management/caching.adoc[Context Caching] for details). Consult the list of
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration[supported annotations] to see
|
||||
which annotations can be inherited in `@Nested` test classes.
|
||||
xref:testing/testcontext-framework/ctx-management/caching.adoc[Context Caching] for details).
|
||||
Consult the list of
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration[supported annotations]
|
||||
to see which annotations can be inherited in `@Nested` test classes.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -626,8 +476,174 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[testcontext-junit4-support]]
|
||||
== JUnit 4 Support
|
||||
|
||||
[[testcontext-junit4-runner]]
|
||||
=== Spring JUnit 4 Runner
|
||||
|
||||
The Spring TestContext Framework offers full integration with JUnit 4 through a custom
|
||||
runner (supported on JUnit 4.12 or higher). By annotating test classes with
|
||||
`@RunWith(SpringJUnit4ClassRunner.class)` or the shorter `@RunWith(SpringRunner.class)`
|
||||
variant, developers can implement standard JUnit 4-based unit and integration tests and
|
||||
simultaneously reap the benefits of the TestContext framework, such as support for
|
||||
loading application contexts, dependency injection of test instances, transactional test
|
||||
method execution, and so on. If you want to use the Spring TestContext Framework with an
|
||||
alternative runner (such as JUnit 4's `Parameterized` runner) or third-party runners
|
||||
(such as the `MockitoJUnitRunner`), you can, optionally, use
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules[Spring's support for JUnit rules]
|
||||
instead.
|
||||
|
||||
The following code listing shows the minimal requirements for configuring a test class to
|
||||
run with the custom Spring `Runner`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@RunWith(SpringRunner.class)
|
||||
@TestExecutionListeners({})
|
||||
public class SimpleTest {
|
||||
|
||||
@Test
|
||||
public void testMethod() {
|
||||
// test logic...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@RunWith(SpringRunner::class)
|
||||
@TestExecutionListeners
|
||||
class SimpleTest {
|
||||
|
||||
@Test
|
||||
fun testMethod() {
|
||||
// test logic...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
In the preceding example, `@TestExecutionListeners` is configured with an empty list, to
|
||||
disable the default listeners, which otherwise would require an `ApplicationContext` to
|
||||
be configured through `@ContextConfiguration`.
|
||||
|
||||
[[testcontext-junit4-rules]]
|
||||
=== Spring JUnit 4 Rules
|
||||
|
||||
The `org.springframework.test.context.junit4.rules` package provides the following JUnit
|
||||
4 rules (supported on JUnit 4.12 or higher):
|
||||
|
||||
* `SpringClassRule`
|
||||
* `SpringMethodRule`
|
||||
|
||||
`SpringClassRule` is a JUnit `TestRule` that supports class-level features of the Spring
|
||||
TestContext Framework, whereas `SpringMethodRule` is a JUnit `MethodRule` that supports
|
||||
instance-level and method-level features of the Spring TestContext Framework.
|
||||
|
||||
In contrast to the `SpringRunner`, Spring's rule-based JUnit support has the advantage of
|
||||
being independent of any `org.junit.runner.Runner` implementation and can, therefore, be
|
||||
combined with existing alternative runners (such as JUnit 4's `Parameterized`) or
|
||||
third-party runners (such as the `MockitoJUnitRunner`).
|
||||
|
||||
To support the full functionality of the TestContext framework, you must combine a
|
||||
`SpringClassRule` with a `SpringMethodRule`. The following example shows the proper way
|
||||
to declare these rules in an integration test:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// Optionally specify a non-Spring Runner via @RunWith(...)
|
||||
@ContextConfiguration
|
||||
public class IntegrationTest {
|
||||
|
||||
@ClassRule
|
||||
public static final SpringClassRule springClassRule = new SpringClassRule();
|
||||
|
||||
@Rule
|
||||
public final SpringMethodRule springMethodRule = new SpringMethodRule();
|
||||
|
||||
@Test
|
||||
public void testMethod() {
|
||||
// test logic...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// Optionally specify a non-Spring Runner via @RunWith(...)
|
||||
@ContextConfiguration
|
||||
class IntegrationTest {
|
||||
|
||||
@Rule
|
||||
val springMethodRule = SpringMethodRule()
|
||||
|
||||
@Test
|
||||
fun testMethod() {
|
||||
// test logic...
|
||||
}
|
||||
|
||||
companion object {
|
||||
@ClassRule
|
||||
val springClassRule = SpringClassRule()
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[[testcontext-support-classes-junit4]]
|
||||
=== JUnit 4 Base Classes
|
||||
|
||||
The `org.springframework.test.context.junit4` package provides the following support
|
||||
classes for JUnit 4-based test cases (supported on JUnit 4.12 or higher):
|
||||
|
||||
* `AbstractJUnit4SpringContextTests`
|
||||
* `AbstractTransactionalJUnit4SpringContextTests`
|
||||
|
||||
`AbstractJUnit4SpringContextTests` is an abstract base test class that integrates the
|
||||
Spring TestContext Framework with explicit `ApplicationContext` testing support in a
|
||||
JUnit 4 environment. When you extend `AbstractJUnit4SpringContextTests`, you can access a
|
||||
`protected` `applicationContext` instance variable that you can use to perform explicit
|
||||
bean lookups or to test the state of the context as a whole.
|
||||
|
||||
`AbstractTransactionalJUnit4SpringContextTests` is an abstract transactional extension of
|
||||
`AbstractJUnit4SpringContextTests` that adds some convenience functionality for JDBC
|
||||
access. This class expects a `javax.sql.DataSource` bean and a
|
||||
`PlatformTransactionManager` bean to be defined in the `ApplicationContext`. When you
|
||||
extend `AbstractTransactionalJUnit4SpringContextTests`, you can access a `protected`
|
||||
`jdbcTemplate` instance variable that you can use to run SQL statements to query the
|
||||
database. You can use such queries to confirm database state both before and after
|
||||
running database-related application code, and Spring ensures that such queries run in
|
||||
the scope of the same transaction as the application code. When used in conjunction with
|
||||
an ORM tool, be sure to avoid
|
||||
xref:testing/testcontext-framework/tx.adoc#testcontext-tx-false-positives[false positives].
|
||||
As mentioned in xref:testing/support-jdbc.adoc[JDBC Testing Support],
|
||||
`AbstractTransactionalJUnit4SpringContextTests` also provides convenience methods that
|
||||
delegate to methods in `JdbcTestUtils` by using the aforementioned `jdbcTemplate`.
|
||||
Furthermore, `AbstractTransactionalJUnit4SpringContextTests` provides an
|
||||
`executeSqlScript(..)` method for running SQL scripts against the configured `DataSource`.
|
||||
|
||||
TIP: These classes are a convenience for extension. If you do not want your test classes
|
||||
to be tied to a Spring-specific class hierarchy, you can configure your own custom test
|
||||
classes by using `@RunWith(SpringRunner.class)` or
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules[Spring's JUnit rules].
|
||||
|
||||
|
||||
[[testcontext-support-classes-testng]]
|
||||
== TestNG Support Classes
|
||||
== TestNG Support
|
||||
|
||||
The `org.springframework.test.context.testng` package provides the following support
|
||||
classes for TestNG based test cases:
|
||||
@@ -650,7 +666,8 @@ extend `AbstractTransactionalTestNGSpringContextTests`, you can access a `protec
|
||||
database. You can use such queries to confirm database state both before and after
|
||||
running database-related application code, and Spring ensures that such queries run in
|
||||
the scope of the same transaction as the application code. When used in conjunction with
|
||||
an ORM tool, be sure to avoid xref:testing/testcontext-framework/tx.adoc#testcontext-tx-false-positives[false positives].
|
||||
an ORM tool, be sure to avoid
|
||||
xref:testing/testcontext-framework/tx.adoc#testcontext-tx-false-positives[false positives].
|
||||
As mentioned in xref:testing/support-jdbc.adoc[JDBC Testing Support],
|
||||
`AbstractTransactionalTestNGSpringContextTests` also provides convenience methods that
|
||||
delegate to methods in `JdbcTestUtils` by using the aforementioned `jdbcTemplate`.
|
||||
|
||||
@@ -7,16 +7,17 @@ by default, exactly in the following order:
|
||||
* `ServletTestExecutionListener`: Configures Servlet API mocks for a
|
||||
`WebApplicationContext`.
|
||||
* `DirtiesContextBeforeModesTestExecutionListener`: Handles the `@DirtiesContext`
|
||||
annotation for "`before`" modes.
|
||||
annotation for "before" modes.
|
||||
* `ApplicationEventsTestExecutionListener`: Provides support for
|
||||
xref:testing/testcontext-framework/application-events.adoc[`ApplicationEvents`].
|
||||
* `BeanOverrideTestExecutionListener`: Provides support for xref:testing/testcontext-framework/bean-overriding.adoc[] .
|
||||
* `BeanOverrideTestExecutionListener`: Provides support for
|
||||
xref:testing/testcontext-framework/bean-overriding.adoc[].
|
||||
* `DependencyInjectionTestExecutionListener`: Provides dependency injection for the test
|
||||
instance.
|
||||
* `MicrometerObservationRegistryTestExecutionListener`: Provides support for
|
||||
Micrometer's `ObservationRegistry`.
|
||||
* `DirtiesContextTestExecutionListener`: Handles the `@DirtiesContext` annotation for
|
||||
"`after`" modes.
|
||||
"after" modes.
|
||||
* `CommonCachesTestExecutionListener`: Clears resource caches in the test's
|
||||
`ApplicationContext` if necessary.
|
||||
* `TransactionalTestExecutionListener`: Provides transactional test execution with
|
||||
@@ -161,15 +162,16 @@ change from release to release -- for example, `SqlScriptsTestExecutionListener`
|
||||
introduced in Spring Framework 4.1, and `DirtiesContextBeforeModesTestExecutionListener`
|
||||
was introduced in Spring Framework 4.2. Furthermore, third-party frameworks like Spring
|
||||
Boot and Spring Security register their own default `TestExecutionListener`
|
||||
implementations by using the aforementioned xref:testing/testcontext-framework/tel-config.adoc#testcontext-tel-config-automatic-discovery[automatic discovery mechanism]
|
||||
.
|
||||
implementations by using the aforementioned
|
||||
xref:testing/testcontext-framework/tel-config.adoc#testcontext-tel-config-automatic-discovery[automatic discovery mechanism].
|
||||
|
||||
To avoid having to be aware of and re-declare all default listeners, you can set the
|
||||
`mergeMode` attribute of `@TestExecutionListeners` to `MergeMode.MERGE_WITH_DEFAULTS`.
|
||||
`MERGE_WITH_DEFAULTS` indicates that locally declared listeners should be merged with the
|
||||
default listeners. The merging algorithm ensures that duplicates are removed from the
|
||||
list and that the resulting set of merged listeners is sorted according to the semantics
|
||||
of `AnnotationAwareOrderComparator`, as described in xref:testing/testcontext-framework/tel-config.adoc#testcontext-tel-config-ordering[Ordering `TestExecutionListener` Implementations].
|
||||
of `AnnotationAwareOrderComparator`, as described in
|
||||
xref:testing/testcontext-framework/tel-config.adoc#testcontext-tel-config-ordering[Ordering `TestExecutionListener` Implementations].
|
||||
If a listener implements `Ordered` or is annotated with `@Order`, it can influence the
|
||||
position in which it is merged with the defaults. Otherwise, locally declared listeners
|
||||
are appended to the list of default listeners when merged.
|
||||
|
||||
@@ -47,8 +47,7 @@ out-of-container tests for code that depends on environment-specific properties.
|
||||
The `org.springframework.mock.web` package contains a comprehensive set of Servlet API
|
||||
mock objects that are useful for testing web contexts, controllers, and filters. These
|
||||
mock objects are targeted at usage with Spring's Web MVC framework and are generally more
|
||||
convenient to use than dynamic mock objects (such as https://easymock.org/[EasyMock])
|
||||
or alternative Servlet API mock objects (such as http://www.mockobjects.com[MockObjects]).
|
||||
convenient to use than dynamic mock objects (such as https://easymock.org/[EasyMock]).
|
||||
|
||||
TIP: Since Spring Framework 6.0, the mock objects in `org.springframework.mock.web` are
|
||||
based on the Servlet 6.0 API.
|
||||
|
||||
@@ -396,7 +396,7 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import org.springframework.test.web.reactive.server.expectBody
|
||||
import org.springframework.test.web.reactive.server.expectBody
|
||||
|
||||
client.get().uri("/persons/1")
|
||||
.exchange()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
[[webflux-view]]
|
||||
= View Technologies
|
||||
|
||||
[.small]#xref:web/webmvc-view.adoc[See equivalent in the Servlet stack]#
|
||||
|
||||
The rendering of views in Spring WebFlux is pluggable. Whether you decide to
|
||||
@@ -224,9 +225,9 @@ The following table shows the templating libraries that we have tested on differ
|
||||
|Scripting Library |Scripting Engine
|
||||
|https://handlebarsjs.com/[Handlebars] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|https://mustache.github.io/[Mustache] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|https://facebook.github.io/react/[React] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|https://www.embeddedjs.com/[EJS] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|https://www.stuartellis.name/articles/erb/[ERB] |https://www.jruby.org[JRuby]
|
||||
|https://react.dev/[React] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|https://ejs.co/[EJS] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|https://docs.ruby-lang.org/en/master/ERB.html[ERB] |https://www.jruby.org[JRuby]
|
||||
|https://docs.python.org/2/library/string.html#template-strings[String templates] |https://www.jython.org/[Jython]
|
||||
|https://github.com/sdeleuze/kotlin-script-templating[Kotlin Script templating] |{kotlin-site}[Kotlin]
|
||||
|===
|
||||
|
||||
@@ -306,34 +306,34 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
Resource resource = ...
|
||||
Mono<String> result = webClient
|
||||
.post()
|
||||
.uri("https://example.com")
|
||||
.body(Flux.concat(
|
||||
FormPartEvent.create("field", "field value"),
|
||||
FilePartEvent.create("file", resource)
|
||||
), PartEvent.class)
|
||||
.retrieve()
|
||||
.bodyToMono(String.class);
|
||||
Resource resource = ...
|
||||
Mono<String> result = webClient
|
||||
.post()
|
||||
.uri("https://example.com")
|
||||
.body(Flux.concat(
|
||||
FormPartEvent.create("field", "field value"),
|
||||
FilePartEvent.create("file", resource)
|
||||
), PartEvent.class)
|
||||
.retrieve()
|
||||
.bodyToMono(String.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
var resource: Resource = ...
|
||||
var result: Mono<String> = webClient
|
||||
.post()
|
||||
.uri("https://example.com")
|
||||
.body(
|
||||
Flux.concat(
|
||||
FormPartEvent.create("field", "field value"),
|
||||
FilePartEvent.create("file", resource)
|
||||
var resource: Resource = ...
|
||||
var result: Mono<String> = webClient
|
||||
.post()
|
||||
.uri("https://example.com")
|
||||
.body(
|
||||
Flux.concat(
|
||||
FormPartEvent.create("field", "field value"),
|
||||
FilePartEvent.create("file", resource)
|
||||
)
|
||||
)
|
||||
)
|
||||
.retrieve()
|
||||
.bodyToMono()
|
||||
.retrieve()
|
||||
.bodyToMono()
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -390,29 +390,29 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
HttpClient httpClient = HttpClient.newBuilder()
|
||||
.followRedirects(Redirect.NORMAL)
|
||||
.connectTimeout(Duration.ofSeconds(20))
|
||||
.build();
|
||||
HttpClient httpClient = HttpClient.newBuilder()
|
||||
.followRedirects(Redirect.NORMAL)
|
||||
.connectTimeout(Duration.ofSeconds(20))
|
||||
.build();
|
||||
|
||||
ClientHttpConnector connector =
|
||||
new JdkClientHttpConnector(httpClient, new DefaultDataBufferFactory());
|
||||
ClientHttpConnector connector =
|
||||
new JdkClientHttpConnector(httpClient, new DefaultDataBufferFactory());
|
||||
|
||||
WebClient webClient = WebClient.builder().clientConnector(connector).build();
|
||||
WebClient webClient = WebClient.builder().clientConnector(connector).build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val httpClient = HttpClient.newBuilder()
|
||||
.followRedirects(Redirect.NORMAL)
|
||||
.connectTimeout(Duration.ofSeconds(20))
|
||||
.build()
|
||||
val httpClient = HttpClient.newBuilder()
|
||||
.followRedirects(Redirect.NORMAL)
|
||||
.connectTimeout(Duration.ofSeconds(20))
|
||||
.build()
|
||||
|
||||
val connector = JdkClientHttpConnector(httpClient, DefaultDataBufferFactory())
|
||||
val connector = JdkClientHttpConnector(httpClient, DefaultDataBufferFactory())
|
||||
|
||||
val webClient = WebClient.builder().clientConnector(connector).build()
|
||||
val webClient = WebClient.builder().clientConnector(connector).build()
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -158,65 +158,65 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public class MultipartExchangeFilterFunction implements ExchangeFilterFunction {
|
||||
|
||||
@Override
|
||||
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
|
||||
if (MediaType.MULTIPART_FORM_DATA.includes(request.headers().getContentType())
|
||||
&& (request.method() == HttpMethod.PUT || request.method() == HttpMethod.POST)) {
|
||||
return next.exchange(ClientRequest.from(request).body((outputMessage, context) ->
|
||||
request.body().insert(new BufferingDecorator(outputMessage), context)).build()
|
||||
);
|
||||
} else {
|
||||
return next.exchange(request);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class BufferingDecorator extends ClientHttpRequestDecorator {
|
||||
|
||||
private BufferingDecorator(ClientHttpRequest delegate) {
|
||||
super(delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
|
||||
return DataBufferUtils.join(body).flatMap(buffer -> {
|
||||
getHeaders().setContentLength(buffer.readableByteCount());
|
||||
return super.writeWith(Mono.just(buffer));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
public class MultipartExchangeFilterFunction implements ExchangeFilterFunction {
|
||||
|
||||
@Override
|
||||
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
|
||||
if (MediaType.MULTIPART_FORM_DATA.includes(request.headers().getContentType())
|
||||
&& (request.method() == HttpMethod.PUT || request.method() == HttpMethod.POST)) {
|
||||
return next.exchange(ClientRequest.from(request).body((outputMessage, context) ->
|
||||
request.body().insert(new BufferingDecorator(outputMessage), context)).build()
|
||||
);
|
||||
} else {
|
||||
return next.exchange(request);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class BufferingDecorator extends ClientHttpRequestDecorator {
|
||||
|
||||
private BufferingDecorator(ClientHttpRequest delegate) {
|
||||
super(delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
|
||||
return DataBufferUtils.join(body).flatMap(buffer -> {
|
||||
getHeaders().setContentLength(buffer.readableByteCount());
|
||||
return super.writeWith(Mono.just(buffer));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
class MultipartExchangeFilterFunction : ExchangeFilterFunction {
|
||||
|
||||
override fun filter(request: ClientRequest, next: ExchangeFunction): Mono<ClientResponse> {
|
||||
return if (MediaType.MULTIPART_FORM_DATA.includes(request.headers().getContentType())
|
||||
&& (request.method() == HttpMethod.PUT || request.method() == HttpMethod.POST)) {
|
||||
next.exchange(ClientRequest.from(request)
|
||||
.body { message, context -> request.body().insert(BufferingDecorator(message), context) }
|
||||
.build())
|
||||
}
|
||||
else {
|
||||
next.exchange(request)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class BufferingDecorator(delegate: ClientHttpRequest) : ClientHttpRequestDecorator(delegate) {
|
||||
override fun writeWith(body: Publisher<out DataBuffer>): Mono<Void> {
|
||||
return DataBufferUtils.join(body)
|
||||
.flatMap {
|
||||
headers.contentLength = it.readableByteCount().toLong()
|
||||
super.writeWith(Mono.just(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
class MultipartExchangeFilterFunction : ExchangeFilterFunction {
|
||||
|
||||
override fun filter(request: ClientRequest, next: ExchangeFunction): Mono<ClientResponse> {
|
||||
return if (MediaType.MULTIPART_FORM_DATA.includes(request.headers().getContentType())
|
||||
&& (request.method() == HttpMethod.PUT || request.method() == HttpMethod.POST)) {
|
||||
next.exchange(ClientRequest.from(request)
|
||||
.body { message, context -> request.body().insert(BufferingDecorator(message), context) }
|
||||
.build())
|
||||
}
|
||||
else {
|
||||
next.exchange(request)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class BufferingDecorator(delegate: ClientHttpRequest) : ClientHttpRequestDecorator(delegate) {
|
||||
override fun writeWith(body: Publisher<out DataBuffer>): Mono<Void> {
|
||||
return DataBufferUtils.join(body)
|
||||
.flatMap {
|
||||
headers.contentLength = it.readableByteCount().toLong()
|
||||
super.writeWith(Mono.just(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
======
|
||||
|
||||
@@ -207,14 +207,14 @@ Kotlin::
|
||||
class ExampleHandler : WebSocketHandler {
|
||||
|
||||
override fun handle(session: WebSocketSession): Mono<Void> {
|
||||
return session.receive() // <1>
|
||||
return session.receive() // <1>
|
||||
.doOnNext {
|
||||
// ... // <2>
|
||||
}
|
||||
.concatMap {
|
||||
// ... // <3>
|
||||
}
|
||||
.then() // <4>
|
||||
.then() // <4>
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -268,16 +268,16 @@ Kotlin::
|
||||
|
||||
override fun handle(session: WebSocketSession): Mono<Void> {
|
||||
|
||||
val output = session.receive() // <1>
|
||||
val output = session.receive() // <1>
|
||||
.doOnNext {
|
||||
// ...
|
||||
}
|
||||
.concatMap {
|
||||
// ...
|
||||
}
|
||||
.map { session.textMessage("Echo $it") } // <2>
|
||||
.map { session.textMessage("Echo $it") } // <2>
|
||||
|
||||
return session.send(output) // <3>
|
||||
return session.send(output) // <3>
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
@@ -149,7 +149,7 @@ Java::
|
||||
DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
|
||||
registrar.setUseIsoFormat(true);
|
||||
registrar.registerFormatters(registry);
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ Java::
|
||||
----
|
||||
class Account {
|
||||
|
||||
private final String firstName;
|
||||
private final String firstName;
|
||||
|
||||
public Account(@BindParam("first-name") String firstName) {
|
||||
this.firstName = firstName;
|
||||
|
||||
+2
-2
@@ -93,8 +93,8 @@ Java::
|
||||
----
|
||||
@ModelAttribute
|
||||
public void addAccount(@RequestParam String number) {
|
||||
Mono<Account> accountMono = accountRepository.findAccount(number);
|
||||
model.addAttribute("account", accountMono);
|
||||
Mono<Account> accountMono = accountRepository.findAccount(number);
|
||||
model.addAttribute("account", accountMono);
|
||||
}
|
||||
|
||||
@PostMapping("/accounts")
|
||||
|
||||
@@ -234,8 +234,8 @@ Kotlin::
|
||||
--
|
||||
|
||||
URI path patterns can also have embedded `${...}` placeholders that are resolved on startup
|
||||
through `PropertySourcesPlaceholderConfigurer` against local, system, environment, and
|
||||
other property sources. You can use this to, for example, parameterize a base URL based on
|
||||
by using `PropertySourcesPlaceholderConfigurer` against local, system, environment, and
|
||||
other property sources. You can use this, for example, to parameterize a base URL based on
|
||||
some external configuration.
|
||||
|
||||
NOTE: Spring WebFlux uses `PathPattern` and the `PathPatternParser` for URI path matching support.
|
||||
|
||||
@@ -57,7 +57,7 @@ locale and language specific resource bundles.
|
||||
For further custom handling of method validation errors, you can extend
|
||||
`ResponseEntityExceptionHandler` or use an `@ExceptionHandler` method in a controller
|
||||
or in a `@ControllerAdvice`, and handle `HandlerMethodValidationException` directly.
|
||||
The exception contains a list of``ParameterValidationResult``s that group validation errors
|
||||
The exception contains a list of ``ParameterValidationResult``s that group validation errors
|
||||
by method parameter. You can either iterate over those, or provide a visitor with callback
|
||||
methods by controller method parameter type:
|
||||
|
||||
@@ -104,21 +104,21 @@ Kotlin::
|
||||
|
||||
override fun requestHeader(requestHeader: RequestHeader, result: ParameterValidationResult) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
override fun requestParam(requestParam: RequestParam?, result: ParameterValidationResult) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
override fun modelAttribute(modelAttribute: ModelAttribute?, errors: ParameterErrors) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
override fun other(result: ParameterValidationResult) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
})
|
||||
----
|
||||
======
|
||||
|
||||
@@ -782,8 +782,8 @@ Java::
|
||||
----
|
||||
WebClient webClient = WebClient.builder()
|
||||
.codecs(configurer -> {
|
||||
CustomDecoder decoder = new CustomDecoder();
|
||||
configurer.customCodecs().registerWithDefaultConfig(decoder);
|
||||
CustomDecoder decoder = new CustomDecoder();
|
||||
configurer.customCodecs().registerWithDefaultConfig(decoder);
|
||||
})
|
||||
.build();
|
||||
----
|
||||
@@ -794,8 +794,8 @@ Kotlin::
|
||||
----
|
||||
val webClient = WebClient.builder()
|
||||
.codecs({ configurer ->
|
||||
val decoder = CustomDecoder()
|
||||
configurer.customCodecs().registerWithDefaultConfig(decoder)
|
||||
val decoder = CustomDecoder()
|
||||
configurer.customCodecs().registerWithDefaultConfig(decoder)
|
||||
})
|
||||
.build()
|
||||
----
|
||||
|
||||
@@ -276,7 +276,7 @@ ServerResponse.async(asyncResponse);
|
||||
----
|
||||
======
|
||||
|
||||
https://www.w3.org/TR/eventsource/[Server-Sent Events] can be provided via the
|
||||
https://html.spec.whatwg.org/multipage/server-sent-events.html[Server-Sent Events] can be provided via the
|
||||
static `sse` method on `ServerResponse`. The builder provided by that method
|
||||
allows you to send Strings, or other objects as JSON. For example:
|
||||
|
||||
@@ -781,7 +781,7 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
ClassPathResource index = new ClassPathResource("static/index.html");
|
||||
ClassPathResource index = new ClassPathResource("static/index.html");
|
||||
List<String> extensions = List.of("js", "css", "ico", "png", "jpg", "gif");
|
||||
RequestPredicate spaPredicate = path("/api/**").or(path("/error")).or(pathExtension(extensions::contains)).negate();
|
||||
RouterFunction<ServerResponse> redirectToIndex = route()
|
||||
@@ -793,7 +793,7 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val redirectToIndex = router {
|
||||
val redirectToIndex = router {
|
||||
val index = ClassPathResource("static/index.html")
|
||||
val extensions = listOf("js", "css", "ico", "png", "jpg", "gif")
|
||||
val spaPredicate = !(path("/api/**") or path("/error") or
|
||||
@@ -814,16 +814,16 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
Resource location = new FileUrlResource("public-resources/");
|
||||
RouterFunction<ServerResponse> resources = RouterFunctions.resources("/resources/**", location);
|
||||
Resource location = new FileUrlResource("public-resources/");
|
||||
RouterFunction<ServerResponse> resources = RouterFunctions.resources("/resources/**", location);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val location = FileUrlResource("public-resources/")
|
||||
val resources = router { resources("/resources/**", location) }
|
||||
val location = FileUrlResource("public-resources/")
|
||||
val resources = router { resources("/resources/**", location) }
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[[mvc-view]]
|
||||
= View Technologies
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
[.small]#xref:web/webflux-view.adoc[See equivalent in the Reactive stack]#
|
||||
|
||||
The rendering of views in Spring MVC is pluggable. Whether you decide to use
|
||||
|
||||
@@ -130,6 +130,7 @@ the `Configuration` object.
|
||||
|
||||
[[mvc-view-freemarker-forms]]
|
||||
== Form Handling
|
||||
[.small]#xref:web/webflux-view.adoc#webflux-view-freemarker-forms[See equivalent in the Reactive stack]#
|
||||
|
||||
Spring provides a tag library for use in JSPs that contains, among others, a
|
||||
`<spring:bind/>` element. This element primarily lets forms display values from
|
||||
|
||||
@@ -13,9 +13,9 @@ templating libraries on different script engines:
|
||||
|Scripting Library |Scripting Engine
|
||||
|https://handlebarsjs.com/[Handlebars] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|https://mustache.github.io/[Mustache] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|https://facebook.github.io/react/[React] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|https://www.embeddedjs.com/[EJS] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|https://www.stuartellis.name/articles/erb/[ERB] |https://www.jruby.org[JRuby]
|
||||
|https://react.dev/[React] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|https://ejs.co/[EJS] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|https://docs.ruby-lang.org/en/master/ERB.html[ERB] |https://www.jruby.org[JRuby]
|
||||
|https://docs.python.org/2/library/string.html#template-strings[String templates] |https://www.jython.org/[Jython]
|
||||
|https://github.com/sdeleuze/kotlin-script-templating[Kotlin Script templating] |{kotlin-site}[Kotlin]
|
||||
|===
|
||||
@@ -47,7 +47,7 @@ through https://www.webjars.org/[WebJars].
|
||||
|
||||
[[mvc-view-script-integrate]]
|
||||
== Script Templates
|
||||
[.small]#xref:web/webflux-view.adoc#webflux-view-script[See equivalent in the Reactive stack]#
|
||||
[.small]#xref:web/webflux-view.adoc#webflux-view-script-integrate[See equivalent in the Reactive stack]#
|
||||
|
||||
You can declare a `ScriptTemplateConfigurer` bean to specify the script engine to use,
|
||||
the script files to load, what function to call to render templates, and so on.
|
||||
|
||||
@@ -281,7 +281,7 @@ invokes the configured exception resolvers and completes the request.
|
||||
=== SSE
|
||||
|
||||
`SseEmitter` (a subclass of `ResponseBodyEmitter`) provides support for
|
||||
https://www.w3.org/TR/eventsource/[Server-Sent Events], where events sent from the server
|
||||
https://html.spec.whatwg.org/multipage/server-sent-events.html[Server-Sent Events], where events sent from the server
|
||||
are formatted according to the W3C SSE specification. To produce an SSE
|
||||
stream from a controller, return `SseEmitter`, as the following example shows:
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ already set.
|
||||
"application/problem+json" over "application/json" when rendering a `ProblemDetail`,
|
||||
and also falls back on it if no compatible media type is found.
|
||||
|
||||
To enable RFC 9457 responses for Spring WebFlux exceptions and for any
|
||||
To enable RFC 9457 responses for Spring MVC exceptions and for any
|
||||
`ErrorResponseException`, extend `ResponseEntityExceptionHandler` and declare it as an
|
||||
xref:web/webmvc/mvc-controller/ann-advice.adoc[@ControllerAdvice] in Spring configuration. The handler
|
||||
has an `@ExceptionHandler` method that handles any `ErrorResponse` exception, which
|
||||
|
||||
+1
-1
@@ -97,7 +97,7 @@ Java::
|
||||
----
|
||||
class Account {
|
||||
|
||||
private final String firstName;
|
||||
private final String firstName;
|
||||
|
||||
public Account(@BindParam("first-name") String firstName) {
|
||||
this.firstName = firstName;
|
||||
|
||||
@@ -57,7 +57,7 @@ locale and language specific resource bundles.
|
||||
For further custom handling of method validation errors, you can extend
|
||||
`ResponseEntityExceptionHandler` or use an `@ExceptionHandler` method in a controller
|
||||
or in a `@ControllerAdvice`, and handle `HandlerMethodValidationException` directly.
|
||||
The exception contains a list of``ParameterValidationResult``s that group validation errors
|
||||
The exception contains a list of ``ParameterValidationResult``s that group validation errors
|
||||
by method parameter. You can either iterate over those, or provide a visitor with callback
|
||||
methods by controller method parameter type:
|
||||
|
||||
@@ -73,12 +73,12 @@ Java::
|
||||
|
||||
@Override
|
||||
public void requestHeader(RequestHeader requestHeader, ParameterValidationResult result) {
|
||||
// ...
|
||||
// ...
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestParam(@Nullable RequestParam requestParam, ParameterValidationResult result) {
|
||||
// ...
|
||||
// ...
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -88,7 +88,7 @@ Java::
|
||||
|
||||
@Override
|
||||
public void other(ParameterValidationResult result) {
|
||||
// ...
|
||||
// ...
|
||||
}
|
||||
});
|
||||
----
|
||||
@@ -103,22 +103,22 @@ Kotlin::
|
||||
ex.visitResults(object : HandlerMethodValidationException.Visitor {
|
||||
|
||||
override fun requestHeader(requestHeader: RequestHeader, result: ParameterValidationResult) {
|
||||
// ...
|
||||
}
|
||||
// ...
|
||||
}
|
||||
|
||||
override fun requestParam(requestParam: RequestParam?, result: ParameterValidationResult) {
|
||||
// ...
|
||||
}
|
||||
// ...
|
||||
}
|
||||
|
||||
override fun modelAttribute(modelAttribute: ModelAttribute?, errors: ParameterErrors) {
|
||||
// ...
|
||||
}
|
||||
// ...
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
override fun other(result: ParameterValidationResult) {
|
||||
// ...
|
||||
}
|
||||
// ...
|
||||
}
|
||||
})
|
||||
----
|
||||
======
|
||||
|
||||
@@ -13,7 +13,7 @@ reference documentation, including:
|
||||
* {docs-spring-security}/features/exploits/csrf.html#csrf-protection[CSRF protection]
|
||||
* {docs-spring-security}/features/exploits/headers.html[Security Response Headers]
|
||||
|
||||
https://hdiv.org/[HDIV] is another web security framework that integrates with Spring MVC.
|
||||
https://github.com/hdiv/hdiv[HDIV] is another web security framework that integrates with Spring MVC.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -22,11 +22,11 @@ The following example code is based on it:
|
||||
[source,javascript,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
const stompClient = new StompJs.Client({
|
||||
brokerURL: 'ws://domain.com/portfolio',
|
||||
onConnect: () => {
|
||||
// ...
|
||||
}
|
||||
});
|
||||
brokerURL: 'ws://domain.com/portfolio',
|
||||
onConnect: () => {
|
||||
// ...
|
||||
}
|
||||
});
|
||||
----
|
||||
|
||||
Alternatively, if you connect through SockJS, you can enable the
|
||||
@@ -47,5 +47,3 @@ interactive web application] -- a getting started guide.
|
||||
* https://github.com/rstoyanchev/spring-websocket-portfolio[Stock Portfolio] -- a sample
|
||||
application.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"@antora/collector-extension": "1.0.0-alpha.3",
|
||||
"@asciidoctor/tabs": "1.0.0-beta.6",
|
||||
"@springio/antora-extensions": "1.14.2",
|
||||
"fast-xml-parser": "4.5.2",
|
||||
"@springio/asciidoctor-extensions": "1.0.0-alpha.10"
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ import org.springframework.web.service.invoker.HttpServiceProxyFactory;
|
||||
public class CustomHttpServiceArgumentResolver {
|
||||
|
||||
// tag::httpinterface[]
|
||||
interface RepositoryService {
|
||||
public interface RepositoryService {
|
||||
|
||||
@GetExchange("/repos/search")
|
||||
List<Repository> searchRepository(Search search);
|
||||
|
||||
@@ -7,31 +7,31 @@ javaPlatform {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(platform("com.fasterxml.jackson:jackson-bom:2.18.2"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.14.4"))
|
||||
api(platform("io.netty:netty-bom:4.1.118.Final"))
|
||||
api(platform("com.fasterxml.jackson:jackson-bom:2.18.4"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.14.7"))
|
||||
api(platform("io.netty:netty-bom:4.1.121.Final"))
|
||||
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
|
||||
api(platform("io.projectreactor:reactor-bom:2024.0.3"))
|
||||
api(platform("io.projectreactor:reactor-bom:2024.0.6"))
|
||||
api(platform("io.rsocket:rsocket-bom:1.1.5"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.24"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.26"))
|
||||
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
|
||||
api(platform("org.assertj:assertj-bom:3.27.3"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:12.0.16"))
|
||||
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.16"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:12.0.21"))
|
||||
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.21"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.3"))
|
||||
api(platform("org.junit:junit-bom:5.11.4"))
|
||||
api(platform("org.mockito:mockito-bom:5.15.2"))
|
||||
api(platform("org.junit:junit-bom:5.12.2"))
|
||||
api(platform("org.mockito:mockito-bom:5.17.0"))
|
||||
|
||||
constraints {
|
||||
api("com.fasterxml:aalto-xml:1.3.2")
|
||||
api("com.fasterxml.woodstox:woodstox-core:6.7.0")
|
||||
api("com.github.ben-manes.caffeine:caffeine:3.1.8")
|
||||
api("com.github.ben-manes.caffeine:caffeine:3.2.0")
|
||||
api("com.github.librepdf:openpdf:1.3.43")
|
||||
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.11.0")
|
||||
api("com.google.protobuf:protobuf-java-util:4.29.3")
|
||||
api("com.google.code.gson:gson:2.12.1")
|
||||
api("com.google.protobuf:protobuf-java-util:4.30.2")
|
||||
api("com.h2database:h2:2.3.232")
|
||||
api("com.jayway.jsonpath:json-path:2.9.0")
|
||||
api("com.oracle.database.jdbc:ojdbc11:21.9.0.0")
|
||||
@@ -43,7 +43,7 @@ dependencies {
|
||||
api("com.sun.xml.bind:jaxb-core:3.0.2")
|
||||
api("com.sun.xml.bind:jaxb-impl:3.0.2")
|
||||
api("com.sun.xml.bind:jaxb-xjc:3.0.2")
|
||||
api("com.thoughtworks.qdox:qdox:2.1.0")
|
||||
api("com.thoughtworks.qdox:qdox:2.2.0")
|
||||
api("com.thoughtworks.xstream:xstream:1.4.21")
|
||||
api("commons-io:commons-io:2.15.0")
|
||||
api("de.bechte.junit:junit-hierarchicalcontextrunner:4.12.2")
|
||||
@@ -100,8 +100,8 @@ 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.4.1")
|
||||
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.3.1")
|
||||
api("org.apache.httpcomponents.client5:httpclient5:5.4.4")
|
||||
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.3.4")
|
||||
api("org.apache.poi:poi-ooxml:5.2.5")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.28")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.28")
|
||||
@@ -110,18 +110,18 @@ dependencies {
|
||||
api("org.aspectj:aspectjrt:1.9.22.1")
|
||||
api("org.aspectj:aspectjtools:1.9.22.1")
|
||||
api("org.aspectj:aspectjweaver:1.9.22.1")
|
||||
api("org.awaitility:awaitility:4.2.2")
|
||||
api("org.awaitility:awaitility:4.3.0")
|
||||
api("org.bouncycastle:bcpkix-jdk18on:1.72")
|
||||
api("org.codehaus.jettison:jettison:1.5.4")
|
||||
api("org.crac:crac:1.4.0")
|
||||
api("org.dom4j:dom4j:2.1.4")
|
||||
api("org.easymock:easymock:5.4.0")
|
||||
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.8")
|
||||
api("org.easymock:easymock:5.5.0")
|
||||
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.9")
|
||||
api("org.eclipse.persistence:org.eclipse.persistence.jpa:3.0.4")
|
||||
api("org.eclipse:yasson:2.0.4")
|
||||
api("org.ehcache:ehcache:3.10.8")
|
||||
api("org.ehcache:jcache:1.0.1")
|
||||
api("org.freemarker:freemarker:2.3.33")
|
||||
api("org.freemarker:freemarker:2.3.34")
|
||||
api("org.glassfish.external:opendmk_jmxremote_optional_jar:1.0-b01-ea")
|
||||
api("org.glassfish:jakarta.el:4.0.2")
|
||||
api("org.glassfish.tyrus:tyrus-container-servlet:2.1.3")
|
||||
@@ -130,24 +130,24 @@ dependencies {
|
||||
api("org.hibernate:hibernate-core-jakarta:5.6.15.Final")
|
||||
api("org.hibernate:hibernate-validator:7.0.5.Final")
|
||||
api("org.hsqldb:hsqldb:2.7.4")
|
||||
api("org.htmlunit:htmlunit:4.6.0")
|
||||
api("org.htmlunit:htmlunit:4.10.0")
|
||||
api("org.javamoney:moneta:1.4.4")
|
||||
api("org.jruby:jruby:9.4.9.0")
|
||||
api("org.jruby:jruby:9.4.12.0")
|
||||
api("org.junit.support:testng-engine:1.0.5")
|
||||
api("org.mozilla:rhino:1.7.15")
|
||||
api("org.ogce:xpp3:1.1.6")
|
||||
api("org.python:jython-standalone:2.7.3")
|
||||
api("org.python:jython-standalone:2.7.4")
|
||||
api("org.quartz-scheduler:quartz:2.3.2")
|
||||
api("org.seleniumhq.selenium:htmlunit3-driver:4.26.0")
|
||||
api("org.seleniumhq.selenium:selenium-java:4.26.0")
|
||||
api("org.seleniumhq.selenium:htmlunit3-driver:4.29.0")
|
||||
api("org.seleniumhq.selenium:selenium-java:4.29.0")
|
||||
api("org.skyscreamer:jsonassert:1.5.3")
|
||||
api("org.slf4j:slf4j-api:2.0.16")
|
||||
api("org.slf4j:slf4j-api:2.0.17")
|
||||
api("org.testng:testng:7.11.0")
|
||||
api("org.webjars:underscorejs:1.8.3")
|
||||
api("org.webjars:webjars-locator-core:0.55")
|
||||
api("org.webjars:webjars-locator-lite:1.0.0")
|
||||
api("org.webjars:webjars-locator-core:0.59")
|
||||
api("org.webjars:webjars-locator-lite:1.1.0")
|
||||
api("org.xmlunit:xmlunit-assertj:2.10.0")
|
||||
api("org.xmlunit:xmlunit-matchers:2.10.0")
|
||||
api("org.yaml:snakeyaml:2.3")
|
||||
api("org.yaml:snakeyaml:2.4")
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
version=6.2.3-SNAPSHOT
|
||||
version=6.2.7
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
|
||||
+2
-2
@@ -98,7 +98,7 @@ if (project.name == "spring-oxm") {
|
||||
}
|
||||
|
||||
// Include project specific settings
|
||||
task eclipseSettings(type: Copy) {
|
||||
tasks.register('eclipseSettings', Copy) {
|
||||
from rootProject.files(
|
||||
'src/eclipse/org.eclipse.core.resources.prefs',
|
||||
'src/eclipse/org.eclipse.jdt.core.prefs',
|
||||
@@ -107,7 +107,7 @@ task eclipseSettings(type: Copy) {
|
||||
outputs.upToDateWhen { false }
|
||||
}
|
||||
|
||||
task cleanEclipseSettings(type: Delete) {
|
||||
tasks.register('cleanEclipseSettings', Delete) {
|
||||
delete project.file('.settings/org.eclipse.core.resources.prefs')
|
||||
delete project.file('.settings/org.eclipse.jdt.core.prefs')
|
||||
delete project.file('.settings/org.eclipse.jdt.ui.prefs')
|
||||
|
||||
+26
-27
@@ -69,32 +69,31 @@ normalization {
|
||||
|
||||
javadoc {
|
||||
description = "Generates project-level javadoc for use in -javadoc jar"
|
||||
|
||||
options.encoding = "UTF-8"
|
||||
options.memberLevel = JavadocMemberLevel.PROTECTED
|
||||
options.author = true
|
||||
options.header = project.name
|
||||
options.use = true
|
||||
options.links(project.ext.javadocLinks)
|
||||
// Check for syntax during linting. 'none' doesn't seem to work in suppressing
|
||||
// all linting warnings all the time (see/link references most notably).
|
||||
options.addStringOption("Xdoclint:syntax", "-quiet")
|
||||
|
||||
// Suppress warnings due to cross-module @see and @link references.
|
||||
// Note that global 'api' task does display all warnings, and
|
||||
// checks for 'reference' on top of 'syntax'.
|
||||
logging.captureStandardError LogLevel.INFO
|
||||
logging.captureStandardOutput LogLevel.INFO // suppress "## warnings" message
|
||||
failOnError = true
|
||||
options {
|
||||
encoding = "UTF-8"
|
||||
memberLevel = JavadocMemberLevel.PROTECTED
|
||||
author = true
|
||||
header = project.name
|
||||
use = true
|
||||
links(project.ext.javadocLinks)
|
||||
// Check for 'syntax' during linting. Note that the global
|
||||
// 'framework-api:javadoc' task checks for 'reference' in addition
|
||||
// to 'syntax'.
|
||||
addBooleanOption("Xdoclint:syntax,-reference", true)
|
||||
addBooleanOption('Werror', true) // fail build on Javadoc warnings
|
||||
}
|
||||
}
|
||||
|
||||
task sourcesJar(type: Jar, dependsOn: classes) {
|
||||
tasks.register('sourcesJar', Jar) {
|
||||
dependsOn classes
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
archiveClassifier.set("sources")
|
||||
from sourceSets.main.allSource
|
||||
// Don't include or exclude anything explicitly by default. See SPR-12085.
|
||||
}
|
||||
|
||||
task javadocJar(type: Jar) {
|
||||
tasks.register('javadocJar', Jar) {
|
||||
archiveClassifier.set("javadoc")
|
||||
from javadoc
|
||||
}
|
||||
@@ -114,16 +113,16 @@ components.java.withVariantsFromConfiguration(configurations.testFixturesApiElem
|
||||
components.java.withVariantsFromConfiguration(configurations.testFixturesRuntimeElements) { skip() }
|
||||
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
options.errorprone {
|
||||
disableAllChecks = true
|
||||
option("NullAway:CustomContractAnnotations", "org.springframework.lang.Contract")
|
||||
option("NullAway:AnnotatedPackages", "org.springframework")
|
||||
option("NullAway:UnannotatedSubPackages", "org.springframework.instrument,org.springframework.context.index," +
|
||||
options.errorprone {
|
||||
disableAllChecks = true
|
||||
option("NullAway:CustomContractAnnotations", "org.springframework.lang.Contract")
|
||||
option("NullAway:AnnotatedPackages", "org.springframework")
|
||||
option("NullAway:UnannotatedSubPackages", "org.springframework.instrument,org.springframework.context.index," +
|
||||
"org.springframework.asm,org.springframework.cglib,org.springframework.objenesis," +
|
||||
"org.springframework.javapoet,org.springframework.aot.nativex.substitution,org.springframework.aot.nativex.feature")
|
||||
}
|
||||
}
|
||||
}
|
||||
tasks.compileJava {
|
||||
// The check defaults to a warning, bump it up to an error for the main sources
|
||||
options.errorprone.error("NullAway")
|
||||
}
|
||||
// The check defaults to a warning, bump it up to an error for the main sources
|
||||
options.errorprone.error("NullAway")
|
||||
}
|
||||
|
||||
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.12-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -114,7 +114,7 @@ case "$( uname )" in #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
CLASSPATH="\\\"\\\""
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
@@ -205,7 +205,7 @@ fi
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
@@ -213,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
|
||||
Vendored
+2
-2
@@ -70,11 +70,11 @@ goto fail
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
set CLASSPATH=
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
|
||||
+11
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -241,7 +241,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
|
||||
try {
|
||||
int algorithmicStep = STEP_JOIN_POINT_BINDING;
|
||||
while ((this.numberOfRemainingUnboundArguments > 0) && algorithmicStep < STEP_FINISHED) {
|
||||
while (this.numberOfRemainingUnboundArguments > 0 && algorithmicStep < STEP_FINISHED) {
|
||||
switch (algorithmicStep++) {
|
||||
case STEP_JOIN_POINT_BINDING -> {
|
||||
if (!maybeBindThisJoinPoint()) {
|
||||
@@ -373,7 +373,8 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
if (this.returningName != null) {
|
||||
if (this.numberOfRemainingUnboundArguments > 1) {
|
||||
throw new AmbiguousBindingException("Binding of returning parameter '" + this.returningName +
|
||||
"' is ambiguous: there are " + this.numberOfRemainingUnboundArguments + " candidates.");
|
||||
"' is ambiguous: there are " + this.numberOfRemainingUnboundArguments + " candidates. " +
|
||||
"Consider compiling with -parameters in order to make declared parameter names available.");
|
||||
}
|
||||
|
||||
// We're all set... find the unbound parameter, and bind it.
|
||||
@@ -485,8 +486,8 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
*/
|
||||
private void maybeBindThisOrTargetOrArgsFromPointcutExpression() {
|
||||
if (this.numberOfRemainingUnboundArguments > 1) {
|
||||
throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments
|
||||
+ " unbound args at this()/target()/args() binding stage, with no way to determine between them");
|
||||
throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments +
|
||||
" unbound args at this()/target()/args() binding stage, with no way to determine between them");
|
||||
}
|
||||
|
||||
List<String> varNames = new ArrayList<>();
|
||||
@@ -535,8 +536,8 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
|
||||
private void maybeBindReferencePointcutParameter() {
|
||||
if (this.numberOfRemainingUnboundArguments > 1) {
|
||||
throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments
|
||||
+ " unbound args at reference pointcut binding stage, with no way to determine between them");
|
||||
throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments +
|
||||
" unbound args at reference pointcut binding stage, with no way to determine between them");
|
||||
}
|
||||
|
||||
List<String> varNames = new ArrayList<>();
|
||||
@@ -741,7 +742,9 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
* Simple record to hold the extracted text from a pointcut body, together
|
||||
* with the number of tokens consumed in extracting it.
|
||||
*/
|
||||
private record PointcutBody(int numTokensConsumed, @Nullable String text) {}
|
||||
private record PointcutBody(int numTokensConsumed, @Nullable String text) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Thrown in response to an ambiguous binding being detected when
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -111,7 +111,7 @@ public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFacto
|
||||
* Create a new {@code ReflectiveAspectJAdvisorFactory}, propagating the given
|
||||
* {@link BeanFactory} to the created {@link AspectJExpressionPointcut} instances,
|
||||
* for bean pointcut handling as well as consistent {@link ClassLoader} resolution.
|
||||
* @param beanFactory the BeanFactory to propagate (may be {@code null}}
|
||||
* @param beanFactory the BeanFactory to propagate (may be {@code null})
|
||||
* @since 4.3.6
|
||||
* @see AspectJExpressionPointcut#setBeanFactory
|
||||
* @see org.springframework.beans.factory.config.ConfigurableBeanFactory#getBeanClassLoader()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -46,8 +46,8 @@ public class AspectEntry implements ParseState.Entry {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Aspect: " + (StringUtils.hasLength(this.id) ? "id='" + this.id + "'"
|
||||
: "ref='" + this.ref + "'");
|
||||
return "Aspect: " + (StringUtils.hasLength(this.id) ? "id='" + this.id + "'" :
|
||||
"ref='" + this.ref + "'");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -35,6 +35,7 @@ import org.springframework.aop.AopInvocationException;
|
||||
import org.springframework.aop.RawTargetAccess;
|
||||
import org.springframework.aop.TargetSource;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.aot.AotDetector;
|
||||
import org.springframework.cglib.core.ClassLoaderAwareGeneratorStrategy;
|
||||
import org.springframework.cglib.core.CodeGenerationException;
|
||||
import org.springframework.cglib.core.GeneratorStrategy;
|
||||
@@ -205,7 +206,7 @@ class CglibAopProxy implements AopProxy, Serializable {
|
||||
enhancer.setSuperclass(proxySuperClass);
|
||||
enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
|
||||
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
|
||||
enhancer.setAttemptLoad(true);
|
||||
enhancer.setAttemptLoad(enhancer.getUseCache() && AotDetector.useGeneratedArtifacts());
|
||||
enhancer.setStrategy(KotlinDetector.isKotlinType(proxySuperClass) ?
|
||||
new ClassLoaderAwareGeneratorStrategy(classLoader) :
|
||||
new ClassLoaderAwareGeneratorStrategy(classLoader, undeclaredThrowableStrategy)
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -114,10 +114,10 @@ public class BeanNameAutoProxyCreator extends AbstractAutoProxyCreator {
|
||||
boolean isFactoryBean = FactoryBean.class.isAssignableFrom(beanClass);
|
||||
for (String mappedName : this.beanNames) {
|
||||
if (isFactoryBean) {
|
||||
if (!mappedName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {
|
||||
if (mappedName.isEmpty() || mappedName.charAt(0) != BeanFactory.FACTORY_BEAN_PREFIX_CHAR) {
|
||||
continue;
|
||||
}
|
||||
mappedName = mappedName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());
|
||||
mappedName = mappedName.substring(1); // length of '&'
|
||||
}
|
||||
if (isMatch(beanName, mappedName)) {
|
||||
return true;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -198,8 +198,8 @@ public abstract class ClassFilters {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
return (this == other || (other instanceof NegateClassFilter that
|
||||
&& this.original.equals(that.original)));
|
||||
return (this == other || (other instanceof NegateClassFilter that &&
|
||||
this.original.equals(that.original)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -378,8 +378,8 @@ public abstract class MethodMatchers {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
return (this == other || (other instanceof NegateMethodMatcher that
|
||||
&& this.original.equals(that.original)));
|
||||
return (this == other || (other instanceof NegateMethodMatcher that &&
|
||||
this.original.equals(that.original)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+31
-34
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -203,7 +203,6 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
itb.getSpouse();
|
||||
|
||||
assertThat(maaif.isMaterialized()).isTrue();
|
||||
|
||||
assertThat(imapa.getDeclaredPointcut().getMethodMatcher().matches(TestBean.class.getMethod("getAge"), null)).isTrue();
|
||||
|
||||
assertThat(itb.getAge()).as("Around advice must apply").isEqualTo(0);
|
||||
@@ -301,7 +300,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
void bindingWithMultipleArgsDifferentlyOrdered() {
|
||||
ManyValuedArgs target = new ManyValuedArgs();
|
||||
ManyValuedArgs mva = createProxy(target, ManyValuedArgs.class,
|
||||
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new ManyValuedArgs(), "someBean")));
|
||||
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new ManyValuedArgs(), "someBean")));
|
||||
|
||||
String a = "a";
|
||||
int b = 12;
|
||||
@@ -320,7 +319,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
NotLockable notLockableTarget = new NotLockable();
|
||||
assertThat(notLockableTarget).isNotInstanceOf(Lockable.class);
|
||||
NotLockable notLockable1 = createProxy(notLockableTarget, NotLockable.class,
|
||||
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")));
|
||||
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")));
|
||||
assertThat(notLockable1).isInstanceOf(Lockable.class);
|
||||
Lockable lockable = (Lockable) notLockable1;
|
||||
assertThat(lockable.locked()).isFalse();
|
||||
@@ -329,7 +328,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
|
||||
NotLockable notLockable2Target = new NotLockable();
|
||||
NotLockable notLockable2 = createProxy(notLockable2Target, NotLockable.class,
|
||||
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")));
|
||||
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")));
|
||||
assertThat(notLockable2).isInstanceOf(Lockable.class);
|
||||
Lockable lockable2 = (Lockable) notLockable2;
|
||||
assertThat(lockable2.locked()).isFalse();
|
||||
@@ -343,20 +342,19 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
void introductionAdvisorExcludedFromTargetImplementingInterface() {
|
||||
assertThat(AopUtils.findAdvisorsThatCanApply(
|
||||
getAdvisorFactory().getAdvisors(
|
||||
aspectInstanceFactory(new MakeLockable(), "someBean")),
|
||||
aspectInstanceFactory(new MakeLockable(), "someBean")),
|
||||
CannotBeUnlocked.class)).isEmpty();
|
||||
assertThat(AopUtils.findAdvisorsThatCanApply(getAdvisorFactory().getAdvisors(
|
||||
aspectInstanceFactory(new MakeLockable(),"someBean")), NotLockable.class)).hasSize(2);
|
||||
aspectInstanceFactory(new MakeLockable(),"someBean")), NotLockable.class)).hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void introductionOnTargetImplementingInterface() {
|
||||
CannotBeUnlocked target = new CannotBeUnlocked();
|
||||
Lockable proxy = createProxy(target, CannotBeUnlocked.class,
|
||||
// Ensure that we exclude
|
||||
AopUtils.findAdvisorsThatCanApply(
|
||||
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")),
|
||||
CannotBeUnlocked.class));
|
||||
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")),
|
||||
CannotBeUnlocked.class));
|
||||
assertThat(proxy).isInstanceOf(Lockable.class);
|
||||
Lockable lockable = proxy;
|
||||
assertThat(lockable.locked()).as("Already locked").isTrue();
|
||||
@@ -370,8 +368,8 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
ArrayList<Object> target = new ArrayList<>();
|
||||
List<?> proxy = createProxy(target, List.class,
|
||||
AopUtils.findAdvisorsThatCanApply(
|
||||
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")),
|
||||
List.class));
|
||||
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")),
|
||||
List.class));
|
||||
assertThat(proxy).as("Type pattern must have excluded mixin").isNotInstanceOf(Lockable.class);
|
||||
}
|
||||
|
||||
@@ -379,7 +377,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
void introductionBasedOnAnnotationMatch() { // gh-9980
|
||||
AnnotatedTarget target = new AnnotatedTargetImpl();
|
||||
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
|
||||
aspectInstanceFactory(new MakeAnnotatedTypeModifiable(), "someBean"));
|
||||
aspectInstanceFactory(new MakeAnnotatedTypeModifiable(), "someBean"));
|
||||
Object proxy = createProxy(target, AnnotatedTarget.class, advisors);
|
||||
assertThat(proxy).isInstanceOf(Lockable.class);
|
||||
Lockable lockable = (Lockable) proxy;
|
||||
@@ -393,9 +391,9 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
TestBean target = new TestBean();
|
||||
|
||||
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
|
||||
aspectInstanceFactory(new MakeITestBeanModifiable(), "someBean"));
|
||||
aspectInstanceFactory(new MakeITestBeanModifiable(), "someBean"));
|
||||
advisors.addAll(getAdvisorFactory().getAdvisors(
|
||||
aspectInstanceFactory(new MakeLockable(), "someBean")));
|
||||
aspectInstanceFactory(new MakeLockable(), "someBean")));
|
||||
|
||||
Modifiable modifiable = (Modifiable) createProxy(target, ITestBean.class, advisors);
|
||||
assertThat(modifiable).isInstanceOf(Modifiable.class);
|
||||
@@ -426,7 +424,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
TestBean target = new TestBean();
|
||||
UnsupportedOperationException expectedException = new UnsupportedOperationException();
|
||||
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
|
||||
aspectInstanceFactory(new ExceptionThrowingAspect(expectedException), "someBean"));
|
||||
aspectInstanceFactory(new ExceptionThrowingAspect(expectedException), "someBean"));
|
||||
assertThat(advisors).as("One advice method was found").hasSize(1);
|
||||
ITestBean itb = createProxy(target, ITestBean.class, advisors);
|
||||
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(itb::getAge);
|
||||
@@ -439,12 +437,12 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
TestBean target = new TestBean();
|
||||
RemoteException expectedException = new RemoteException();
|
||||
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
|
||||
aspectInstanceFactory(new ExceptionThrowingAspect(expectedException), "someBean"));
|
||||
aspectInstanceFactory(new ExceptionThrowingAspect(expectedException), "someBean"));
|
||||
assertThat(advisors).as("One advice method was found").hasSize(1);
|
||||
ITestBean itb = createProxy(target, ITestBean.class, advisors);
|
||||
assertThatExceptionOfType(UndeclaredThrowableException.class)
|
||||
.isThrownBy(itb::getAge)
|
||||
.withCause(expectedException);
|
||||
.isThrownBy(itb::getAge)
|
||||
.withCause(expectedException);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -452,7 +450,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
TestBean target = new TestBean();
|
||||
TwoAdviceAspect twoAdviceAspect = new TwoAdviceAspect();
|
||||
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
|
||||
aspectInstanceFactory(twoAdviceAspect, "someBean"));
|
||||
aspectInstanceFactory(twoAdviceAspect, "someBean"));
|
||||
assertThat(advisors).as("Two advice methods found").hasSize(2);
|
||||
ITestBean itb = createProxy(target, ITestBean.class, advisors);
|
||||
itb.setName("");
|
||||
@@ -466,7 +464,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
void afterAdviceTypes() throws Exception {
|
||||
InvocationTrackingAspect aspect = new InvocationTrackingAspect();
|
||||
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
|
||||
aspectInstanceFactory(aspect, "exceptionHandlingAspect"));
|
||||
aspectInstanceFactory(aspect, "exceptionHandlingAspect"));
|
||||
Echo echo = createProxy(new Echo(), Echo.class, advisors);
|
||||
|
||||
assertThat(aspect.invocations).isEmpty();
|
||||
@@ -475,7 +473,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
|
||||
aspect.invocations.clear();
|
||||
assertThatExceptionOfType(FileNotFoundException.class)
|
||||
.isThrownBy(() -> echo.echo(new FileNotFoundException()));
|
||||
.isThrownBy(() -> echo.echo(new FileNotFoundException()));
|
||||
assertThat(aspect.invocations).containsExactly("around - start", "before", "after throwing", "after", "around - end");
|
||||
}
|
||||
|
||||
@@ -487,7 +485,6 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
assertThat(Modifier.isAbstract(aspect.getClass().getSuperclass().getModifiers())).isFalse();
|
||||
|
||||
List<Advisor> advisors = getAdvisorFactory().getAdvisors(aspectInstanceFactory(aspect, "incrementingAspect"));
|
||||
|
||||
ITestBean proxy = createProxy(new TestBean("Jane", 42), ITestBean.class, advisors);
|
||||
assertThat(proxy.getAge()).isEqualTo(86); // (42 + 1) * 2
|
||||
}
|
||||
@@ -812,20 +809,20 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
invocations.add("before");
|
||||
}
|
||||
|
||||
@AfterReturning("echo()")
|
||||
void afterReturning() {
|
||||
invocations.add("after returning");
|
||||
}
|
||||
|
||||
@AfterThrowing("echo()")
|
||||
void afterThrowing() {
|
||||
invocations.add("after throwing");
|
||||
}
|
||||
|
||||
@After("echo()")
|
||||
void after() {
|
||||
invocations.add("after");
|
||||
}
|
||||
|
||||
@AfterReturning(pointcut = "this(target) && execution(* echo(*))", returning = "returnValue")
|
||||
void afterReturning(JoinPoint joinPoint, Echo target, Object returnValue) {
|
||||
invocations.add("after returning");
|
||||
}
|
||||
|
||||
@AfterThrowing(pointcut = "this(target) && execution(* echo(*))", throwing = "exception")
|
||||
void afterThrowing(JoinPoint joinPoint, Echo target, Throwable exception) {
|
||||
invocations.add("after throwing");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -967,7 +964,7 @@ abstract class AbstractMakeModifiable {
|
||||
class MakeITestBeanModifiable extends AbstractMakeModifiable {
|
||||
|
||||
@DeclareParents(value = "org.springframework.beans.testfixture.beans.ITestBean+",
|
||||
defaultImpl=ModifiableImpl.class)
|
||||
defaultImpl = ModifiableImpl.class)
|
||||
static MutableModifiable mixin;
|
||||
|
||||
}
|
||||
|
||||
+1
-2
@@ -27,13 +27,12 @@ import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.core.task.AsyncTaskExecutor;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
|
||||
/**
|
||||
* Tests for {@link AsyncExecutionInterceptor}.
|
||||
*
|
||||
|
||||
+2
-3
@@ -291,7 +291,7 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
|
||||
String lastKey = tokens.keys[tokens.keys.length - 1];
|
||||
|
||||
if (propValue.getClass().isArray()) {
|
||||
Class<?> requiredType = propValue.getClass().componentType();
|
||||
Class<?> componentType = propValue.getClass().componentType();
|
||||
int arrayIndex = Integer.parseInt(lastKey);
|
||||
Object oldValue = null;
|
||||
try {
|
||||
@@ -299,10 +299,9 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
|
||||
oldValue = Array.get(propValue, arrayIndex);
|
||||
}
|
||||
Object convertedValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(),
|
||||
requiredType, ph.nested(tokens.keys.length));
|
||||
componentType, ph.nested(tokens.keys.length));
|
||||
int length = Array.getLength(propValue);
|
||||
if (arrayIndex >= length && arrayIndex < this.autoGrowCollectionLimit) {
|
||||
Class<?> componentType = propValue.getClass().componentType();
|
||||
Object newArray = Array.newInstance(componentType, arrayIndex + 1);
|
||||
System.arraycopy(propValue, 0, newArray, 0, length);
|
||||
int lastKeyIndex = tokens.canonicalName.lastIndexOf('[');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2025 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,6 +25,7 @@ import org.springframework.lang.Nullable;
|
||||
* analogous to an InvocationTargetException.
|
||||
*
|
||||
* @author Rod Johnson
|
||||
* @author Juergen Hoeller
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class MethodInvocationException extends PropertyAccessException {
|
||||
@@ -41,7 +42,9 @@ public class MethodInvocationException extends PropertyAccessException {
|
||||
* @param cause the Throwable raised by the invoked method
|
||||
*/
|
||||
public MethodInvocationException(PropertyChangeEvent propertyChangeEvent, @Nullable Throwable cause) {
|
||||
super(propertyChangeEvent, "Property '" + propertyChangeEvent.getPropertyName() + "' threw exception", cause);
|
||||
super(propertyChangeEvent,
|
||||
"Property '" + propertyChangeEvent.getPropertyName() + "' threw exception: " + cause,
|
||||
cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -124,9 +124,16 @@ public interface BeanFactory {
|
||||
* beans <i>created</i> by the FactoryBean. For example, if the bean named
|
||||
* {@code myJndiObject} is a FactoryBean, getting {@code &myJndiObject}
|
||||
* will return the factory, not the instance returned by the factory.
|
||||
* @see #FACTORY_BEAN_PREFIX_CHAR
|
||||
*/
|
||||
String FACTORY_BEAN_PREFIX = "&";
|
||||
|
||||
/**
|
||||
* Character variant of {@link #FACTORY_BEAN_PREFIX}.
|
||||
* @since 6.2.6
|
||||
*/
|
||||
char FACTORY_BEAN_PREFIX_CHAR = '&';
|
||||
|
||||
|
||||
/**
|
||||
* Return an instance, which may be shared or independent, of the specified bean.
|
||||
|
||||
@@ -72,7 +72,7 @@ public abstract class BeanFactoryUtils {
|
||||
* @see BeanFactory#FACTORY_BEAN_PREFIX
|
||||
*/
|
||||
public static boolean isFactoryDereference(@Nullable String name) {
|
||||
return (name != null && name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));
|
||||
return (name != null && !name.isEmpty() && name.charAt(0) == BeanFactory.FACTORY_BEAN_PREFIX_CHAR);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,14 +84,14 @@ public abstract class BeanFactoryUtils {
|
||||
*/
|
||||
public static String transformedBeanName(String name) {
|
||||
Assert.notNull(name, "'name' must not be null");
|
||||
if (!name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {
|
||||
if (name.isEmpty() || name.charAt(0) != BeanFactory.FACTORY_BEAN_PREFIX_CHAR) {
|
||||
return name;
|
||||
}
|
||||
return transformedBeanNameCache.computeIfAbsent(name, beanName -> {
|
||||
do {
|
||||
beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());
|
||||
beanName = beanName.substring(1); // length of '&'
|
||||
}
|
||||
while (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));
|
||||
while (beanName.charAt(0) == BeanFactory.FACTORY_BEAN_PREFIX_CHAR);
|
||||
return beanName;
|
||||
});
|
||||
}
|
||||
|
||||
+13
-15
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -145,8 +145,6 @@ public interface ListableBeanFactory extends BeanFactory {
|
||||
* <p>Does not consider any hierarchy this factory may participate in.
|
||||
* Use BeanFactoryUtils' {@code beanNamesForTypeIncludingAncestors}
|
||||
* to include beans in ancestor factories too.
|
||||
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
|
||||
* by other means than bean definitions.
|
||||
* <p>This version of {@code getBeanNamesForType} matches all kinds of beans,
|
||||
* be it singletons, prototypes, or FactoryBeans. In most implementations, the
|
||||
* result will be the same as for {@code getBeanNamesForType(type, true, true)}.
|
||||
@@ -176,8 +174,6 @@ public interface ListableBeanFactory extends BeanFactory {
|
||||
* <p>Does not consider any hierarchy this factory may participate in.
|
||||
* Use BeanFactoryUtils' {@code beanNamesForTypeIncludingAncestors}
|
||||
* to include beans in ancestor factories too.
|
||||
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
|
||||
* by other means than bean definitions.
|
||||
* <p>Bean names returned by this method should always return bean names <i>in the
|
||||
* order of definition</i> in the backend configuration, as far as possible.
|
||||
* @param type the generically typed class or interface to match
|
||||
@@ -210,8 +206,6 @@ public interface ListableBeanFactory extends BeanFactory {
|
||||
* <p>Does not consider any hierarchy this factory may participate in.
|
||||
* Use BeanFactoryUtils' {@code beanNamesForTypeIncludingAncestors}
|
||||
* to include beans in ancestor factories too.
|
||||
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
|
||||
* by other means than bean definitions.
|
||||
* <p>This version of {@code getBeanNamesForType} matches all kinds of beans,
|
||||
* be it singletons, prototypes, or FactoryBeans. In most implementations, the
|
||||
* result will be the same as for {@code getBeanNamesForType(type, true, true)}.
|
||||
@@ -239,8 +233,6 @@ public interface ListableBeanFactory extends BeanFactory {
|
||||
* <p>Does not consider any hierarchy this factory may participate in.
|
||||
* Use BeanFactoryUtils' {@code beanNamesForTypeIncludingAncestors}
|
||||
* to include beans in ancestor factories too.
|
||||
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
|
||||
* by other means than bean definitions.
|
||||
* <p>Bean names returned by this method should always return bean names <i>in the
|
||||
* order of definition</i> in the backend configuration, as far as possible.
|
||||
* @param type the class or interface to match, or {@code null} for all bean names
|
||||
@@ -265,21 +257,24 @@ public interface ListableBeanFactory extends BeanFactory {
|
||||
* subclasses), judging from either bean definitions or the value of
|
||||
* {@code getObjectType} in the case of FactoryBeans.
|
||||
* <p><b>NOTE: This method introspects top-level beans only.</b> It does <i>not</i>
|
||||
* check nested beans which might match the specified type as well.
|
||||
* check nested beans which might match the specified type as well. Also, it
|
||||
* <b>suppresses exceptions for beans that are currently in creation in a circular
|
||||
* reference scenario:</b> typically, references back to the caller of this method.
|
||||
* <p>Does consider objects created by FactoryBeans, which means that FactoryBeans
|
||||
* will get initialized. If the object created by the FactoryBean doesn't match,
|
||||
* the raw FactoryBean itself will be matched against the type.
|
||||
* <p>Does not consider any hierarchy this factory may participate in.
|
||||
* Use BeanFactoryUtils' {@code beansOfTypeIncludingAncestors}
|
||||
* to include beans in ancestor factories too.
|
||||
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
|
||||
* by other means than bean definitions.
|
||||
* <p>This version of getBeansOfType matches all kinds of beans, be it
|
||||
* singletons, prototypes, or FactoryBeans. In most implementations, the
|
||||
* result will be the same as for {@code getBeansOfType(type, true, true)}.
|
||||
* <p>The Map returned by this method should always return bean names and
|
||||
* corresponding bean instances <i>in the order of definition</i> in the
|
||||
* backend configuration, as far as possible.
|
||||
* <p><b>Consider {@link #getBeanNamesForType(Class)} with selective {@link #getBean}
|
||||
* calls for specific bean names in preference to this Map-based retrieval method.</b>
|
||||
* Aside from lazy instantiation benefits, this also avoids any exception suppression.
|
||||
* @param type the class or interface to match, or {@code null} for all concrete beans
|
||||
* @return a Map with the matching beans, containing the bean names as
|
||||
* keys and the corresponding bean instances as values
|
||||
@@ -295,7 +290,9 @@ public interface ListableBeanFactory extends BeanFactory {
|
||||
* subclasses), judging from either bean definitions or the value of
|
||||
* {@code getObjectType} in the case of FactoryBeans.
|
||||
* <p><b>NOTE: This method introspects top-level beans only.</b> It does <i>not</i>
|
||||
* check nested beans which might match the specified type as well.
|
||||
* check nested beans which might match the specified type as well. Also, it
|
||||
* <b>suppresses exceptions for beans that are currently in creation in a circular
|
||||
* reference scenario:</b> typically, references back to the caller of this method.
|
||||
* <p>Does consider objects created by FactoryBeans if the "allowEagerInit" flag is set,
|
||||
* which means that FactoryBeans will get initialized. If the object created by the
|
||||
* FactoryBean doesn't match, the raw FactoryBean itself will be matched against the
|
||||
@@ -304,11 +301,12 @@ public interface ListableBeanFactory extends BeanFactory {
|
||||
* <p>Does not consider any hierarchy this factory may participate in.
|
||||
* Use BeanFactoryUtils' {@code beansOfTypeIncludingAncestors}
|
||||
* to include beans in ancestor factories too.
|
||||
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
|
||||
* by other means than bean definitions.
|
||||
* <p>The Map returned by this method should always return bean names and
|
||||
* corresponding bean instances <i>in the order of definition</i> in the
|
||||
* backend configuration, as far as possible.
|
||||
* <p><b>Consider {@link #getBeanNamesForType(Class)} with selective {@link #getBean}
|
||||
* calls for specific bean names in preference to this Map-based retrieval method.</b>
|
||||
* Aside from lazy instantiation benefits, this also avoids any exception suppression.
|
||||
* @param type the class or interface to match, or {@code null} for all concrete beans
|
||||
* @param includeNonSingletons whether to include prototype or scoped beans too
|
||||
* or just singletons (also applies to FactoryBeans)
|
||||
|
||||
@@ -46,6 +46,21 @@ import org.springframework.lang.Nullable;
|
||||
* Alternatively, you may implement the specific methods that your callers expect,
|
||||
* for example, just {@link #getObject()} or {@link #getIfAvailable()}.
|
||||
*
|
||||
* <p>Note that {@link #getObject()} never returns {@code null} - it will throw a
|
||||
* {@link NoSuchBeanDefinitionException} instead -, whereas {@link #getIfAvailable()}
|
||||
* will return {@code null} if no matching bean is present at all. However, both
|
||||
* methods will throw a {@link NoUniqueBeanDefinitionException} if more than one
|
||||
* matching bean is found without a clear unique winner (see below). Last but not
|
||||
* least, {@link #getIfUnique()} will return {@code null} both when no matching bean
|
||||
* is found and when more than one matching bean is found without a unique winner.
|
||||
*
|
||||
* <p>Uniqueness is generally up to the container's candidate resolution algorithm
|
||||
* but always honors the "primary" flag (with only one of the candidate beans marked
|
||||
* as primary) and the "fallback" flag (with only one of the candidate beans not
|
||||
* marked as fallback). The default-candidate flag is consistently taken into
|
||||
* account as well, even for non-annotation-based injection points, with a single
|
||||
* default candidate winning in case of no clear primary/fallback indication.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 4.3
|
||||
* @param <T> the object type
|
||||
@@ -188,7 +203,7 @@ public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
|
||||
* if unique (not called otherwise)
|
||||
* @throws BeansException in case of creation errors
|
||||
* @since 5.0
|
||||
* @see #getIfAvailable()
|
||||
* @see #getIfUnique()
|
||||
*/
|
||||
default void ifUnique(Consumer<T> dependencyConsumer) throws BeansException {
|
||||
T dependency = getIfUnique();
|
||||
@@ -259,7 +274,7 @@ public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
|
||||
* @see #orderedStream(Predicate)
|
||||
*/
|
||||
default Stream<T> stream(Predicate<Class<?>> customFilter) {
|
||||
return stream().filter(obj -> customFilter.test(obj.getClass()));
|
||||
return stream(customFilter, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -273,6 +288,44 @@ public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
|
||||
* @see #stream(Predicate)
|
||||
*/
|
||||
default Stream<T> orderedStream(Predicate<Class<?>> customFilter) {
|
||||
return orderedStream(customFilter, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a custom-filtered {@link Stream} over all matching object instances,
|
||||
* without specific ordering guarantees (but typically in registration order).
|
||||
* @param customFilter a custom type filter for selecting beans among the raw
|
||||
* bean type matches (or {@link #UNFILTERED} for all raw type matches without
|
||||
* any default filtering)
|
||||
* @param includeNonSingletons whether to include prototype or scoped beans too
|
||||
* or just singletons (also applies to FactoryBeans)
|
||||
* @since 6.2.5
|
||||
* @see #stream(Predicate)
|
||||
* @see #orderedStream(Predicate, boolean)
|
||||
*/
|
||||
default Stream<T> stream(Predicate<Class<?>> customFilter, boolean includeNonSingletons) {
|
||||
if (!includeNonSingletons) {
|
||||
throw new UnsupportedOperationException("Only supports includeNonSingletons=true by default");
|
||||
}
|
||||
return stream().filter(obj -> customFilter.test(obj.getClass()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a custom-filtered {@link Stream} over all matching object instances,
|
||||
* pre-ordered according to the factory's common order comparator.
|
||||
* @param customFilter a custom type filter for selecting beans among the raw
|
||||
* bean type matches (or {@link #UNFILTERED} for all raw type matches without
|
||||
* any default filtering)
|
||||
* @param includeNonSingletons whether to include prototype or scoped beans too
|
||||
* or just singletons (also applies to FactoryBeans)
|
||||
* @since 6.2.5
|
||||
* @see #orderedStream()
|
||||
* @see #stream(Predicate)
|
||||
*/
|
||||
default Stream<T> orderedStream(Predicate<Class<?>> customFilter, boolean includeNonSingletons) {
|
||||
if (!includeNonSingletons) {
|
||||
throw new UnsupportedOperationException("Only supports includeNonSingletons=true by default");
|
||||
}
|
||||
return orderedStream().filter(obj -> customFilter.test(obj.getClass()));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -212,9 +212,9 @@ public final class AotServices<T> implements Iterable<T> {
|
||||
}
|
||||
|
||||
private <T> Map<String, T> loadBeans(Class<T> type) {
|
||||
return (this.beanFactory != null) ? BeanFactoryUtils
|
||||
.beansOfTypeIncludingAncestors(this.beanFactory, type, true, false)
|
||||
: Collections.emptyMap();
|
||||
return (this.beanFactory != null ?
|
||||
BeanFactoryUtils.beansOfTypeIncludingAncestors(this.beanFactory, type, true, false) :
|
||||
Collections.emptyMap());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-2
@@ -69,8 +69,8 @@ class BeanDefinitionMethodGeneratorFactory {
|
||||
this.excludeFilters = loader.load(BeanRegistrationExcludeFilter.class);
|
||||
for (BeanRegistrationExcludeFilter excludeFilter : this.excludeFilters) {
|
||||
if (this.excludeFilters.getSource(excludeFilter) == Source.BEAN_FACTORY) {
|
||||
Assert.state(excludeFilter instanceof BeanRegistrationAotProcessor
|
||||
|| excludeFilter instanceof BeanFactoryInitializationAotProcessor,
|
||||
Assert.state(excludeFilter instanceof BeanRegistrationAotProcessor ||
|
||||
excludeFilter instanceof BeanFactoryInitializationAotProcessor,
|
||||
() -> "BeanRegistrationExcludeFilter bean of type %s must also implement an AOT processor interface"
|
||||
.formatted(excludeFilter.getClass().getName()));
|
||||
}
|
||||
|
||||
+3
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -156,6 +156,8 @@ abstract class BeanDefinitionPropertyValueCodeGeneratorDelegates {
|
||||
.builder(SuppressWarnings.class)
|
||||
.addMember("value", "{\"rawtypes\", \"unchecked\"}")
|
||||
.build());
|
||||
method.addModifiers(javax.lang.model.element.Modifier.PRIVATE,
|
||||
javax.lang.model.element.Modifier.STATIC);
|
||||
method.returns(Map.class);
|
||||
method.addStatement("$T map = new $T($L)", Map.class,
|
||||
LinkedHashMap.class, map.size());
|
||||
|
||||
+4
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -234,8 +234,7 @@ public class InstanceSupplierCodeGenerator {
|
||||
|
||||
CodeBlock arguments = hasArguments ?
|
||||
new AutowiredArgumentsCodeGenerator(actualType, constructor)
|
||||
.generateCode(constructor.getParameterTypes(), (onInnerClass ? 1 : 0))
|
||||
: NO_ARGS;
|
||||
.generateCode(constructor.getParameterTypes(), (onInnerClass ? 1 : 0)) : NO_ARGS;
|
||||
|
||||
CodeBlock newInstance = generateNewInstanceCodeForConstructor(actualType, arguments);
|
||||
code.add(generateWithGeneratorCode(hasArguments, newInstance));
|
||||
@@ -325,8 +324,7 @@ public class InstanceSupplierCodeGenerator {
|
||||
boolean hasArguments = factoryMethod.getParameterCount() > 0;
|
||||
CodeBlock arguments = hasArguments ?
|
||||
new AutowiredArgumentsCodeGenerator(ClassUtils.getUserClass(targetClass), factoryMethod)
|
||||
.generateCode(factoryMethod.getParameterTypes())
|
||||
: NO_ARGS;
|
||||
.generateCode(factoryMethod.getParameterTypes()) : NO_ARGS;
|
||||
|
||||
CodeBlock newInstance = generateNewInstanceCodeForMethod(
|
||||
factoryBeanName, ClassUtils.getUserClass(targetClass), factoryMethodName, arguments);
|
||||
@@ -443,6 +441,7 @@ public class InstanceSupplierCodeGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
record ConstructorDescriptor(String beanName, Constructor<?> constructor, Class<?> publicType) {
|
||||
|
||||
Class<?> actualType() {
|
||||
|
||||
+36
-25
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -20,8 +20,10 @@ import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.BeanNameAware;
|
||||
import org.springframework.core.env.AbstractPropertyResolver;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.StringValueResolver;
|
||||
import org.springframework.util.SystemPropertyUtils;
|
||||
|
||||
/**
|
||||
* Abstract base class for property resource configurers that resolve placeholders
|
||||
@@ -37,16 +39,16 @@ import org.springframework.util.StringValueResolver;
|
||||
*
|
||||
* <pre class="code">
|
||||
* <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
|
||||
* <property name="driverClassName" value="${driver}" />
|
||||
* <property name="url" value="jdbc:${dbname}" />
|
||||
* <property name="driverClassName" value="${jdbc.driver}" />
|
||||
* <property name="url" value="jdbc:${jdbc.dbname}" />
|
||||
* </bean>
|
||||
* </pre>
|
||||
*
|
||||
* Example properties file:
|
||||
*
|
||||
* <pre class="code">
|
||||
* driver=com.mysql.jdbc.Driver
|
||||
* dbname=mysql:mydb</pre>
|
||||
* jdbc.driver=com.mysql.jdbc.Driver
|
||||
* jdbc.dbname=mysql:mydb</pre>
|
||||
*
|
||||
* Annotated bean definitions may take advantage of property replacement using
|
||||
* the {@link org.springframework.beans.factory.annotation.Value @Value} annotation:
|
||||
@@ -79,11 +81,12 @@ import org.springframework.util.StringValueResolver;
|
||||
* <p>Example XML property with default value:
|
||||
*
|
||||
* <pre class="code">
|
||||
* <property name="url" value="jdbc:${dbname:defaultdb}" />
|
||||
* <property name="url" value="jdbc:${jdbc.dbname:defaultdb}" />
|
||||
* </pre>
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @since 3.1
|
||||
* @see PropertyPlaceholderConfigurer
|
||||
* @see org.springframework.context.support.PropertySourcesPlaceholderConfigurer
|
||||
@@ -92,16 +95,21 @@ public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfi
|
||||
implements BeanNameAware, BeanFactoryAware {
|
||||
|
||||
/** Default placeholder prefix: {@value}. */
|
||||
public static final String DEFAULT_PLACEHOLDER_PREFIX = "${";
|
||||
public static final String DEFAULT_PLACEHOLDER_PREFIX = SystemPropertyUtils.PLACEHOLDER_PREFIX;
|
||||
|
||||
/** Default placeholder suffix: {@value}. */
|
||||
public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}";
|
||||
public static final String DEFAULT_PLACEHOLDER_SUFFIX = SystemPropertyUtils.PLACEHOLDER_SUFFIX;
|
||||
|
||||
/** Default value separator: {@value}. */
|
||||
public static final String DEFAULT_VALUE_SEPARATOR = ":";
|
||||
public static final String DEFAULT_VALUE_SEPARATOR = SystemPropertyUtils.VALUE_SEPARATOR;
|
||||
|
||||
/**
|
||||
* Default escape character: {@code '\'}.
|
||||
* @since 6.2
|
||||
* @see AbstractPropertyResolver#getDefaultEscapeCharacter()
|
||||
*/
|
||||
public static final Character DEFAULT_ESCAPE_CHARACTER = SystemPropertyUtils.ESCAPE_CHARACTER;
|
||||
|
||||
/** Default escape character: {@code '\'}. */
|
||||
public static final Character DEFAULT_ESCAPE_CHARACTER = '\\';
|
||||
|
||||
/** Defaults to {@value #DEFAULT_PLACEHOLDER_PREFIX}. */
|
||||
protected String placeholderPrefix = DEFAULT_PLACEHOLDER_PREFIX;
|
||||
@@ -113,9 +121,11 @@ public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfi
|
||||
@Nullable
|
||||
protected String valueSeparator = DEFAULT_VALUE_SEPARATOR;
|
||||
|
||||
/** Defaults to {@link #DEFAULT_ESCAPE_CHARACTER}. */
|
||||
/**
|
||||
* The default is determined by {@link AbstractPropertyResolver#getDefaultEscapeCharacter()}.
|
||||
*/
|
||||
@Nullable
|
||||
protected Character escapeCharacter = DEFAULT_ESCAPE_CHARACTER;
|
||||
protected Character escapeCharacter = AbstractPropertyResolver.getDefaultEscapeCharacter();
|
||||
|
||||
protected boolean trimValues = false;
|
||||
|
||||
@@ -133,7 +143,7 @@ public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfi
|
||||
|
||||
/**
|
||||
* Set the prefix that a placeholder string starts with.
|
||||
* The default is {@value #DEFAULT_PLACEHOLDER_PREFIX}.
|
||||
* <p>The default is {@value #DEFAULT_PLACEHOLDER_PREFIX}.
|
||||
*/
|
||||
public void setPlaceholderPrefix(String placeholderPrefix) {
|
||||
this.placeholderPrefix = placeholderPrefix;
|
||||
@@ -141,31 +151,32 @@ public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfi
|
||||
|
||||
/**
|
||||
* Set the suffix that a placeholder string ends with.
|
||||
* The default is {@value #DEFAULT_PLACEHOLDER_SUFFIX}.
|
||||
* <p>The default is {@value #DEFAULT_PLACEHOLDER_SUFFIX}.
|
||||
*/
|
||||
public void setPlaceholderSuffix(String placeholderSuffix) {
|
||||
this.placeholderSuffix = placeholderSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the separating character between the placeholder variable
|
||||
* and the associated default value, or {@code null} if no such
|
||||
* special character should be processed as a value separator.
|
||||
* The default is {@value #DEFAULT_VALUE_SEPARATOR}.
|
||||
* Specify the separating character between the placeholder variable and the
|
||||
* associated default value, or {@code null} if no such special character
|
||||
* should be processed as a value separator.
|
||||
* <p>The default is {@value #DEFAULT_VALUE_SEPARATOR}.
|
||||
*/
|
||||
public void setValueSeparator(@Nullable String valueSeparator) {
|
||||
this.valueSeparator = valueSeparator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the escape character to use to ignore placeholder prefix
|
||||
* or value separator, or {@code null} if no escaping should take
|
||||
* place.
|
||||
* <p>Default is {@link #DEFAULT_ESCAPE_CHARACTER}.
|
||||
* Set the escape character to use to ignore the
|
||||
* {@linkplain #setPlaceholderPrefix(String) placeholder prefix} and the
|
||||
* {@linkplain #setValueSeparator(String) value separator}, or {@code null}
|
||||
* if no escaping should take place.
|
||||
* <p>The default is determined by {@link AbstractPropertyResolver#getDefaultEscapeCharacter()}.
|
||||
* @since 6.2
|
||||
*/
|
||||
public void setEscapeCharacter(@Nullable Character escsEscapeCharacter) {
|
||||
this.escapeCharacter = escsEscapeCharacter;
|
||||
public void setEscapeCharacter(@Nullable Character escapeCharacter) {
|
||||
this.escapeCharacter = escapeCharacter;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+7
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -35,12 +35,14 @@ import org.springframework.beans.factory.BeanInitializationException;
|
||||
*
|
||||
* Example properties file:
|
||||
*
|
||||
* <pre class="code">dataSource.driverClassName=com.mysql.jdbc.Driver
|
||||
* <pre class="code">
|
||||
* dataSource.driverClassName=com.mysql.jdbc.Driver
|
||||
* dataSource.url=jdbc:mysql:mydb</pre>
|
||||
*
|
||||
* In contrast to PropertyPlaceholderConfigurer, the original definition can have default
|
||||
* values or no values at all for such bean properties. If an overriding properties file does
|
||||
* not have an entry for a certain bean property, the default context definition is used.
|
||||
* <p>In contrast to {@link PropertyPlaceholderConfigurer}, the original definition
|
||||
* can have default values or no values at all for such bean properties. If an
|
||||
* overriding properties file does not have an entry for a certain bean property,
|
||||
* the default context definition is used.
|
||||
*
|
||||
* <p>Note that the context definition <i>is not</i> aware of being overridden;
|
||||
* so this is not immediately obvious when looking at the XML definition file.
|
||||
|
||||
+22
-8
@@ -144,8 +144,10 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||
private final Set<Class<?>> ignoredDependencyTypes = new HashSet<>();
|
||||
|
||||
/**
|
||||
* Dependency interfaces to ignore on dependency check and autowire, as Set of
|
||||
* Class objects. By default, only the BeanFactory interface is ignored.
|
||||
* Dependency interfaces to ignore on dependency check and autowire, as a Set
|
||||
* of Class objects.
|
||||
* <p>By default, the {@code BeanNameAware}, {@code BeanFactoryAware}, and
|
||||
* {@code BeanClassLoaderAware} interfaces are ignored.
|
||||
*/
|
||||
private final Set<Class<?>> ignoredDependencyInterfaces = new HashSet<>();
|
||||
|
||||
@@ -285,11 +287,15 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||
/**
|
||||
* Ignore the given dependency interface for autowiring.
|
||||
* <p>This will typically be used by application contexts to register
|
||||
* dependencies that are resolved in other ways, like BeanFactory through
|
||||
* BeanFactoryAware or ApplicationContext through ApplicationContextAware.
|
||||
* <p>By default, only the BeanFactoryAware interface is ignored.
|
||||
* dependencies that are resolved in other ways, like {@code BeanFactory}
|
||||
* through {@code BeanFactoryAware} or {@code ApplicationContext} through
|
||||
* {@code ApplicationContextAware}.
|
||||
* <p>By default, the {@code BeanNameAware}, {@code BeanFactoryAware}, and
|
||||
* {@code BeanClassLoaderAware} interfaces are ignored.
|
||||
* For further types to ignore, invoke this method for each type.
|
||||
* @see org.springframework.beans.factory.BeanNameAware
|
||||
* @see org.springframework.beans.factory.BeanFactoryAware
|
||||
* @see org.springframework.beans.factory.BeanClassLoaderAware
|
||||
* @see org.springframework.context.ApplicationContextAware
|
||||
*/
|
||||
public void ignoreDependencyInterface(Class<?> ifc) {
|
||||
@@ -991,9 +997,17 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||
*/
|
||||
@Nullable
|
||||
private FactoryBean<?> getSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd) {
|
||||
boolean locked = this.singletonLock.tryLock();
|
||||
if (!locked) {
|
||||
return null;
|
||||
Boolean lockFlag = isCurrentThreadAllowedToHoldSingletonLock();
|
||||
if (lockFlag == null) {
|
||||
this.singletonLock.lock();
|
||||
}
|
||||
else {
|
||||
boolean locked = (lockFlag && this.singletonLock.tryLock());
|
||||
if (!locked) {
|
||||
// Avoid shortcut FactoryBean instance but allow for subsequent type-based resolution.
|
||||
resolveBeanClass(mbd, beanName);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
+18
-6
@@ -770,16 +770,16 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
public String[] getAliases(String name) {
|
||||
String beanName = transformedBeanName(name);
|
||||
List<String> aliases = new ArrayList<>();
|
||||
boolean factoryPrefix = name.startsWith(FACTORY_BEAN_PREFIX);
|
||||
boolean hasFactoryPrefix = (!name.isEmpty() && name.charAt(0) == BeanFactory.FACTORY_BEAN_PREFIX_CHAR);
|
||||
String fullBeanName = beanName;
|
||||
if (factoryPrefix) {
|
||||
if (hasFactoryPrefix) {
|
||||
fullBeanName = FACTORY_BEAN_PREFIX + beanName;
|
||||
}
|
||||
if (!fullBeanName.equals(name)) {
|
||||
aliases.add(fullBeanName);
|
||||
}
|
||||
String[] retrievedAliases = super.getAliases(beanName);
|
||||
String prefix = (factoryPrefix ? FACTORY_BEAN_PREFIX : "");
|
||||
String prefix = (hasFactoryPrefix ? FACTORY_BEAN_PREFIX : "");
|
||||
for (String retrievedAlias : retrievedAliases) {
|
||||
String alias = prefix + retrievedAlias;
|
||||
if (!alias.equals(name)) {
|
||||
@@ -1153,7 +1153,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
public BeanDefinition getMergedBeanDefinition(String name) throws BeansException {
|
||||
String beanName = transformedBeanName(name);
|
||||
// Efficiently check whether bean definition exists in this factory.
|
||||
if (!containsBeanDefinition(beanName) && getParentBeanFactory() instanceof ConfigurableBeanFactory parent) {
|
||||
if (getParentBeanFactory() instanceof ConfigurableBeanFactory parent && !containsBeanDefinition(beanName)) {
|
||||
return parent.getMergedBeanDefinition(beanName);
|
||||
}
|
||||
// Resolve merged bean definition locally.
|
||||
@@ -1292,7 +1292,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
*/
|
||||
protected String originalBeanName(String name) {
|
||||
String beanName = transformedBeanName(name);
|
||||
if (name.startsWith(FACTORY_BEAN_PREFIX)) {
|
||||
if (!name.isEmpty() && name.charAt(0) == BeanFactory.FACTORY_BEAN_PREFIX_CHAR) {
|
||||
beanName = FACTORY_BEAN_PREFIX + beanName;
|
||||
}
|
||||
return beanName;
|
||||
@@ -1474,7 +1474,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
// Cache the merged bean definition for the time being
|
||||
// (it might still get re-merged later on in order to pick up metadata changes)
|
||||
if (containingBd == null && (isCacheBeanMetadata() || isBeanEligibleForMetadataCaching(beanName))) {
|
||||
this.mergedBeanDefinitions.put(beanName, mbd);
|
||||
cacheMergedBeanDefinition(mbd, beanName);
|
||||
}
|
||||
}
|
||||
if (previous != null) {
|
||||
@@ -1503,6 +1503,18 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache the given merged bean definition.
|
||||
* <p>Subclasses can override this to derive additional cached state
|
||||
* from the final post-processed bean definition.
|
||||
* @param mbd the merged bean definition to cache
|
||||
* @param beanName the name of the bean
|
||||
* @since 6.2.6
|
||||
*/
|
||||
protected void cacheMergedBeanDefinition(RootBeanDefinition mbd, String beanName) {
|
||||
this.mergedBeanDefinitions.put(beanName, mbd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the given merged bean definition,
|
||||
* potentially throwing validation exceptions.
|
||||
|
||||
+20
@@ -35,6 +35,7 @@ import java.util.Set;
|
||||
import org.springframework.beans.BeanMetadataElement;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.ObjectFactory;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.beans.factory.config.TypedStringValue;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -279,6 +280,25 @@ abstract class AutowireUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the default-candidate status for the specified bean.
|
||||
* @param beanFactory the bean factory
|
||||
* @param beanName the name of the bean to check
|
||||
* @return whether the specified bean qualifies as a default candidate
|
||||
* @since 6.2.4
|
||||
* @see AbstractBeanDefinition#isDefaultCandidate()
|
||||
*/
|
||||
public static boolean isDefaultCandidate(ConfigurableBeanFactory beanFactory, String beanName) {
|
||||
try {
|
||||
BeanDefinition mbd = beanFactory.getMergedBeanDefinition(beanName);
|
||||
return (!(mbd instanceof AbstractBeanDefinition abd) || abd.isDefaultCandidate());
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException ex) {
|
||||
// A manually registered singleton instance not backed by a BeanDefinition.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reflective {@link InvocationHandler} for lazy access to the current target object.
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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,6 +22,7 @@ import java.lang.reflect.Method;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.aot.AotDetector;
|
||||
import org.springframework.beans.BeanInstantiationException;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
@@ -153,7 +154,7 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
|
||||
Enhancer enhancer = new Enhancer();
|
||||
enhancer.setSuperclass(beanDefinition.getBeanClass());
|
||||
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
|
||||
enhancer.setAttemptLoad(true);
|
||||
enhancer.setAttemptLoad(AotDetector.useGeneratedArtifacts());
|
||||
if (this.owner instanceof ConfigurableBeanFactory cbf) {
|
||||
ClassLoader cl = cbf.getBeanClassLoader();
|
||||
enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(cl));
|
||||
@@ -276,12 +277,11 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
@Nullable
|
||||
public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable {
|
||||
ReplaceOverride ro = (ReplaceOverride) getBeanDefinition().getMethodOverrides().getOverride(method);
|
||||
Assert.state(ro != null, "ReplaceOverride not found");
|
||||
// TODO could cache if a singleton for minor performance optimization
|
||||
MethodReplacer mr = this.owner.getBean(ro.getMethodReplacerBeanName(), MethodReplacer.class);
|
||||
return processReturnType(method, mr.reimplement(obj, method, args));
|
||||
}
|
||||
|
||||
+125
-22
@@ -76,6 +76,7 @@ import org.springframework.core.NamedThreadLocal;
|
||||
import org.springframework.core.OrderComparator;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.SpringProperties;
|
||||
import org.springframework.core.annotation.MergedAnnotation;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
|
||||
@@ -128,6 +129,20 @@ import org.springframework.util.StringUtils;
|
||||
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
|
||||
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
|
||||
|
||||
/**
|
||||
* System property that instructs Spring to enforce strict locking during bean creation,
|
||||
* rather than the mix of strict and lenient locking that 6.2 applies by default. Setting
|
||||
* this flag to "true" restores 6.1.x style locking in the entire pre-instantiation phase.
|
||||
* <p>By default, the factory infers strict locking from the encountered thread names:
|
||||
* If additional threads have names that match the thread prefix of the main bootstrap thread,
|
||||
* they are considered external (multiple external bootstrap threads calling into the factory)
|
||||
* and therefore have strict locking applied to them. This inference can be turned off through
|
||||
* explicitly setting this flag to "false" rather than leaving it unspecified.
|
||||
* @since 6.2.6
|
||||
* @see #preInstantiateSingletons()
|
||||
*/
|
||||
public static final String STRICT_LOCKING_PROPERTY_NAME = "spring.locking.strict";
|
||||
|
||||
@Nullable
|
||||
private static Class<?> jakartaInjectProviderClass;
|
||||
|
||||
@@ -147,6 +162,10 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
private static final Map<String, Reference<DefaultListableBeanFactory>> serializableFactories =
|
||||
new ConcurrentHashMap<>(8);
|
||||
|
||||
/** Whether strict locking is enforced or relaxed in this factory. */
|
||||
@Nullable
|
||||
private final Boolean strictLocking = SpringProperties.checkFlag(STRICT_LOCKING_PROPERTY_NAME);
|
||||
|
||||
/** Optional id for this factory, for serialization purposes. */
|
||||
@Nullable
|
||||
private String serializationId;
|
||||
@@ -199,7 +218,9 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
/** Whether bean definition metadata may be cached for all beans. */
|
||||
private volatile boolean configurationFrozen;
|
||||
|
||||
private volatile boolean preInstantiationPhase;
|
||||
/** Name prefix of main thread: only set during pre-instantiation phase. */
|
||||
@Nullable
|
||||
private volatile String mainThreadPrefix;
|
||||
|
||||
private final NamedThreadLocal<PreInstantiation> preInstantiationThread =
|
||||
new NamedThreadLocal<>("Pre-instantiation thread marker");
|
||||
@@ -487,14 +508,14 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Stream<T> stream() {
|
||||
return Arrays.stream(getBeanNamesForTypedStream(requiredType, allowEagerInit))
|
||||
return Arrays.stream(beanNamesForStream(requiredType, true, allowEagerInit))
|
||||
.map(name -> (T) getBean(name))
|
||||
.filter(bean -> !(bean instanceof NullBean));
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Stream<T> orderedStream() {
|
||||
String[] beanNames = getBeanNamesForTypedStream(requiredType, allowEagerInit);
|
||||
String[] beanNames = beanNamesForStream(requiredType, true, allowEagerInit);
|
||||
if (beanNames.length == 0) {
|
||||
return Stream.empty();
|
||||
}
|
||||
@@ -510,16 +531,16 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Stream<T> stream(Predicate<Class<?>> customFilter) {
|
||||
return Arrays.stream(getBeanNamesForTypedStream(requiredType, allowEagerInit))
|
||||
public Stream<T> stream(Predicate<Class<?>> customFilter, boolean includeNonSingletons) {
|
||||
return Arrays.stream(beanNamesForStream(requiredType, includeNonSingletons, allowEagerInit))
|
||||
.filter(name -> customFilter.test(getType(name)))
|
||||
.map(name -> (T) getBean(name))
|
||||
.filter(bean -> !(bean instanceof NullBean));
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Stream<T> orderedStream(Predicate<Class<?>> customFilter) {
|
||||
String[] beanNames = getBeanNamesForTypedStream(requiredType, allowEagerInit);
|
||||
public Stream<T> orderedStream(Predicate<Class<?>> customFilter, boolean includeNonSingletons) {
|
||||
String[] beanNames = beanNamesForStream(requiredType, includeNonSingletons, allowEagerInit);
|
||||
if (beanNames.length == 0) {
|
||||
return Stream.empty();
|
||||
}
|
||||
@@ -559,8 +580,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
return null;
|
||||
}
|
||||
|
||||
private String[] getBeanNamesForTypedStream(ResolvableType requiredType, boolean allowEagerInit) {
|
||||
return BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this, requiredType, true, allowEagerInit);
|
||||
private String[] beanNamesForStream(ResolvableType requiredType, boolean includeNonSingletons, boolean allowEagerInit) {
|
||||
return BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this, requiredType, includeNonSingletons, allowEagerInit);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -626,10 +647,15 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (includeNonSingletons || isNonLazyDecorated ||
|
||||
(allowFactoryBeanInit && isSingleton(beanName, mbd, dbd))) {
|
||||
if (includeNonSingletons || isNonLazyDecorated) {
|
||||
matchFound = isTypeMatch(beanName, type, allowFactoryBeanInit);
|
||||
}
|
||||
else if (allowFactoryBeanInit) {
|
||||
// Type check before singleton check, avoiding FactoryBean instantiation
|
||||
// for early FactoryBean.isSingleton() calls on non-matching beans.
|
||||
matchFound = isTypeMatch(beanName, type, allowFactoryBeanInit) &&
|
||||
isSingleton(beanName, mbd, dbd);
|
||||
}
|
||||
if (!matchFound) {
|
||||
// In case of FactoryBean, try to match FactoryBean instance itself next.
|
||||
beanName = FACTORY_BEAN_PREFIX + beanName;
|
||||
@@ -895,7 +921,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
String beanName, DependencyDescriptor descriptor, AutowireCandidateResolver resolver)
|
||||
throws NoSuchBeanDefinitionException {
|
||||
|
||||
String bdName = BeanFactoryUtils.transformedBeanName(beanName);
|
||||
String bdName = transformedBeanName(beanName);
|
||||
if (containsBeanDefinition(bdName)) {
|
||||
return isAutowireCandidate(beanName, getMergedLocalBeanDefinition(bdName), descriptor, resolver);
|
||||
}
|
||||
@@ -929,7 +955,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
protected boolean isAutowireCandidate(String beanName, RootBeanDefinition mbd,
|
||||
DependencyDescriptor descriptor, AutowireCandidateResolver resolver) {
|
||||
|
||||
String bdName = BeanFactoryUtils.transformedBeanName(beanName);
|
||||
String bdName = transformedBeanName(beanName);
|
||||
resolveBeanClass(mbd, bdName);
|
||||
if (mbd.isFactoryMethodUnique && mbd.factoryMethodToIntrospect == null) {
|
||||
new ConstructorResolver(this).resolveFactoryMethodIfPossible(mbd);
|
||||
@@ -1007,6 +1033,14 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
return super.obtainInstanceFromSupplier(supplier, beanName, mbd);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cacheMergedBeanDefinition(RootBeanDefinition mbd, String beanName) {
|
||||
super.cacheMergedBeanDefinition(mbd, beanName);
|
||||
if (mbd.isPrimary()) {
|
||||
this.primaryBeanNames.add(beanName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void checkMergedBeanDefinition(RootBeanDefinition mbd, String beanName, @Nullable Object[] args) {
|
||||
super.checkMergedBeanDefinition(mbd, beanName, args);
|
||||
@@ -1019,7 +1053,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Bean intended to be initialized in main bootstrap thread
|
||||
// Bean intended to be initialized in main bootstrap thread.
|
||||
if (this.preInstantiationThread.get() == PreInstantiation.BACKGROUND) {
|
||||
throw new BeanCurrentlyInCreationException(beanName, "Bean marked for mainline initialization " +
|
||||
"but requested in background thread - enforce early instantiation in mainline thread " +
|
||||
@@ -1031,7 +1065,40 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
@Override
|
||||
@Nullable
|
||||
protected Boolean isCurrentThreadAllowedToHoldSingletonLock() {
|
||||
return (this.preInstantiationPhase ? this.preInstantiationThread.get() != PreInstantiation.BACKGROUND : null);
|
||||
String mainThreadPrefix = this.mainThreadPrefix;
|
||||
if (mainThreadPrefix != null) {
|
||||
// We only differentiate in the preInstantiateSingletons phase, using
|
||||
// the volatile mainThreadPrefix field as an indicator for that phase.
|
||||
|
||||
PreInstantiation preInstantiation = this.preInstantiationThread.get();
|
||||
if (preInstantiation != null) {
|
||||
// A Spring-managed bootstrap thread:
|
||||
// MAIN is allowed to lock (true) or even forced to lock (null),
|
||||
// BACKGROUND is never allowed to lock (false).
|
||||
return switch (preInstantiation) {
|
||||
case MAIN -> (Boolean.TRUE.equals(this.strictLocking) ? null : true);
|
||||
case BACKGROUND -> false;
|
||||
};
|
||||
}
|
||||
|
||||
// Not a Spring-managed bootstrap thread...
|
||||
if (Boolean.FALSE.equals(this.strictLocking)) {
|
||||
// Explicitly configured to use lenient locking wherever possible.
|
||||
return true;
|
||||
}
|
||||
else if (this.strictLocking == null) {
|
||||
// No explicit locking configuration -> infer appropriate locking.
|
||||
if (!getThreadNamePrefix().equals(mainThreadPrefix)) {
|
||||
// An unmanaged thread (assumed to be application-internal) with lenient locking,
|
||||
// and not part of the same thread pool that provided the main bootstrap thread
|
||||
// (excluding scenarios where we are hit by multiple external bootstrap threads).
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Traditional behavior: forced to always hold a full lock.
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1047,8 +1114,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
// Trigger initialization of all non-lazy singleton beans...
|
||||
List<CompletableFuture<?>> futures = new ArrayList<>();
|
||||
|
||||
this.preInstantiationPhase = true;
|
||||
this.preInstantiationThread.set(PreInstantiation.MAIN);
|
||||
this.mainThreadPrefix = getThreadNamePrefix();
|
||||
try {
|
||||
for (String beanName : beanNames) {
|
||||
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
|
||||
@@ -1061,8 +1128,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
}
|
||||
}
|
||||
finally {
|
||||
this.mainThreadPrefix = null;
|
||||
this.preInstantiationThread.remove();
|
||||
this.preInstantiationPhase = false;
|
||||
}
|
||||
|
||||
if (!futures.isEmpty()) {
|
||||
@@ -1156,6 +1223,12 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
}
|
||||
}
|
||||
|
||||
private static String getThreadNamePrefix() {
|
||||
String name = Thread.currentThread().getName();
|
||||
int numberSeparator = name.lastIndexOf('-');
|
||||
return (numberSeparator >= 0 ? name.substring(0, numberSeparator) : name);
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Implementation of BeanDefinitionRegistry interface
|
||||
@@ -1497,6 +1570,9 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
if (candidateName == null) {
|
||||
candidateName = determineHighestPriorityCandidate(candidates, requiredType.toClass());
|
||||
}
|
||||
if (candidateName == null) {
|
||||
candidateName = determineDefaultCandidate(candidates);
|
||||
}
|
||||
if (candidateName != null) {
|
||||
Object beanInstance = candidates.get(candidateName);
|
||||
if (beanInstance == null) {
|
||||
@@ -1967,7 +2043,12 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
if (priorityCandidate != null) {
|
||||
return priorityCandidate;
|
||||
}
|
||||
// Step 4: pick directly registered dependency
|
||||
// Step 4: pick unique default-candidate
|
||||
String defaultCandidate = determineDefaultCandidate(candidates);
|
||||
if (defaultCandidate != null) {
|
||||
return defaultCandidate;
|
||||
}
|
||||
// Step 5: pick directly registered dependency
|
||||
for (Map.Entry<String, Object> entry : candidates.entrySet()) {
|
||||
String candidateName = entry.getKey();
|
||||
Object beanInstance = entry.getValue();
|
||||
@@ -2128,6 +2209,28 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a unique "default-candidate" among remaining non-default candidates.
|
||||
* @param candidates a Map of candidate names and candidate instances
|
||||
* (or candidate classes if not created yet) that match the required type
|
||||
* @return the name of the default candidate, or {@code null} if none found
|
||||
* @since 6.2.4
|
||||
* @see AbstractBeanDefinition#isDefaultCandidate()
|
||||
*/
|
||||
@Nullable
|
||||
private String determineDefaultCandidate(Map<String, Object> candidates) {
|
||||
String defaultBeanName = null;
|
||||
for (String candidateBeanName : candidates.keySet()) {
|
||||
if (AutowireUtils.isDefaultCandidate(this, candidateBeanName)) {
|
||||
if (defaultBeanName != null) {
|
||||
return null;
|
||||
}
|
||||
defaultBeanName = candidateBeanName;
|
||||
}
|
||||
}
|
||||
return defaultBeanName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the given candidate name matches the bean name or the aliases
|
||||
* stored in this bean definition.
|
||||
@@ -2514,8 +2617,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Object> stream(Predicate<Class<?>> customFilter) {
|
||||
return Arrays.stream(getBeanNamesForTypedStream(this.descriptor.getResolvableType(), true))
|
||||
public Stream<Object> stream(Predicate<Class<?>> customFilter, boolean includeNonSingletons) {
|
||||
return Arrays.stream(beanNamesForStream(this.descriptor.getResolvableType(), includeNonSingletons, true))
|
||||
.filter(name -> AutowireUtils.isAutowireCandidate(DefaultListableBeanFactory.this, name))
|
||||
.filter(name -> customFilter.test(getType(name)))
|
||||
.map(name -> getBean(name))
|
||||
@@ -2523,8 +2626,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Object> orderedStream(Predicate<Class<?>> customFilter) {
|
||||
String[] beanNames = getBeanNamesForTypedStream(this.descriptor.getResolvableType(), true);
|
||||
public Stream<Object> orderedStream(Predicate<Class<?>> customFilter, boolean includeNonSingletons) {
|
||||
String[] beanNames = beanNamesForStream(this.descriptor.getResolvableType(), includeNonSingletons, true);
|
||||
if (beanNames.length == 0) {
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
+94
-31
@@ -17,6 +17,7 @@
|
||||
package org.springframework.beans.factory.support;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -110,6 +111,12 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||
/** Names of beans that are currently in lenient creation. */
|
||||
private final Set<String> singletonsInLenientCreation = new HashSet<>();
|
||||
|
||||
/** Map from one creation thread waiting on a lenient creation thread. */
|
||||
private final Map<Thread, Thread> lenientWaitingThreads = new HashMap<>();
|
||||
|
||||
/** Map from bean name to actual creation thread for currently created beans. */
|
||||
private final Map<String, Thread> currentCreationThreads = new ConcurrentHashMap<>();
|
||||
|
||||
/** Flag that indicates whether we're currently within destroySingletons. */
|
||||
private volatile boolean singletonsCurrentlyInDestruction = false;
|
||||
|
||||
@@ -250,10 +257,11 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
|
||||
Assert.notNull(beanName, "Bean name must not be null");
|
||||
|
||||
Thread currentThread = Thread.currentThread();
|
||||
Boolean lockFlag = isCurrentThreadAllowedToHoldSingletonLock();
|
||||
boolean acquireLock = !Boolean.FALSE.equals(lockFlag);
|
||||
boolean locked = (acquireLock && this.singletonLock.tryLock());
|
||||
boolean lenient = false;
|
||||
|
||||
try {
|
||||
Object singletonObject = this.singletonObjects.get(beanName);
|
||||
if (singletonObject == null) {
|
||||
@@ -263,14 +271,15 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||
// Fallback as of 6.2: process given singleton bean outside of singleton lock.
|
||||
// Thread-safe exposure is still guaranteed, there is just a risk of collisions
|
||||
// when triggering creation of other beans as dependencies of the current bean.
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Creating singleton bean '" + beanName + "' in thread \"" +
|
||||
Thread.currentThread().getName() + "\" while other thread holds " +
|
||||
"singleton lock for other beans " + this.singletonsCurrentlyInCreation);
|
||||
}
|
||||
lenient = true;
|
||||
this.lenientCreationLock.lock();
|
||||
try {
|
||||
if (logger.isInfoEnabled()) {
|
||||
Set<String> lockedBeans = new HashSet<>(this.singletonsCurrentlyInCreation);
|
||||
lockedBeans.removeAll(this.singletonsInLenientCreation);
|
||||
logger.info("Obtaining singleton bean '" + beanName + "' in thread \"" +
|
||||
currentThread.getName() + "\" while other thread holds singleton " +
|
||||
"lock for other beans " + lockedBeans);
|
||||
}
|
||||
this.singletonsInLenientCreation.add(beanName);
|
||||
}
|
||||
finally {
|
||||
@@ -306,14 +315,27 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||
this.lenientCreationLock.lock();
|
||||
try {
|
||||
while ((singletonObject = this.singletonObjects.get(beanName)) == null) {
|
||||
Thread otherThread = this.currentCreationThreads.get(beanName);
|
||||
if (otherThread != null && (otherThread == currentThread ||
|
||||
checkDependentWaitingThreads(otherThread, currentThread))) {
|
||||
throw ex;
|
||||
}
|
||||
if (!this.singletonsInLenientCreation.contains(beanName)) {
|
||||
break;
|
||||
}
|
||||
if (otherThread != null) {
|
||||
this.lenientWaitingThreads.put(currentThread, otherThread);
|
||||
}
|
||||
try {
|
||||
this.lenientCreationFinished.await();
|
||||
}
|
||||
catch (InterruptedException ie) {
|
||||
Thread.currentThread().interrupt();
|
||||
currentThread.interrupt();
|
||||
}
|
||||
finally {
|
||||
if (otherThread != null) {
|
||||
this.lenientWaitingThreads.remove(currentThread);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -329,7 +351,7 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||
// Try late locking for waiting on specific bean to be finished.
|
||||
this.singletonLock.lock();
|
||||
locked = true;
|
||||
// Singleton object should have appeared in the meantime.
|
||||
// Lock-created singleton object should have appeared in the meantime.
|
||||
singletonObject = this.singletonObjects.get(beanName);
|
||||
if (singletonObject != null) {
|
||||
return singletonObject;
|
||||
@@ -343,8 +365,18 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||
this.suppressedExceptions = new LinkedHashSet<>();
|
||||
}
|
||||
try {
|
||||
singletonObject = singletonFactory.getObject();
|
||||
newSingleton = true;
|
||||
// Leniently created singleton object could have appeared in the meantime.
|
||||
singletonObject = this.singletonObjects.get(beanName);
|
||||
if (singletonObject == null) {
|
||||
this.currentCreationThreads.put(beanName, currentThread);
|
||||
try {
|
||||
singletonObject = singletonFactory.getObject();
|
||||
}
|
||||
finally {
|
||||
this.currentCreationThreads.remove(beanName);
|
||||
}
|
||||
newSingleton = true;
|
||||
}
|
||||
}
|
||||
catch (IllegalStateException ex) {
|
||||
// Has the singleton object implicitly appeared in the meantime ->
|
||||
@@ -368,8 +400,18 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||
}
|
||||
afterSingletonCreation(beanName);
|
||||
}
|
||||
|
||||
if (newSingleton) {
|
||||
addSingleton(beanName, singletonObject);
|
||||
try {
|
||||
addSingleton(beanName, singletonObject);
|
||||
}
|
||||
catch (IllegalStateException ex) {
|
||||
// Leniently accept same instance if implicitly appeared.
|
||||
Object object = this.singletonObjects.get(beanName);
|
||||
if (singletonObject != object) {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return singletonObject;
|
||||
@@ -378,27 +420,41 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||
if (locked) {
|
||||
this.singletonLock.unlock();
|
||||
}
|
||||
if (lenient) {
|
||||
this.lenientCreationLock.lock();
|
||||
try {
|
||||
this.singletonsInLenientCreation.remove(beanName);
|
||||
this.lenientCreationFinished.signalAll();
|
||||
}
|
||||
finally {
|
||||
this.lenientCreationLock.unlock();
|
||||
}
|
||||
this.lenientCreationLock.lock();
|
||||
try {
|
||||
this.singletonsInLenientCreation.remove(beanName);
|
||||
this.lenientWaitingThreads.entrySet().removeIf(
|
||||
entry -> entry.getValue() == currentThread);
|
||||
this.lenientCreationFinished.signalAll();
|
||||
}
|
||||
finally {
|
||||
this.lenientCreationLock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkDependentWaitingThreads(Thread waitingThread, Thread candidateThread) {
|
||||
Thread threadToCheck = waitingThread;
|
||||
while ((threadToCheck = this.lenientWaitingThreads.get(threadToCheck)) != null) {
|
||||
if (threadToCheck == candidateThread) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the current thread is allowed to hold the singleton lock.
|
||||
* <p>By default, any thread may acquire and hold the singleton lock, except
|
||||
* background threads from {@link DefaultListableBeanFactory#setBootstrapExecutor}.
|
||||
* @return {@code false} if the current thread is explicitly not allowed to hold
|
||||
* the lock, {@code true} if it is explicitly allowed to hold the lock but also
|
||||
* accepts lenient fallback behavior, or {@code null} if there is no specific
|
||||
* indication (traditional behavior: always holding a full lock)
|
||||
* <p>By default, all threads are forced to hold a full lock through {@code null}.
|
||||
* {@link DefaultListableBeanFactory} overrides this to specifically handle its
|
||||
* threads during the pre-instantiation phase: {@code true} for the main thread,
|
||||
* {@code false} for managed background threads, and configuration-dependent
|
||||
* behavior for unmanaged threads.
|
||||
* @return {@code true} if the current thread is explicitly allowed to hold the
|
||||
* lock but also accepts lenient fallback behavior, {@code false} if it is
|
||||
* explicitly not allowed to hold the lock and therefore forced to use lenient
|
||||
* fallback behavior, or {@code null} if there is no specific indication
|
||||
* (traditional behavior: forced to always hold a full lock)
|
||||
* @since 6.2
|
||||
*/
|
||||
@Nullable
|
||||
@@ -697,12 +753,19 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||
// For an individual destruction, remove the registered instance now.
|
||||
// As of 6.2, this happens after the current bean's destruction step,
|
||||
// allowing for late bean retrieval by on-demand suppliers etc.
|
||||
this.singletonLock.lock();
|
||||
try {
|
||||
if (this.currentCreationThreads.get(beanName) == Thread.currentThread()) {
|
||||
// Local remove after failed creation step -> without singleton lock
|
||||
// since bean creation may have happened leniently without any lock.
|
||||
removeSingleton(beanName);
|
||||
}
|
||||
finally {
|
||||
this.singletonLock.unlock();
|
||||
else {
|
||||
this.singletonLock.lock();
|
||||
try {
|
||||
removeSingleton(beanName);
|
||||
}
|
||||
finally {
|
||||
this.singletonLock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+22
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -118,7 +118,15 @@ public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanReg
|
||||
*/
|
||||
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
|
||||
if (factory.isSingleton() && containsSingleton(beanName)) {
|
||||
this.singletonLock.lock();
|
||||
Boolean lockFlag = isCurrentThreadAllowedToHoldSingletonLock();
|
||||
boolean locked;
|
||||
if (lockFlag == null) {
|
||||
this.singletonLock.lock();
|
||||
locked = true;
|
||||
}
|
||||
else {
|
||||
locked = (lockFlag && this.singletonLock.tryLock());
|
||||
}
|
||||
try {
|
||||
Object object = this.factoryBeanObjectCache.get(beanName);
|
||||
if (object == null) {
|
||||
@@ -131,11 +139,13 @@ public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanReg
|
||||
}
|
||||
else {
|
||||
if (shouldPostProcess) {
|
||||
if (isSingletonCurrentlyInCreation(beanName)) {
|
||||
// Temporarily return non-post-processed object, not storing it yet
|
||||
return object;
|
||||
if (locked) {
|
||||
if (isSingletonCurrentlyInCreation(beanName)) {
|
||||
// Temporarily return non-post-processed object, not storing it yet
|
||||
return object;
|
||||
}
|
||||
beforeSingletonCreation(beanName);
|
||||
}
|
||||
beforeSingletonCreation(beanName);
|
||||
try {
|
||||
object = postProcessObjectFromFactoryBean(object, beanName);
|
||||
}
|
||||
@@ -144,7 +154,9 @@ public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanReg
|
||||
"Post-processing of FactoryBean's singleton object failed", ex);
|
||||
}
|
||||
finally {
|
||||
afterSingletonCreation(beanName);
|
||||
if (locked) {
|
||||
afterSingletonCreation(beanName);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (containsSingleton(beanName)) {
|
||||
@@ -155,7 +167,9 @@ public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanReg
|
||||
return object;
|
||||
}
|
||||
finally {
|
||||
this.singletonLock.unlock();
|
||||
if (locked) {
|
||||
this.singletonLock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -87,7 +87,7 @@ public class MethodOverrides {
|
||||
|
||||
/**
|
||||
* Return the override for the given method, if any.
|
||||
* @param method method to check for overrides for
|
||||
* @param method the method to check for overrides for
|
||||
* @return the method override, or {@code null} if none
|
||||
*/
|
||||
@Nullable
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -35,7 +35,7 @@ public interface MethodReplacer {
|
||||
* @param obj the instance we're reimplementing the method for
|
||||
* @param method the method to reimplement
|
||||
* @param args arguments to the method
|
||||
* @return return value for the method
|
||||
* @return the return value for the method
|
||||
*/
|
||||
Object reimplement(Object obj, Method method, Object[] args) throws Throwable;
|
||||
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2025 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.
|
||||
@@ -405,10 +405,10 @@ public class PropertiesBeanDefinitionReader extends AbstractBeanDefinitionReader
|
||||
/**
|
||||
* Get all property values, given a prefix (which will be stripped)
|
||||
* and add the bean they define to the factory with the given name.
|
||||
* @param beanName name of the bean to define
|
||||
* @param beanName the name of the bean to define
|
||||
* @param map a Map containing string pairs
|
||||
* @param prefix prefix of each entry, which will be stripped
|
||||
* @param resourceDescription description of the resource that the
|
||||
* @param prefix the prefix of each entry, which will be stripped
|
||||
* @param resourceDescription the description of the resource that the
|
||||
* Map came from (for logging purposes)
|
||||
* @throws BeansException if the bean definition could not be parsed or registered
|
||||
*/
|
||||
|
||||
+36
-2
@@ -67,10 +67,44 @@ public class SimpleAutowireCandidateResolver implements AutowireCandidateResolve
|
||||
* @see AbstractBeanDefinition#isDefaultCandidate()
|
||||
*/
|
||||
public static <T> Map<String, T> resolveAutowireCandidates(ConfigurableListableBeanFactory lbf, Class<T> type) {
|
||||
return resolveAutowireCandidates(lbf, type, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a map of all beans of the given type, also picking up beans defined in
|
||||
* ancestor bean factories, with the specific condition that each bean actually
|
||||
* has autowire candidate status. This matches simple injection point resolution
|
||||
* as implemented by this {@link AutowireCandidateResolver} strategy, including
|
||||
* beans which are not marked as default candidates but excluding beans which
|
||||
* are not even marked as autowire candidates.
|
||||
* @param lbf the bean factory
|
||||
* @param type the type of bean to match
|
||||
* @param includeNonSingletons whether to include prototype or scoped beans too
|
||||
* or just singletons (also applies to FactoryBeans)
|
||||
* @param allowEagerInit whether to initialize <i>lazy-init singletons</i> and
|
||||
* <i>objects created by FactoryBeans</i> (or by factory methods with a
|
||||
* "factory-bean" reference) for the type check. Note that FactoryBeans need to be
|
||||
* eagerly initialized to determine their type: So be aware that passing in "true"
|
||||
* for this flag will initialize FactoryBeans and "factory-bean" references.
|
||||
* @return the Map of matching bean instances, or an empty Map if none
|
||||
* @throws BeansException if a bean could not be created
|
||||
* @since 6.2.5
|
||||
* @see BeanFactoryUtils#beansOfTypeIncludingAncestors(ListableBeanFactory, Class, boolean, boolean)
|
||||
* @see org.springframework.beans.factory.config.BeanDefinition#isAutowireCandidate()
|
||||
* @see AbstractBeanDefinition#isDefaultCandidate()
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> Map<String, T> resolveAutowireCandidates(ConfigurableListableBeanFactory lbf, Class<T> type,
|
||||
boolean includeNonSingletons, boolean allowEagerInit) {
|
||||
|
||||
Map<String, T> candidates = new LinkedHashMap<>();
|
||||
for (String beanName : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(lbf, type)) {
|
||||
for (String beanName : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(lbf, type,
|
||||
includeNonSingletons, allowEagerInit)) {
|
||||
if (AutowireUtils.isAutowireCandidate(lbf, beanName)) {
|
||||
candidates.put(beanName, lbf.getBean(beanName, type));
|
||||
Object beanInstance = lbf.getBean(beanName);
|
||||
if (!(beanInstance instanceof NullBean)) {
|
||||
candidates.put(beanName, (T) beanInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
return candidates;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user