mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
516 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 66f4509c2c | |||
| b8395a2321 | |||
| 6df8be8be3 | |||
| 4a599d0b22 | |||
| a1463c2bf2 | |||
| 23eff5c650 | |||
| c44bb29aa5 | |||
| 0c22866b72 | |||
| 0c6957e395 | |||
| 8d51fc0444 | |||
| bad01011da | |||
| 867a199507 | |||
| 1b4a4ac51f | |||
| dd0d26b4ba | |||
| d074f660a1 | |||
| 1b8baffbd7 | |||
| 56c63c779f | |||
| 198cf063fd | |||
| b1b6b544a2 | |||
| 9d13ea290f | |||
| c9163b77df | |||
| 033c8df53f | |||
| b272f4e615 | |||
| bafff5079f | |||
| ccaecab500 | |||
| 67e03105b5 | |||
| 76bc9cf325 | |||
| db52c77cca | |||
| 1e742aae34 | |||
| 20dd585c93 | |||
| 707eb701dc | |||
| 2c97996796 | |||
| 3a068b807b | |||
| f54b19ff90 | |||
| 494d2ab727 | |||
| 627d9cf8be | |||
| dd3a67c7ab | |||
| 85cc229063 | |||
| fa95f12be0 | |||
| 035cc72fc8 | |||
| 29a39b617e | |||
| a1471a9266 | |||
| 21793b4f93 | |||
| 2d255c4d5e | |||
| 10391586d1 | |||
| edadc79835 | |||
| 3783d31c09 | |||
| 87730f76b1 | |||
| 6fae3e150e | |||
| ad44e8ab0a | |||
| aadf96ba92 | |||
| 2baf064d04 | |||
| 6121e2f526 | |||
| 8e33805d29 | |||
| 1f19bb2311 | |||
| 2784410cc6 | |||
| f4ac323409 | |||
| 6db00e63c7 | |||
| ed613e767a | |||
| 65781046cf | |||
| 0ee36095e7 | |||
| 0fc38117df | |||
| 86b8a70ce6 | |||
| 4bf759d872 | |||
| 770cbd2fb5 | |||
| e5f04e5ddf | |||
| c18784678d | |||
| 51cdff591c | |||
| d93114df9a | |||
| 5ac4c3bd76 | |||
| 05c3ffb2fb | |||
| 19e8ed130c | |||
| 4464251754 | |||
| 8868fe2ea5 | |||
| c373f496f3 | |||
| eb1883bdc4 | |||
| df6f66110f | |||
| ac235a0c43 | |||
| a4e3af5cbe | |||
| e71117dcdf | |||
| d5874ab99e | |||
| 99327b7db1 | |||
| 3e06441d97 | |||
| 01f2925048 | |||
| 44a37000ec | |||
| 53fe5fafed | |||
| b9bd98fc5b | |||
| 7714110940 | |||
| 75d1278bde | |||
| 620f558547 | |||
| 1e78cc35e5 | |||
| 11fdb5ba17 | |||
| 9957bb6918 | |||
| 6a7a0bddb7 | |||
| 5c012bbb0c | |||
| 5df6e8825d | |||
| 654e822676 | |||
| e943058b18 | |||
| cafb38ad1d | |||
| e778d2e908 | |||
| c5bcfc7682 | |||
| 5752e03d97 | |||
| dc26d3b0ec | |||
| 7f94c64b72 | |||
| 50e55d5219 | |||
| fcd4ba2f1f | |||
| e73107341c | |||
| f8a33cd66e | |||
| f088d4a05b | |||
| 84c28995fb | |||
| 925fa0272b | |||
| 09aa59f9e7 | |||
| 7874a59771 | |||
| 4dab35205d | |||
| 5c6b9be3a1 | |||
| 33fba8ea0c | |||
| 9aa707ec4b | |||
| ea30c8fb5b | |||
| 6bdf7ad36a | |||
| d2108d2db6 | |||
| f9be717602 | |||
| bb446a3905 | |||
| f3dce4bb9a | |||
| 70cb96c1d8 | |||
| 6ec264252b | |||
| 187b4e5ea6 | |||
| 3bc607df53 | |||
| 69c92f9ac7 | |||
| 875eeabb6f | |||
| 7a60e2024b | |||
| da95542d8f | |||
| 6669ab1ae7 | |||
| d05ac097dd | |||
| 35103b0cd1 | |||
| 3b50f992fe | |||
| 4bd54dffbc | |||
| 86650d1a39 | |||
| 87424cd605 | |||
| 80e82cd43f | |||
| 66ce8c9a25 | |||
| e9fcb21d55 | |||
| ee9dff30c5 | |||
| 2158410853 | |||
| e0a55c2caa | |||
| 0b96da4b6d | |||
| 387a16bd4e | |||
| 8b5d993e61 | |||
| 5459304a4b | |||
| 00d4830e5c | |||
| 3c2e21a578 | |||
| 5f053401e2 | |||
| 63770fb074 | |||
| 9df4bce043 | |||
| 19fd8159b2 | |||
| 147abc91a5 | |||
| e12eb9436d | |||
| 23162bb306 | |||
| a2c5fed494 | |||
| 957b6b2caf | |||
| 35f458fa5f | |||
| 8e83f93bcb | |||
| 407113945d | |||
| 4cf5c7796d | |||
| 847e8a2b23 | |||
| 9aab4a60f5 | |||
| 1fe2216c59 | |||
| 5ff9e6955c | |||
| 0cb4043aac | |||
| 6030e62766 | |||
| 867b9f61b2 | |||
| 6300fb37ad | |||
| 0d22569422 | |||
| 06658c3c71 | |||
| a37abd5e54 | |||
| 18456dec52 | |||
| d17c75a7ef | |||
| f547b6ad2a | |||
| e1bd13d3d6 | |||
| 08237da4b4 | |||
| f7496a393d | |||
| 0f92ba1663 | |||
| 865fa33927 | |||
| 9120f87897 | |||
| edd1e9134f | |||
| fef3cf8e58 | |||
| ce0923b946 | |||
| 227049824c | |||
| 8f130316d2 | |||
| 54c4f1b226 | |||
| 062c6241e1 | |||
| a51eb29e50 | |||
| 50d4a44dfc | |||
| ebf2cef94e | |||
| c89002a0fb | |||
| 29a4dabbe7 | |||
| f5f8eab405 | |||
| 659500bc1f | |||
| 4235a11c4f | |||
| 966b0a92c6 | |||
| 4ca70256d6 | |||
| c892ce5537 | |||
| 76b8bb2c75 | |||
| db7654225e | |||
| 78fce80c43 | |||
| 268043e9c9 | |||
| 2880e6fba5 | |||
| 11dc11e989 | |||
| 10de295a72 | |||
| 1227fe5774 | |||
| ea41051651 | |||
| 311c58ea2d | |||
| 40f1cf67bd | |||
| 6da9aed055 | |||
| 12a01a680b | |||
| 740f3b797f | |||
| ab48b88f91 | |||
| eda35e8074 | |||
| e4887f3ed9 | |||
| 1cdbd68aec | |||
| 453c0e5191 | |||
| a6b567a630 | |||
| 7a516170ef | |||
| 249f6f2da5 | |||
| 73766c01e6 | |||
| 278f228688 | |||
| c0c4298048 | |||
| 61f89a1f04 | |||
| 3e3f05109f | |||
| 2d1d14b145 | |||
| 1641cb75e2 | |||
| c01e1b8901 | |||
| 2e4e43b5bd | |||
| 6876c284f4 | |||
| b167152108 | |||
| bbf73848b5 | |||
| 1af259f928 | |||
| 1396daa4b6 | |||
| 1b409d5290 | |||
| ca14202d78 | |||
| 7231f22c23 | |||
| 3470240ef0 | |||
| 7598bca799 | |||
| f9588de247 | |||
| 2b3539a6de | |||
| 2731d4f100 | |||
| 00fffb7ab0 | |||
| 89b7a6bf47 | |||
| 19570338c9 | |||
| 906a9f7982 | |||
| 6fed3a0d6b | |||
| 8934eb8464 | |||
| 4b9f89101d | |||
| b204f2e396 | |||
| 112f755e17 | |||
| 542d0ef0b4 | |||
| 452b2df849 | |||
| 229b4782ee | |||
| ad1554a631 | |||
| 8be77cc650 | |||
| d0d0ed0578 | |||
| 9911d91f08 | |||
| 620a87bcbc | |||
| 368036cab4 | |||
| 8a6c0cd221 | |||
| b0fc2fe473 | |||
| 2952cb95f5 | |||
| c91708c1c0 | |||
| 47b1a2bc55 | |||
| e6565c600a | |||
| 8af9648c43 | |||
| c5bdd9d79d | |||
| 9efa99e0d8 | |||
| 957eb021d7 | |||
| c8a4026512 | |||
| 2111bf9b9d | |||
| c72dd1ff66 | |||
| 43bd78913c | |||
| 5458e0dccc | |||
| 78a73e5f57 | |||
| c7269feeaa | |||
| 1e75041b00 | |||
| 08bc7ed8f0 | |||
| 2ce75dc415 | |||
| 8b3ddeed05 | |||
| 389238f622 | |||
| 6f2a13fafd | |||
| 6baa60d454 | |||
| 92410395e3 | |||
| d03af15516 | |||
| d781f299c0 | |||
| 6fc4898a1b | |||
| 0c15be004e | |||
| 1c6ef3fe38 | |||
| d254bff197 | |||
| 6fc5a78252 | |||
| d58e48d9f5 | |||
| 8973d1ad8a | |||
| 2aae0a4e0c | |||
| 156b3696a7 | |||
| c36174b263 | |||
| 6e5af9dccb | |||
| 4a81814dbb | |||
| 1451f30781 | |||
| 4b54ca46d3 | |||
| 169392e132 | |||
| c050642290 | |||
| 7636eecb48 | |||
| d890827bae | |||
| 07a1aea9c7 | |||
| da7b68a643 | |||
| e83793ba7f | |||
| 18966d048c | |||
| 7e6612a920 | |||
| 9333ed22f6 | |||
| 4b6fabbd2f | |||
| cba2b6eaf4 | |||
| 34747baed0 | |||
| 7c5b2db5bf | |||
| 2e07a72119 | |||
| 9ba5622efd | |||
| 3ff81a47c9 | |||
| dcec61ab7a | |||
| 4922e0e439 | |||
| 7adacd5ce5 | |||
| 08d89f7aac | |||
| d250a5155a | |||
| 52176edcbf | |||
| ae279eaced | |||
| 18e72d5c01 | |||
| 148f5c459e | |||
| abbea39855 | |||
| 6fd40a8f60 | |||
| 333249e7b0 | |||
| 2573ba4a50 | |||
| bbde68c49e | |||
| fdf1418dfb | |||
| 5bcf5c6f7c | |||
| 87d4afda81 | |||
| 3a9e0ea8a7 | |||
| 4ce1ac0dcb | |||
| 8cc6dd629a | |||
| 391d7f2c6a | |||
| c64a322e19 | |||
| 2f33e77ab4 | |||
| bbcc788f60 | |||
| 038dda97f8 | |||
| 1ac0549881 | |||
| 616f728afa | |||
| 161a717639 | |||
| 28e63e9279 | |||
| 11de70ed08 | |||
| 9283fd2162 | |||
| 544f594592 | |||
| c21a8aa8b0 | |||
| 30d6ec3398 | |||
| ab83972c3e | |||
| 9e3f3bee71 | |||
| 2ba9939bd8 | |||
| 3a278cc66d | |||
| 6183f06846 | |||
| a34f9fa66c | |||
| f5db8bd1e8 | |||
| 317c6fbec2 | |||
| c354b1014d | |||
| a28ec3a0a8 | |||
| 6ad647d7ce | |||
| 384246c360 | |||
| e30391661d | |||
| 75f5dac16b | |||
| bddd3feb83 | |||
| c873a597c7 | |||
| 4dc93bc485 | |||
| 68f2b0ca59 | |||
| 1edc0d8002 | |||
| a6dc020dc4 | |||
| 07422d709e | |||
| 768fc7e341 | |||
| 8ecedb81b3 | |||
| f19433f2d8 | |||
| 0b02a5e073 | |||
| e2b24f3c12 | |||
| c375fb1f70 | |||
| ad3e5425d4 | |||
| c418118683 | |||
| fb3f30832c | |||
| a275d942d2 | |||
| 20afa3265a | |||
| df50c8db3e | |||
| 679b668bbb | |||
| 676daa990b | |||
| 7c7fa69558 | |||
| 9ad92b16b0 | |||
| 3b899fe7e2 | |||
| c91041b675 | |||
| a17cf742b2 | |||
| a102cd5f32 | |||
| d03b6aa1d6 | |||
| b32b4f3a59 | |||
| 502997d8e9 | |||
| fb4ad2f3ba | |||
| b3de1b8e95 | |||
| fb17e283d1 | |||
| 0b7a24fc14 | |||
| 75b540f25c | |||
| 8bf79cc9c4 | |||
| 7ff80bc09d | |||
| 8d6b0eb191 | |||
| 35c7e3960e | |||
| 8e8c3f5a7c | |||
| f50b230fb3 | |||
| 02ba06953f | |||
| df22ba39f8 | |||
| 826776f321 | |||
| 29f92c8a2b | |||
| 64e04b7bc2 | |||
| ad05b02ff5 | |||
| 430a24e6bc | |||
| b7b9f2cb6b | |||
| b76664e757 | |||
| ae13823851 | |||
| 58b4286216 | |||
| df079feea9 | |||
| 372282457f | |||
| 79df1da792 | |||
| c5771bc7c8 | |||
| 2365581265 | |||
| c1a8b9a14d | |||
| e945e7426e | |||
| f07b9fd217 | |||
| 80a20488fd | |||
| 1dc9dffc70 | |||
| 0226580773 | |||
| 3ef1b7d83c | |||
| 08bce69d3d | |||
| 56b60120fe | |||
| 1364a179a9 | |||
| 2161e865d7 | |||
| 0b2c2d04b2 | |||
| 07fe8eea83 | |||
| 5aac35b99e | |||
| 040ea0a97c | |||
| 3c05679a97 | |||
| d8729a7c67 | |||
| c95426a616 | |||
| 1e403d1606 | |||
| f1567fb21a | |||
| 60865eae4b | |||
| 0c39fff831 | |||
| e902f9551a | |||
| 0a20c8a44a | |||
| 3cb746c358 | |||
| 6526e79eea | |||
| b77d4d01c5 | |||
| 599ac58baa | |||
| 449174c7d4 | |||
| 9266e6d29e | |||
| 062d701ae1 | |||
| 7137b22e6b | |||
| acb786d359 | |||
| fa7300c1de | |||
| db17a97ce8 | |||
| 55f946c5a0 | |||
| 3181dca5ef | |||
| f86a69ebfb | |||
| 6d63890c56 | |||
| 489c89b912 | |||
| 39bc7566df | |||
| d3a249e34d | |||
| 81f1edbaf2 | |||
| 23ecb50137 | |||
| 3d33d2baa9 | |||
| 3745224646 | |||
| 3bf78d6f8c | |||
| 563b2a8505 | |||
| 9ccbeec947 | |||
| 29248dff15 | |||
| 65d450ab6d | |||
| 271f2dc665 | |||
| da323d3335 | |||
| 32f061a3e0 | |||
| 9b5cbc1334 | |||
| 40e378a5a6 | |||
| 8eb0a0b94e | |||
| 09cf489c6b | |||
| 9a5290ea27 | |||
| b3176208e2 | |||
| 1dfe737d0e | |||
| dc2f513619 | |||
| 0eb33d09ac | |||
| f67f98a1a7 | |||
| 6c42f374c8 | |||
| 4879b56bb9 | |||
| 2fd83aa764 | |||
| 93218a06ba | |||
| 714c3c59eb | |||
| adcdefce43 | |||
| 049a024dea | |||
| 20bbebb299 | |||
| 26f006509f | |||
| 089503aab7 | |||
| 74155e3d88 | |||
| 564f33d5ef | |||
| 83acd5b050 | |||
| 67798a7b52 | |||
| 2b981651e1 | |||
| 294cdba80c | |||
| 072a86149d | |||
| 8bb4c167e4 | |||
| dff7aa4d4b | |||
| c634acd9ff | |||
| ed5c19f53e | |||
| 03420f811b | |||
| 4565bcd757 | |||
| f22f439a68 | |||
| 5672284f53 | |||
| 367f381fea |
+1
-2
@@ -21,8 +21,7 @@ derby.log
|
||||
/build
|
||||
buildSrc/build
|
||||
/spring-*/build
|
||||
/framework-bom/build
|
||||
/framework-docs/build
|
||||
/framework-*/build
|
||||
/integration-tests/build
|
||||
/src/asciidoc/build
|
||||
spring-test/test-output/
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
Juergen Hoeller <jhoeller@vmware.com>
|
||||
Juergen Hoeller <jhoeller@vmware.com> <jhoeller@pivotal.io>
|
||||
Juergen Hoeller <jhoeller@vmware.com> <jhoeller@gopivotal.com>
|
||||
Rossen Stoyanchev <rstoyanchev@vmware.com>
|
||||
Rossen Stoyanchev <rstoyanchev@vmware.com> <rstoyanchev@pivotal.io>
|
||||
Rossen Stoyanchev <rstoyanchev@vmware.com> <rstoyanchev@gopivotal.com>
|
||||
Phillip Webb <pwebb@vmware.com>
|
||||
Phillip Webb <pwebb@vmware.com> <pwebb@pivotal.io>
|
||||
Phillip Webb <pwebb@vmware.com> <pwebb@gopivotal.com>
|
||||
Chris Beams <cbeams@vmware.com>
|
||||
Chris Beams <cbeams@vmware.com> <cbeams@pivotal.io>
|
||||
Chris Beams <cbeams@vmware.com> <cbeams@gopivotal.com>
|
||||
Arjen Poutsma <poutsmaa@vmware.com>
|
||||
Arjen Poutsma <poutsmaa@vmware.com> <apoutsma@pivotal.io>
|
||||
Arjen Poutsma <poutsmaa@vmware.com> <apoutsma@gopivotal.com>
|
||||
Arjen Poutsma <poutsmaa@vmware.com> <poutsma@mac.com>
|
||||
Arjen Poutsma <poutsmaa@vmware.com> <apoutsma@vmware.com>
|
||||
Oliver Drotbohm <odrotbohm@vmware.com>
|
||||
Oliver Drotbohm <odrotbohm@vmware.com> <ogierke@vmware.com>
|
||||
Oliver Drotbohm <odrotbohm@vmware.com> <ogierke@pivotal.io>
|
||||
Oliver Drotbohm <odrotbohm@vmware.com> <ogierke@gopivotal.com>
|
||||
Dave Syer <dsyer@vmware.com>
|
||||
Dave Syer <dsyer@vmware.com> <dsyer@pivotal.io>
|
||||
Dave Syer <dsyer@vmware.com> <dsyer@gopivotal.com>
|
||||
Dave Syer <dsyer@vmware.com> <david_syer@hotmail.com>
|
||||
Andy Clement <aclement@vmware.com>
|
||||
Andy Clement <aclement@vmware.com> <aclement@pivotal.io>
|
||||
Andy Clement <aclement@vmware.com> <aclement@gopivotal.com>
|
||||
Andy Clement <aclement@vmware.com> <andrew.clement@gmail.com>
|
||||
Sam Brannen <sbrannen@vmware.com>
|
||||
Sam Brannen <sbrannen@vmware.com> <sbrannen@pivotal.io>
|
||||
Sam Brannen <sbrannen@vmware.com> <sam@sambrannen.com>
|
||||
Simon Basle <sbasle@vmware.com>
|
||||
Simon Baslé <sbasle@vmware.com>
|
||||
<dmitry.katsubo@gmail.com> <dmitry.katsubo@gmai.com>
|
||||
Nick Williams <nicholas@nicholaswilliams.net>
|
||||
@@ -1,3 +1,3 @@
|
||||
# Enable auto-env through the sdkman_auto_env config
|
||||
# Add key=value pairs of SDKs to use below
|
||||
java=17.0.7-librca
|
||||
java=17.0.8.1-librca
|
||||
|
||||
+5
-6
@@ -5,10 +5,10 @@ plugins {
|
||||
id 'org.jetbrains.kotlin.plugin.serialization' version "${kotlinVersion}" apply false
|
||||
id 'org.jetbrains.dokka' version '1.8.10'
|
||||
id 'org.unbroken-dome.xjc' version '2.0.0' apply false
|
||||
id 'com.github.ben-manes.versions' version '0.46.0'
|
||||
id 'com.github.ben-manes.versions' version '0.49.0'
|
||||
id 'com.github.johnrengelman.shadow' version '8.1.1' apply false
|
||||
id 'de.undercouch.download' version '5.4.0'
|
||||
id 'me.champeau.jmh' version '0.7.0' apply false
|
||||
id 'me.champeau.jmh' version '0.7.1' apply false
|
||||
}
|
||||
|
||||
ext {
|
||||
@@ -75,7 +75,7 @@ configure([rootProject] + javaProjects) { project ->
|
||||
}
|
||||
|
||||
checkstyle {
|
||||
toolVersion = "10.12.0"
|
||||
toolVersion = "10.12.7"
|
||||
configDirectory.set(rootProject.file("src/checkstyle"))
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ configure([rootProject] + javaProjects) { project ->
|
||||
// JSR-305 only used for non-required meta-annotations
|
||||
compileOnly("com.google.code.findbugs:jsr305")
|
||||
testCompileOnly("com.google.code.findbugs:jsr305")
|
||||
checkstyle("io.spring.javaformat:spring-javaformat-checkstyle:0.0.38")
|
||||
checkstyle("io.spring.javaformat:spring-javaformat-checkstyle:0.0.41")
|
||||
}
|
||||
|
||||
ext.javadocLinks = [
|
||||
@@ -116,7 +116,7 @@ configure([rootProject] + javaProjects) { project ->
|
||||
"https://www.ibm.com/docs/api/v1/content/SSEQTP_8.5.5/com.ibm.websphere.javadoc.doc/web/apidocs/", // com.ibm.*
|
||||
"https://docs.jboss.org/jbossas/javadoc/4.0.5/connector/", // org.jboss.resource.*
|
||||
"https://docs.jboss.org/hibernate/orm/5.6/javadocs/",
|
||||
"https://www.eclipse.org/aspectj/doc/released/aspectj5rt-api/",
|
||||
"https://eclipse.dev/aspectj/doc/released/aspectj5rt-api",
|
||||
"https://www.quartz-scheduler.org/api/2.3.0/",
|
||||
"https://www.javadoc.io/doc/com.fasterxml.jackson.core/jackson-core/2.14.1/",
|
||||
"https://www.javadoc.io/doc/com.fasterxml.jackson.core/jackson-databind/2.14.1/",
|
||||
@@ -146,7 +146,6 @@ configure(rootProject) {
|
||||
description = "Spring Framework"
|
||||
|
||||
apply plugin: "io.spring.nohttp"
|
||||
apply plugin: 'org.springframework.build.api-diff'
|
||||
|
||||
nohttp {
|
||||
source.exclude "**/test-output/**"
|
||||
|
||||
@@ -22,21 +22,6 @@ but doesn't affect the classpath of dependent projects.
|
||||
This plugin does not provide a `provided` configuration, as the native `compileOnly` and `testCompileOnly`
|
||||
configurations are preferred.
|
||||
|
||||
### API Diff
|
||||
|
||||
This plugin uses the [Gradle JApiCmp](https://github.com/melix/japicmp-gradle-plugin) plugin
|
||||
to generate API Diff reports for each Spring Framework module. This plugin is applied once on the root
|
||||
project and creates tasks in each framework module. Unlike previous versions of this part of the build,
|
||||
there is no need for checking out a specific tag. The plugin will fetch the JARs we want to compare the
|
||||
current working version with. You can generate the reports for all modules or a single module:
|
||||
|
||||
```
|
||||
./gradlew apiDiff -PbaselineVersion=5.1.0.RELEASE
|
||||
./gradlew :spring-core:apiDiff -PbaselineVersion=5.1.0.RELEASE
|
||||
```
|
||||
|
||||
The reports are located under `build/reports/api-diff/$OLDVERSION_to_$NEWVERSION/`.
|
||||
|
||||
|
||||
### RuntimeHints Java Agent
|
||||
|
||||
|
||||
@@ -19,16 +19,11 @@ ext {
|
||||
dependencies {
|
||||
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
|
||||
implementation("org.jetbrains.kotlin:kotlin-compiler-embeddable:${kotlinVersion}")
|
||||
implementation "me.champeau.gradle:japicmp-gradle-plugin:0.3.0"
|
||||
implementation "org.gradle:test-retry-gradle-plugin:1.4.1"
|
||||
implementation "org.gradle:test-retry-gradle-plugin:1.5.6"
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
apiDiffPlugin {
|
||||
id = "org.springframework.build.api-diff"
|
||||
implementationClass = "org.springframework.build.api.ApiDiffPlugin"
|
||||
}
|
||||
conventionsPlugin {
|
||||
id = "org.springframework.build.conventions"
|
||||
implementationClass = "org.springframework.build.ConventionsPlugin"
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.build.api;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import me.champeau.gradle.japicmp.JapicmpPlugin;
|
||||
import me.champeau.gradle.japicmp.JapicmpTask;
|
||||
import org.gradle.api.GradleException;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.artifacts.Dependency;
|
||||
import org.gradle.api.plugins.JavaBasePlugin;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
import org.gradle.jvm.tasks.Jar;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* {@link Plugin} that applies the {@code "japicmp-gradle-plugin"}
|
||||
* and create tasks for all subprojects named {@code "spring-*"}, diffing the public API one by one
|
||||
* and creating the reports in {@code "build/reports/api-diff/$OLDVERSION_to_$NEWVERSION/"}.
|
||||
* <p>{@code "./gradlew apiDiff -PbaselineVersion=5.1.0.RELEASE"} will output the
|
||||
* reports for the API diff between the baseline version and the current one for all modules.
|
||||
* You can limit the report to a single module with
|
||||
* {@code "./gradlew :spring-core:apiDiff -PbaselineVersion=5.1.0.RELEASE"}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class ApiDiffPlugin implements Plugin<Project> {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ApiDiffPlugin.class);
|
||||
|
||||
public static final String TASK_NAME = "apiDiff";
|
||||
|
||||
private static final String BASELINE_VERSION_PROPERTY = "baselineVersion";
|
||||
|
||||
private static final List<String> PACKAGE_INCLUDES = Collections.singletonList("org.springframework.*");
|
||||
|
||||
private static final URI SPRING_MILESTONE_REPOSITORY = URI.create("https://repo.spring.io/milestone");
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
if (project.hasProperty(BASELINE_VERSION_PROPERTY) && project.equals(project.getRootProject())) {
|
||||
project.getPluginManager().apply(JapicmpPlugin.class);
|
||||
project.getPlugins().withType(JapicmpPlugin.class,
|
||||
plugin -> applyApiDiffConventions(project));
|
||||
}
|
||||
}
|
||||
|
||||
private void applyApiDiffConventions(Project project) {
|
||||
String baselineVersion = project.property(BASELINE_VERSION_PROPERTY).toString();
|
||||
project.subprojects(subProject -> {
|
||||
if (subProject.getName().startsWith("spring-")) {
|
||||
createApiDiffTask(baselineVersion, subProject);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void createApiDiffTask(String baselineVersion, Project project) {
|
||||
if (isProjectEligible(project)) {
|
||||
// Add Spring Milestone repository for generating diffs against previous milestones
|
||||
project.getRootProject()
|
||||
.getRepositories()
|
||||
.maven(mavenArtifactRepository -> mavenArtifactRepository.setUrl(SPRING_MILESTONE_REPOSITORY));
|
||||
JapicmpTask apiDiff = project.getTasks().create(TASK_NAME, JapicmpTask.class);
|
||||
apiDiff.setDescription("Generates an API diff report with japicmp");
|
||||
apiDiff.setGroup(JavaBasePlugin.DOCUMENTATION_GROUP);
|
||||
|
||||
apiDiff.setOldClasspath(createBaselineConfiguration(baselineVersion, project));
|
||||
TaskProvider<Jar> jar = project.getTasks().withType(Jar.class).named("jar");
|
||||
apiDiff.setNewArchives(project.getLayout().files(jar.get().getArchiveFile().get().getAsFile()));
|
||||
apiDiff.setNewClasspath(getRuntimeClassPath(project));
|
||||
apiDiff.setPackageIncludes(PACKAGE_INCLUDES);
|
||||
apiDiff.setOnlyModified(true);
|
||||
apiDiff.setIgnoreMissingClasses(true);
|
||||
// Ignore Kotlin metadata annotations since they contain
|
||||
// illegal HTML characters and fail the report generation
|
||||
apiDiff.setAnnotationExcludes(Collections.singletonList("@kotlin.Metadata"));
|
||||
|
||||
apiDiff.setHtmlOutputFile(getOutputFile(baselineVersion, project));
|
||||
|
||||
apiDiff.dependsOn(project.getTasks().getByName("jar"));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isProjectEligible(Project project) {
|
||||
return project.getPlugins().hasPlugin(JavaPlugin.class)
|
||||
&& project.getPlugins().hasPlugin(MavenPublishPlugin.class);
|
||||
}
|
||||
|
||||
private Configuration createBaselineConfiguration(String baselineVersion, Project project) {
|
||||
String baseline = String.join(":",
|
||||
project.getGroup().toString(), project.getName(), baselineVersion);
|
||||
Dependency baselineDependency = project.getDependencies().create(baseline + "@jar");
|
||||
Configuration baselineConfiguration = project.getRootProject().getConfigurations().detachedConfiguration(baselineDependency);
|
||||
try {
|
||||
// eagerly resolve the baseline configuration to check whether this is a new Spring module
|
||||
baselineConfiguration.resolve();
|
||||
return baselineConfiguration;
|
||||
}
|
||||
catch (GradleException exception) {
|
||||
logger.warn("Could not resolve {} - assuming this is a new Spring module.", baseline);
|
||||
}
|
||||
return project.getRootProject().getConfigurations().detachedConfiguration();
|
||||
}
|
||||
|
||||
private Configuration getRuntimeClassPath(Project project) {
|
||||
return project.getConfigurations().getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);
|
||||
}
|
||||
|
||||
private File getOutputFile(String baseLineVersion, Project project) {
|
||||
Path outDir = Paths.get(project.getRootProject().getBuildDir().getAbsolutePath(),
|
||||
"reports", "api-diff",
|
||||
baseLineVersion + "_to_" + project.getRootProject().getVersion());
|
||||
return project.file(outDir.resolve(project.getName() + ".html").toString());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ubuntu:jammy-20230425
|
||||
FROM ubuntu:jammy-20231211.1
|
||||
|
||||
ADD setup.sh /setup.sh
|
||||
ADD get-jdk-url.sh /get-jdk-url.sh
|
||||
|
||||
@@ -3,7 +3,7 @@ set -e
|
||||
|
||||
case "$1" in
|
||||
java17)
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/17.0.7+7/bellsoft-jdk17.0.7+7-linux-amd64.tar.gz"
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/17.0.9+11/bellsoft-jdk17.0.9+11-linux-amd64.tar.gz"
|
||||
;;
|
||||
java20)
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/20.0.1+10/bellsoft-jdk20.0.1+10-linux-amd64.tar.gz"
|
||||
|
||||
@@ -4,7 +4,7 @@ image_resource:
|
||||
type: registry-image
|
||||
source:
|
||||
repository: springio/concourse-release-scripts
|
||||
tag: '0.3.4'
|
||||
tag: '0.4.0-SNAPSHOT'
|
||||
username: ((docker-hub-username))
|
||||
password: ((docker-hub-password))
|
||||
inputs:
|
||||
|
||||
@@ -168,7 +168,7 @@ Kotlin::
|
||||
======
|
||||
|
||||
NOTE: Pooling stateless service objects is not usually necessary. We do not believe it should
|
||||
be the default choice, as most stateless objects are naturally thread safe, and instance
|
||||
be the default choice, as most stateless objects are naturally thread-safe, and instance
|
||||
pooling is problematic if resources are cached.
|
||||
|
||||
Simpler pooling is available by using auto-proxying. You can set the `TargetSource` implementations
|
||||
|
||||
@@ -176,7 +176,7 @@ Kotlin::
|
||||
@AfterReturning(
|
||||
pointcut = "execution(* com.xyz.dao.*.*(..))",
|
||||
returning = "retVal")
|
||||
fun doAccessCheck(retVal: Any) {
|
||||
fun doAccessCheck(retVal: Any?) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
@@ -448,7 +448,7 @@ Kotlin::
|
||||
class AroundExample {
|
||||
|
||||
@Around("execution(* com.xyz..service.*.*(..))")
|
||||
fun doBasicProfiling(pjp: ProceedingJoinPoint): Any {
|
||||
fun doBasicProfiling(pjp: ProceedingJoinPoint): Any? {
|
||||
// start stopwatch
|
||||
val retVal = pjp.proceed()
|
||||
// stop stopwatch
|
||||
@@ -893,7 +893,7 @@ Kotlin::
|
||||
"com.xyz.CommonPointcuts.inDataAccessLayer() && " +
|
||||
"args(accountHolderNamePattern)") // <1>
|
||||
fun preProcessQueryPattern(pjp: ProceedingJoinPoint,
|
||||
accountHolderNamePattern: String): Any {
|
||||
accountHolderNamePattern: String): Any? {
|
||||
val newPattern = preProcess(accountHolderNamePattern)
|
||||
return pjp.proceed(arrayOf<Any>(newPattern))
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ Kotlin::
|
||||
}
|
||||
|
||||
@Around("com.xyz.CommonPointcuts.businessService()") // <1>
|
||||
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any {
|
||||
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any? {
|
||||
var numAttempts = 0
|
||||
var lockFailureException: PessimisticLockingFailureException
|
||||
do {
|
||||
@@ -173,7 +173,7 @@ Kotlin::
|
||||
----
|
||||
@Around("execution(* com.xyz..service.*.*(..)) && " +
|
||||
"@annotation(com.xyz.service.Idempotent)")
|
||||
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any {
|
||||
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any? {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
@@ -6,8 +6,8 @@ it until later.
|
||||
|
||||
By default, there is a single instance of each aspect within the application
|
||||
context. AspectJ calls this the singleton instantiation model. It is possible to define
|
||||
aspects with alternate lifecycles. Spring supports AspectJ's `perthis` and `pertarget`
|
||||
instantiation models; `percflow`, `percflowbelow`, and `pertypewithin` are not currently
|
||||
aspects with alternate lifecycles. Spring supports AspectJ's `perthis`, `pertarget`, and
|
||||
`pertypewithin` instantiation models; `percflow` and `percflowbelow` are not currently
|
||||
supported.
|
||||
|
||||
You can declare a `perthis` aspect by specifying a `perthis` clause in the `@Aspect`
|
||||
|
||||
@@ -154,7 +154,6 @@ Java::
|
||||
----
|
||||
package com.xyz;
|
||||
|
||||
@Aspect
|
||||
public class Pointcuts {
|
||||
|
||||
@Pointcut("execution(public * *(..))")
|
||||
@@ -179,7 +178,6 @@ Kotlin::
|
||||
----
|
||||
package com.xyz
|
||||
|
||||
@Aspect
|
||||
class Pointcuts {
|
||||
|
||||
@Pointcut("execution(public * *(..))")
|
||||
@@ -211,9 +209,9 @@ pointcut matching.
|
||||
|
||||
When working with enterprise applications, developers often have the need to refer to
|
||||
modules of the application and particular sets of operations from within several aspects.
|
||||
We recommend defining a dedicated aspect that captures commonly used _named pointcut_
|
||||
expressions for this purpose. Such an aspect typically resembles the following
|
||||
`CommonPointcuts` example (though what you name the aspect is up to you):
|
||||
We recommend defining a dedicated class that captures commonly used _named pointcut_
|
||||
expressions for this purpose. Such a class typically resembles the following
|
||||
`CommonPointcuts` example (though what you name the class is up to you):
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -223,10 +221,8 @@ Java::
|
||||
----
|
||||
package com.xyz;
|
||||
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
|
||||
@Aspect
|
||||
public class CommonPointcuts {
|
||||
|
||||
/**
|
||||
@@ -287,10 +283,8 @@ Kotlin::
|
||||
----
|
||||
package com.xyz
|
||||
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.Pointcut
|
||||
|
||||
@Aspect
|
||||
class CommonPointcuts {
|
||||
|
||||
/**
|
||||
@@ -346,9 +340,9 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
You can refer to the pointcuts defined in such an aspect anywhere you need a pointcut
|
||||
expression by referencing the fully-qualified name of the `@Aspect` class combined with
|
||||
the `@Pointcut` method's name. For example, to make the service layer transactional, you
|
||||
You can refer to the pointcuts defined in such a class anywhere you need a pointcut
|
||||
expression by referencing the fully-qualified name of the class combined with the
|
||||
`@Pointcut` method's name. For example, to make the service layer transactional, you
|
||||
could write the following which references the
|
||||
`com.xyz.CommonPointcuts.businessService()` _named pointcut_:
|
||||
|
||||
|
||||
@@ -435,7 +435,7 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
fun doBasicProfiling(pjp: ProceedingJoinPoint): Any {
|
||||
fun doBasicProfiling(pjp: ProceedingJoinPoint): Any? {
|
||||
// start stopwatch
|
||||
val retVal = pjp.proceed()
|
||||
// stop stopwatch
|
||||
@@ -554,7 +554,7 @@ Kotlin::
|
||||
|
||||
class SimpleProfiler {
|
||||
|
||||
fun profile(call: ProceedingJoinPoint, name: String, age: Int): Any {
|
||||
fun profile(call: ProceedingJoinPoint, name: String, age: Int): Any? {
|
||||
val clock = StopWatch("Profiling for '$name' and '$age'")
|
||||
try {
|
||||
clock.start(call.toShortString())
|
||||
@@ -890,7 +890,7 @@ Kotlin::
|
||||
this.order = order
|
||||
}
|
||||
|
||||
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any {
|
||||
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any? {
|
||||
var numAttempts = 0
|
||||
var lockFailureException: PessimisticLockingFailureException
|
||||
do {
|
||||
|
||||
@@ -493,7 +493,7 @@ Kotlin::
|
||||
class ProfilingAspect {
|
||||
|
||||
@Around("methodsToBeProfiled()")
|
||||
fun profile(pjp: ProceedingJoinPoint): Any {
|
||||
fun profile(pjp: ProceedingJoinPoint): Any? {
|
||||
val sw = StopWatch(javaClass.simpleName)
|
||||
try {
|
||||
sw.start(pjp.getSignature().getName())
|
||||
@@ -828,13 +828,6 @@ The following table summarizes various `LoadTimeWeaver` implementations:
|
||||
| Running in Red Hat's https://www.jboss.org/jbossas/[JBoss AS] or https://www.wildfly.org/[WildFly]
|
||||
| `JBossLoadTimeWeaver`
|
||||
|
||||
| Running in IBM's https://www-01.ibm.com/software/webservers/appserv/was/[WebSphere]
|
||||
| `WebSphereLoadTimeWeaver`
|
||||
|
||||
| Running in Oracle's
|
||||
https://www.oracle.com/technetwork/middleware/weblogic/overview/index-085209.html[WebLogic]
|
||||
| `WebLogicLoadTimeWeaver`
|
||||
|
||||
| JVM started with Spring `InstrumentationSavingAgent`
|
||||
(`java -javaagent:path/to/spring-instrument.jar`)
|
||||
| `InstrumentationLoadTimeWeaver`
|
||||
@@ -949,11 +942,11 @@ when you use Spring's LTW support in environments such as application servers an
|
||||
containers.
|
||||
|
||||
[[aop-aj-ltw-environments-tomcat-jboss-etc]]
|
||||
==== Tomcat, JBoss, WebSphere, WebLogic
|
||||
==== Tomcat, JBoss, WildFly
|
||||
|
||||
Tomcat, JBoss/WildFly, IBM WebSphere Application Server and Oracle WebLogic Server all
|
||||
provide a general app `ClassLoader` that is capable of local instrumentation. Spring's
|
||||
native LTW may leverage those ClassLoader implementations to provide AspectJ weaving.
|
||||
Tomcat and JBoss/WildFly provide a general app `ClassLoader` that is capable of local
|
||||
instrumentation. Spring's native LTW may leverage those ClassLoader implementations
|
||||
to provide AspectJ weaving.
|
||||
You can simply enable load-time weaving, as xref:core/aop/using-aspectj.adoc[described earlier].
|
||||
Specifically, you do not need to modify the JVM launch script to add
|
||||
`-javaagent:path/to/spring-instrument.jar`.
|
||||
|
||||
@@ -318,6 +318,51 @@ Java::
|
||||
----
|
||||
======
|
||||
|
||||
[[aot.bestpractices.jpa]]
|
||||
=== JPA
|
||||
|
||||
The JPA persistence unit has to be known upfront for certain optimizations to apply. Consider the following basic example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Bean
|
||||
LocalContainerEntityManagerFactoryBean customDBEntityManagerFactory(DataSource dataSource) {
|
||||
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
|
||||
factoryBean.setDataSource(dataSource);
|
||||
factoryBean.setPackagesToScan("com.example.app");
|
||||
return factoryBean;
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
To make sure the scanning occurs ahead of time, a `PersistenceManagedTypes` bean must be declared and used by the
|
||||
factory bean definition, as shown by the following example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Bean
|
||||
PersistenceManagedTypes persistenceManagedTypes(ResourceLoader resourceLoader) {
|
||||
return new PersistenceManagedTypesScanner(resourceLoader)
|
||||
.scan("com.example.app");
|
||||
}
|
||||
|
||||
@Bean
|
||||
LocalContainerEntityManagerFactoryBean customDBEntityManagerFactory(DataSource dataSource, PersistenceManagedTypes managedTypes) {
|
||||
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
|
||||
factoryBean.setDataSource(dataSource);
|
||||
factoryBean.setManagedTypes(managedTypes);
|
||||
return factoryBean;
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[[aot.hints]]
|
||||
== Runtime Hints
|
||||
|
||||
@@ -565,6 +565,17 @@ is a convenience mechanism that sets up a xref:core/beans/factory-extension.adoc
|
||||
for you. If you need more control over the specific
|
||||
`PropertySourcesPlaceholderConfigurer` setup, you can explicitly define it as a bean yourself.
|
||||
|
||||
[WARNING]
|
||||
=====
|
||||
Only one such element should be defined for a given application with the properties
|
||||
that it needs. Several property placeholders can be configured as long as they have distinct
|
||||
placeholder syntax (`${...}`).
|
||||
|
||||
If you need to modularize the source of properties used for the replacement, you should
|
||||
not create multiple properties placeholders. Rather, each module should contribute a
|
||||
`PropertySource` to the `Environment`. Alternatively, you can create your own
|
||||
`PropertySourcesPlaceholderConfigurer` bean that gathers the properties to use.
|
||||
=====
|
||||
|
||||
[[xsd-schemas-context-ac]]
|
||||
=== Using `<annotation-config/>`
|
||||
|
||||
@@ -364,8 +364,6 @@ ignored if it cannot be autowired. This allows properties to be assigned default
|
||||
that can be optionally overridden via dependency injection.
|
||||
====
|
||||
|
||||
|
||||
|
||||
[[beans-autowired-annotation-constructor-resolution]]
|
||||
Injected constructor and factory method arguments are a special case since the `required`
|
||||
attribute in `@Autowired` has a somewhat different meaning due to Spring's constructor
|
||||
|
||||
@@ -22,10 +22,11 @@ additional metadata formats.
|
||||
In most application scenarios, explicit user code is not required to instantiate one or
|
||||
more instances of a Spring IoC container. For example, in a web application scenario, a
|
||||
simple eight (or so) lines of boilerplate web descriptor XML in the `web.xml` file
|
||||
of the application typically suffices (see xref:core/beans/context-introduction.adoc#context-create[Convenient ApplicationContext Instantiation for Web Applications]). If you use the
|
||||
https://spring.io/tools[Spring Tools for Eclipse] (an Eclipse-powered development
|
||||
environment), you can easily create this boilerplate configuration with a few mouse clicks or
|
||||
keystrokes.
|
||||
of the application typically suffices (see
|
||||
xref:core/beans/context-introduction.adoc#context-create[Convenient ApplicationContext Instantiation for Web Applications]).
|
||||
If you use the https://spring.io/tools[Spring Tools for Eclipse] (an Eclipse-powered
|
||||
development environment), you can easily create this boilerplate configuration with a
|
||||
few mouse clicks or keystrokes.
|
||||
|
||||
The following diagram shows a high-level view of how Spring works. Your application classes
|
||||
are combined with configuration metadata so that, after the `ApplicationContext` is
|
||||
@@ -139,9 +140,11 @@ Kotlin::
|
||||
[NOTE]
|
||||
====
|
||||
After you learn about Spring's IoC container, you may want to know more about Spring's
|
||||
`Resource` abstraction (as described in xref:web/webflux-webclient/client-builder.adoc#webflux-client-builder-reactor-resources[Resources]), which provides a convenient
|
||||
mechanism for reading an InputStream from locations defined in a URI syntax. In particular,
|
||||
`Resource` paths are used to construct applications contexts, as described in xref:core/resources.adoc#resources-app-ctx[Application Contexts and Resource Paths].
|
||||
`Resource` abstraction (as described in
|
||||
xref:web/webflux-webclient/client-builder.adoc#webflux-client-builder-reactor-resources[Resources])
|
||||
which provides a convenient mechanism for reading an InputStream from locations defined
|
||||
in a URI syntax. In particular, `Resource` paths are used to construct applications contexts,
|
||||
as described in xref:core/resources.adoc#resources-app-ctx[Application Contexts and Resource Paths].
|
||||
====
|
||||
|
||||
The following example shows the service layer objects `(services.xml)` configuration file:
|
||||
@@ -208,9 +211,9 @@ XML configuration file represents a logical layer or module in your architecture
|
||||
|
||||
You can use the application context constructor to load bean definitions from all these
|
||||
XML fragments. This constructor takes multiple `Resource` locations, as was shown in the
|
||||
xref:core/beans/basics.adoc#beans-factory-instantiation[previous section]. Alternatively, use one or more
|
||||
occurrences of the `<import/>` element to load bean definitions from another file or
|
||||
files. The following example shows how to do so:
|
||||
xref:core/beans/basics.adoc#beans-factory-instantiation[previous section]. Alternatively,
|
||||
use one or more occurrences of the `<import/>` element to load bean definitions from
|
||||
another file or files. The following example shows how to do so:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
|
||||
@@ -484,13 +484,14 @@ custom event (`BlockedListEvent` in the preceding example). This means that the
|
||||
You can register as many event listeners as you wish, but note that, by default, event
|
||||
listeners receive events synchronously. This means that the `publishEvent()` method
|
||||
blocks until all listeners have finished processing the event. One advantage of this
|
||||
synchronous and single-threaded approach is that, when a listener receives an event, it
|
||||
operates inside the transaction context of the publisher if a transaction context is
|
||||
available. If another strategy for event publication becomes necessary, see the javadoc
|
||||
for Spring's
|
||||
synchronous and single-threaded approach is that, when a listener receives an event,
|
||||
it operates inside the transaction context of the publisher if a transaction context
|
||||
is available. If another strategy for event publication becomes necessary, e.g.
|
||||
asynchronous event processing by default, see the javadoc for Spring's
|
||||
{api-spring-framework}/context/event/ApplicationEventMulticaster.html[`ApplicationEventMulticaster`] interface
|
||||
and {api-spring-framework}/context/event/SimpleApplicationEventMulticaster.html[`SimpleApplicationEventMulticaster`]
|
||||
implementation for configuration options.
|
||||
implementation for configuration options which can be applied to a custom
|
||||
"applicationEventMulticaster" bean definition.
|
||||
|
||||
The following example shows the bean definitions used to register and configure each of
|
||||
the classes above:
|
||||
@@ -510,6 +511,12 @@ the classes above:
|
||||
<bean id="blockedListNotifier" class="example.BlockedListNotifier">
|
||||
<property name="notificationAddress" value="blockedlist@example.org"/>
|
||||
</bean>
|
||||
|
||||
<!-- optional: a custom ApplicationEventMulticaster definition -->
|
||||
<bean id="applicationEventMulticaster" class="org.springframework.context.event.SimpleApplicationEventMulticaster">
|
||||
<property name="taskExecutor" ref="..."/>
|
||||
<property name="errorHandler" ref="..."/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
Putting it all together, when the `sendEmail()` method of the `emailService` bean is
|
||||
@@ -849,6 +856,23 @@ Kotlin::
|
||||
TIP: This works not only for `ApplicationEvent` but any arbitrary object that you send as
|
||||
an event.
|
||||
|
||||
Finally, as with classic `ApplicationListener` implementations, the actual multicasting
|
||||
happens via a context-wide `ApplicationEventMulticaster` at runtime. By default, this is a
|
||||
`SimpleApplicationEventMulticaster` with synchronous event publication in the caller thread.
|
||||
This can be replaced/customized through an "applicationEventMulticaster" bean definition,
|
||||
e.g. for processing all events asynchronously and/or for handling listener exceptions:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Bean
|
||||
ApplicationEventMulticaster applicationEventMulticaster() {
|
||||
SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
|
||||
multicaster.setTaskExecutor(...);
|
||||
multicaster.setErrorHandler(...);
|
||||
return multicaster;
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[context-functionality-resources]]
|
||||
@@ -910,7 +934,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// create a startup step and start recording
|
||||
StartupStep scanPackages = this.getApplicationStartup().start("spring.context.base-packages.scan");
|
||||
StartupStep scanPackages = getApplicationStartup().start("spring.context.base-packages.scan");
|
||||
// add tagging information to the current step
|
||||
scanPackages.tag("packages", () -> Arrays.toString(basePackages));
|
||||
// perform the actual phase we're instrumenting
|
||||
@@ -924,7 +948,7 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// create a startup step and start recording
|
||||
val scanPackages = this.getApplicationStartup().start("spring.context.base-packages.scan")
|
||||
val scanPackages = getApplicationStartup().start("spring.context.base-packages.scan")
|
||||
// add tagging information to the current step
|
||||
scanPackages.tag("packages", () -> Arrays.toString(basePackages))
|
||||
// perform the actual phase we're instrumenting
|
||||
|
||||
@@ -309,7 +309,7 @@ how to configure such a bean:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<!-- the factory bean, which contains a method called createInstance() -->
|
||||
<!-- the factory bean, which contains a method called createClientServiceInstance() -->
|
||||
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
|
||||
<!-- inject any dependencies required by this locator bean -->
|
||||
</bean>
|
||||
|
||||
@@ -562,8 +562,9 @@ If no profile is active, the `dataSource` is created. You can see this
|
||||
as a way to provide a default definition for one or more beans. If any
|
||||
profile is enabled, the default profile does not apply.
|
||||
|
||||
You can change the name of the default profile by using `setDefaultProfiles()` on
|
||||
the `Environment` or, declaratively, by using the `spring.profiles.default` property.
|
||||
The name of the default profile is `default`. You can change the name of
|
||||
the default profile by using `setDefaultProfiles()` on the `Environment` or,
|
||||
declaratively, by using the `spring.profiles.default` property.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -23,9 +23,8 @@ You can set this property only if the `BeanPostProcessor` implements the `Ordere
|
||||
interface. If you write your own `BeanPostProcessor`, you should consider implementing
|
||||
the `Ordered` interface, too. For further details, see the javadoc of the
|
||||
{api-spring-framework}/beans/factory/config/BeanPostProcessor.html[`BeanPostProcessor`]
|
||||
and {api-spring-framework}/core/Ordered.html[`Ordered`] interfaces. See also the note
|
||||
on xref:core/beans/factory-extension.adoc#beans-factory-programmatically-registering-beanpostprocessors[programmatic registration of `BeanPostProcessor` instances]
|
||||
.
|
||||
and {api-spring-framework}/core/Ordered.html[`Ordered`] interfaces. See also the note on
|
||||
xref:core/beans/factory-extension.adoc#beans-factory-programmatically-registering-beanpostprocessors[programmatic registration of `BeanPostProcessor` instances].
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
@@ -280,11 +279,12 @@ and {api-spring-framework}/core/Ordered.html[`Ordered`] interfaces for more deta
|
||||
====
|
||||
If you want to change the actual bean instances (that is, the objects that are created
|
||||
from the configuration metadata), then you instead need to use a `BeanPostProcessor`
|
||||
(described earlier in xref:core/beans/factory-extension.adoc#beans-factory-extension-bpp[Customizing Beans by Using a `BeanPostProcessor`]). While it is technically possible
|
||||
to work with bean instances within a `BeanFactoryPostProcessor` (for example, by using
|
||||
`BeanFactory.getBean()`), doing so causes premature bean instantiation, violating the
|
||||
standard container lifecycle. This may cause negative side effects, such as bypassing
|
||||
bean post processing.
|
||||
(described earlier in
|
||||
xref:core/beans/factory-extension.adoc#beans-factory-extension-bpp[Customizing Beans by Using a `BeanPostProcessor`]).
|
||||
While it is technically possible to work with bean instances within a `BeanFactoryPostProcessor`
|
||||
(for example, by using `BeanFactory.getBean()`), doing so causes premature bean instantiation,
|
||||
violating the standard container lifecycle. This may cause negative side effects, such as
|
||||
bypassing bean post processing.
|
||||
|
||||
Also, `BeanFactoryPostProcessor` instances are scoped per-container. This is only relevant
|
||||
if you use container hierarchies. If you define a `BeanFactoryPostProcessor` in one
|
||||
@@ -331,8 +331,7 @@ with placeholder values is defined:
|
||||
<property name="locations" value="classpath:com/something/jdbc.properties"/>
|
||||
</bean>
|
||||
|
||||
<bean id="dataSource" destroy-method="close"
|
||||
class="org.apache.commons.dbcp.BasicDataSource">
|
||||
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
|
||||
<property name="driverClassName" value="${jdbc.driverClassName}"/>
|
||||
<property name="url" value="${jdbc.url}"/>
|
||||
<property name="username" value="${jdbc.username}"/>
|
||||
@@ -373,6 +372,17 @@ The `PropertySourcesPlaceholderConfigurer` not only looks for properties in the
|
||||
file you specify. By default, if it cannot find a property in the specified properties files,
|
||||
it checks against Spring `Environment` properties and regular Java `System` properties.
|
||||
|
||||
[WARNING]
|
||||
=====
|
||||
Only one such element should be defined for a given application with the properties
|
||||
that it needs. Several property placeholders can be configured as long as they have distinct
|
||||
placeholder syntax (`${...}`).
|
||||
|
||||
If you need to modularize the source of properties used for the replacement, you should
|
||||
not create multiple properties placeholders. Rather, you should create your own
|
||||
`PropertySourcesPlaceholderConfigurer` bean that gathers the properties to use.
|
||||
=====
|
||||
|
||||
[TIP]
|
||||
=====
|
||||
You can use the `PropertySourcesPlaceholderConfigurer` to substitute class names, which
|
||||
@@ -433,8 +443,8 @@ This example file can be used with a container definition that contains a bean c
|
||||
|
||||
Compound property names are also supported, as long as every component of the path
|
||||
except the final property being overridden is already non-null (presumably initialized
|
||||
by the constructors). In the following example, the `sammy` property of the `bob` property of the `fred` property of the `tom` bean
|
||||
is set to the scalar value `123`:
|
||||
by the constructors). In the following example, the `sammy` property of the `bob`
|
||||
property of the `fred` property of the `tom` bean is set to the scalar value `123`:
|
||||
|
||||
[literal,subs="verbatim,quotes"]
|
||||
----
|
||||
|
||||
@@ -42,6 +42,7 @@ startup and shutdown process, as driven by the container's own lifecycle.
|
||||
The lifecycle callback interfaces are described in this section.
|
||||
|
||||
|
||||
|
||||
[[beans-factory-lifecycle-initializingbean]]
|
||||
=== Initialization Callbacks
|
||||
|
||||
@@ -132,6 +133,30 @@ Kotlin::
|
||||
|
||||
However, the first of the two preceding examples does not couple the code to Spring.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Be aware that `@PostConstruct` and initialization methods in general are executed
|
||||
within the container's singleton creation lock. The bean instance is only considered
|
||||
as fully initialized and ready to be published to others after returning from the
|
||||
`@PostConstruct` method. Such individual initialization methods are only meant
|
||||
for validating the configuration state and possibly preparing some data structures
|
||||
based on the given configuration but no further activity with external bean access.
|
||||
Otherwise there is a risk for an initialization deadlock.
|
||||
|
||||
For a scenario where expensive post-initialization activity is to be triggered,
|
||||
e.g. asynchronous database preparation steps, your bean should either implement
|
||||
`SmartInitializingSingleton.afterSingletonsInstantiated()` or rely on the context
|
||||
refresh event: implementing `ApplicationListener<ContextRefreshedEvent>` or
|
||||
declaring its annotation equivalent `@EventListener(ContextRefreshedEvent.class)`.
|
||||
Those variants come after all regular singleton initialization and therefore
|
||||
outside of any singleton creation lock.
|
||||
|
||||
Alternatively, you may implement the `(Smart)Lifecycle` interface and integrate with
|
||||
the container's overall lifecycle management, including an auto-startup mechanism,
|
||||
a pre-destroy stop step, and potential stop/restart callbacks (see below).
|
||||
====
|
||||
|
||||
|
||||
|
||||
[[beans-factory-lifecycle-disposablebean]]
|
||||
=== Destruction Callbacks
|
||||
@@ -155,7 +180,7 @@ xref:core/beans/java/bean-annotation.adoc#beans-java-lifecycle-callbacks[Receivi
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
|
||||
<bean id="exampleDestructionBean" class="examples.ExampleBean" destroy-method="cleanup"/>
|
||||
----
|
||||
|
||||
[tabs]
|
||||
@@ -189,7 +214,7 @@ The preceding definition has almost exactly the same effect as the following def
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
|
||||
<bean id="exampleDestructionBean" class="examples.AnotherExampleBean"/>
|
||||
----
|
||||
|
||||
[tabs]
|
||||
@@ -222,32 +247,45 @@ Kotlin::
|
||||
|
||||
However, the first of the two preceding definitions does not couple the code to Spring.
|
||||
|
||||
TIP: You can assign the `destroy-method` attribute of a `<bean>` element a special
|
||||
`(inferred)` value, which instructs Spring to automatically detect a public `close` or
|
||||
`shutdown` method on the specific bean class. (Any class that implements
|
||||
`java.lang.AutoCloseable` or `java.io.Closeable` would therefore match.) You can also set
|
||||
this special `(inferred)` value on the `default-destroy-method` attribute of a
|
||||
`<beans>` element to apply this behavior to an entire set of beans (see
|
||||
xref:core/beans/factory-nature.adoc#beans-factory-lifecycle-default-init-destroy-methods[Default Initialization and Destroy Methods]). Note that this is the
|
||||
default behavior with Java configuration.
|
||||
Note that Spring also supports inference of destroy methods, detecting a public `close` or
|
||||
`shutdown` method. This is the default behavior for `@Bean` methods in Java configuration
|
||||
classes and automatically matches `java.lang.AutoCloseable` or `java.io.Closeable`
|
||||
implementations, not coupling the destruction logic to Spring either.
|
||||
|
||||
TIP: For destroy method inference with XML, you may assign the `destroy-method` attribute
|
||||
of a `<bean>` element a special `(inferred)` value, which instructs Spring to automatically
|
||||
detect a public `close` or `shutdown` method on the bean class for a specific bean definition.
|
||||
You can also set this special `(inferred)` value on the `default-destroy-method` attribute
|
||||
of a `<beans>` element to apply this behavior to an entire set of bean definitions (see
|
||||
xref:core/beans/factory-nature.adoc#beans-factory-lifecycle-default-init-destroy-methods[Default Initialization and Destroy Methods]).
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
For extended shutdown phases, you may implement the `Lifecycle` interface and receive
|
||||
an early stop signal before the destroy methods of any singleton beans are called.
|
||||
You may also implement `SmartLifecycle` for a time-bound stop step where the container
|
||||
will wait for all such stop processing to complete before moving on to destroy methods.
|
||||
====
|
||||
|
||||
|
||||
|
||||
[[beans-factory-lifecycle-default-init-destroy-methods]]
|
||||
=== Default Initialization and Destroy Methods
|
||||
|
||||
When you write initialization and destroy method callbacks that do not use the
|
||||
Spring-specific `InitializingBean` and `DisposableBean` callback interfaces, you
|
||||
typically write methods with names such as `init()`, `initialize()`, `dispose()`, and so
|
||||
on. Ideally, the names of such lifecycle callback methods are standardized across a
|
||||
project so that all developers use the same method names and ensure consistency.
|
||||
typically write methods with names such as `init()`, `initialize()`, `dispose()`,
|
||||
and so on. Ideally, the names of such lifecycle callback methods are standardized across
|
||||
a project so that all developers use the same method names and ensure consistency.
|
||||
|
||||
You can configure the Spring container to "`look`" for named initialization and destroy
|
||||
callback method names on every bean. This means that you, as an application
|
||||
developer, can write your application classes and use an initialization callback called
|
||||
`init()`, without having to configure an `init-method="init"` attribute with each bean
|
||||
definition. The Spring IoC container calls that method when the bean is created (and in
|
||||
accordance with the standard lifecycle callback contract xref:core/beans/factory-nature.adoc#beans-factory-lifecycle[described previously]
|
||||
). This feature also enforces a consistent naming convention for
|
||||
initialization and destroy method callbacks.
|
||||
callback method names on every bean. This means that you, as an application developer,
|
||||
can write your application classes and use an initialization callback called `init()`,
|
||||
without having to configure an `init-method="init"` attribute with each bean definition.
|
||||
The Spring IoC container calls that method when the bean is created (and in accordance
|
||||
with the standard lifecycle callback contract xref:core/beans/factory-nature.adoc#beans-factory-lifecycle[described previously]).
|
||||
This feature also enforces a consistent naming convention for initialization and
|
||||
destroy method callbacks.
|
||||
|
||||
Suppose that your initialization callback methods are named `init()` and your destroy
|
||||
callback methods are named `destroy()`. Your class then resembles the class in the
|
||||
@@ -407,14 +445,15 @@ and closed.
|
||||
[TIP]
|
||||
====
|
||||
Note that the regular `org.springframework.context.Lifecycle` interface is a plain
|
||||
contract for explicit start and stop notifications and does not imply auto-startup at context
|
||||
refresh time. For fine-grained control over auto-startup of a specific bean (including startup phases),
|
||||
consider implementing `org.springframework.context.SmartLifecycle` instead.
|
||||
contract for explicit start and stop notifications and does not imply auto-startup
|
||||
at context refresh time. For fine-grained control over auto-startup and for graceful
|
||||
stopping of a specific bean (including startup and stop phases), consider implementing
|
||||
the extended `org.springframework.context.SmartLifecycle` interface instead.
|
||||
|
||||
Also, please note that stop notifications are not guaranteed to come before destruction.
|
||||
On regular shutdown, all `Lifecycle` beans first receive a stop notification before
|
||||
the general destruction callbacks are being propagated. However, on hot refresh during a
|
||||
context's lifetime or on stopped refresh attempts, only destroy methods are called.
|
||||
the general destruction callbacks are being propagated. However, on hot refresh during
|
||||
a context's lifetime or on stopped refresh attempts, only destroy methods are called.
|
||||
====
|
||||
|
||||
The order of startup and shutdown invocations can be important. If a "`depends-on`"
|
||||
|
||||
@@ -125,8 +125,8 @@ objects regardless of scope, in the case of prototypes, configured destruction
|
||||
lifecycle callbacks are not called. The client code must clean up prototype-scoped
|
||||
objects and release expensive resources that the prototype beans hold. To get
|
||||
the Spring container to release resources held by prototype-scoped beans, try using a
|
||||
custom xref:core/beans/factory-extension.adoc#beans-factory-extension-bpp[bean post-processor], which holds a reference to
|
||||
beans that need to be cleaned up.
|
||||
custom xref:core/beans/factory-extension.adoc#beans-factory-extension-bpp[bean post-processor]
|
||||
which holds a reference to beans that need to be cleaned up.
|
||||
|
||||
In some respects, the Spring container's role in regard to a prototype-scoped bean is a
|
||||
replacement for the Java `new` operator. All lifecycle management past that point must
|
||||
@@ -324,7 +324,6 @@ Kotlin::
|
||||
|
||||
|
||||
|
||||
|
||||
[[beans-factory-scopes-application]]
|
||||
=== Application Scope
|
||||
|
||||
@@ -374,7 +373,6 @@ Kotlin::
|
||||
|
||||
|
||||
|
||||
|
||||
[[beans-factory-scopes-websocket]]
|
||||
=== WebSocket Scope
|
||||
|
||||
@@ -384,7 +382,6 @@ xref:web/websocket/stomp/scope.adoc[WebSocket scope] for more details.
|
||||
|
||||
|
||||
|
||||
|
||||
[[beans-factory-scopes-other-injection]]
|
||||
=== Scoped Beans as Dependencies
|
||||
|
||||
@@ -449,12 +446,13 @@ understand the "`why`" as well as the "`how`" behind it:
|
||||
----
|
||||
<1> The line that defines the proxy.
|
||||
|
||||
To create such a proxy, you insert a child `<aop:scoped-proxy/>` element into a
|
||||
scoped bean definition (see
|
||||
xref:core/beans/factory-scopes.adoc#beans-factory-scopes-other-injection-proxies[Choosing the Type of Proxy to Create]
|
||||
and xref:core/appendix/xsd-schemas.adoc[XML Schema-based configuration]).
|
||||
|
||||
To create such a proxy, you insert a child `<aop:scoped-proxy/>` element into a scoped
|
||||
bean definition (see xref:core/beans/factory-scopes.adoc#beans-factory-scopes-other-injection-proxies[Choosing the Type of Proxy to Create] and
|
||||
xref:core/appendix/xsd-schemas.adoc[XML Schema-based configuration]).
|
||||
Why do definitions of beans scoped at the `request`, `session` and custom-scope
|
||||
levels require the `<aop:scoped-proxy/>` element?
|
||||
levels require the `<aop:scoped-proxy/>` element in common scenarios?
|
||||
Consider the following singleton bean definition and contrast it with
|
||||
what you need to define for the aforementioned scopes (note that the following
|
||||
`userPreferences` bean definition as it stands is incomplete):
|
||||
@@ -513,8 +511,8 @@ the `<aop:scoped-proxy/>` element, a CGLIB-based class proxy is created.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
CGLIB proxies intercept only public method calls! Do not call non-public methods
|
||||
on such a proxy. They are not delegated to the actual scoped target object.
|
||||
CGLIB proxies do not intercept private methods. Attempting to call a private method
|
||||
on such a proxy will not delegate to the actual scoped target object.
|
||||
====
|
||||
|
||||
Alternatively, you can configure the Spring container to create standard JDK
|
||||
|
||||
@@ -180,9 +180,10 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
The resolution mechanism is pretty much identical to constructor-based dependency
|
||||
injection. See xref:core/beans/dependencies/factory-collaborators.adoc#beans-constructor-injection[the relevant section] for more details.
|
||||
injection. See
|
||||
xref:core/beans/dependencies/factory-collaborators.adoc#beans-constructor-injection[the relevant section]
|
||||
for more details.
|
||||
|
||||
|
||||
[[beans-java-lifecycle-callbacks]]
|
||||
@@ -317,8 +318,9 @@ type, making it harder to use for cross-reference calls in other `@Bean` methods
|
||||
intend to refer to the provided resource here).
|
||||
=====
|
||||
|
||||
In the case of `BeanOne` from the example above the preceding note, it would be equally valid to call the `init()`
|
||||
method directly during construction, as the following example shows:
|
||||
In the case of `BeanOne` from the example above the preceding note, it would be
|
||||
equally valid to call the `init()` method directly during construction, as the
|
||||
following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -506,10 +508,10 @@ Kotlin::
|
||||
[[beans-java-bean-aliasing]]
|
||||
== Bean Aliasing
|
||||
|
||||
As discussed in xref:core/beans/definition.adoc#beans-beanname[Naming Beans], it is sometimes desirable to give a single bean
|
||||
multiple names, otherwise known as bean aliasing. The `name` attribute of the `@Bean`
|
||||
annotation accepts a String array for this purpose. The following example shows how to set
|
||||
a number of aliases for a bean:
|
||||
As discussed in xref:core/beans/definition.adoc#beans-beanname[Naming Beans], it is
|
||||
sometimes desirable to give a single bean multiple names, otherwise known as bean aliasing.
|
||||
The `name` attribute of the `@Bean` annotation accepts a String array for this purpose.
|
||||
The following example shows how to set a number of aliases for a bean:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
||||
+7
-2
@@ -113,7 +113,8 @@ issue, because no compiler is involved, and you can declare
|
||||
When using `@Configuration` classes, the Java compiler places constraints on
|
||||
the configuration model, in that references to other beans must be valid Java syntax.
|
||||
|
||||
Fortunately, solving this problem is simple. As xref:core/beans/java/bean-annotation.adoc#beans-java-dependencies[we already discussed],
|
||||
Fortunately, solving this problem is simple. As
|
||||
xref:core/beans/java/bean-annotation.adoc#beans-java-dependencies[we already discussed],
|
||||
a `@Bean` method can have an arbitrary number of parameters that describe the bean
|
||||
dependencies. Consider the following more real-world scenario with several `@Configuration`
|
||||
classes, each depending on beans declared in the others:
|
||||
@@ -204,7 +205,6 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
There is another way to achieve the same result. Remember that `@Configuration` classes are
|
||||
ultimately only another bean in the container: This means that they can take advantage of
|
||||
`@Autowired` and `@Value` injection and other features the same as any other bean.
|
||||
@@ -216,6 +216,11 @@ classes are processed quite early during the initialization of the context, and
|
||||
to be injected this way may lead to unexpected early initialization. Whenever possible, resort to
|
||||
parameter-based injection, as in the preceding example.
|
||||
|
||||
Avoid access to locally defined beans within a `@PostConstruct` method on the same configuration
|
||||
class. This effectively leads to a circular reference since non-static `@Bean` methods semantically
|
||||
require a fully initialized configuration class instance to be called on. With circular references
|
||||
disallowed (e.g. in Spring Boot 2.6+), this may trigger a `BeanCurrentlyInCreationException`.
|
||||
|
||||
Also, be particularly careful with `BeanPostProcessor` and `BeanFactoryPostProcessor` definitions
|
||||
through `@Bean`. Those should usually be declared as `static @Bean` methods, not triggering the
|
||||
instantiation of their containing configuration class. Otherwise, `@Autowired` and `@Value` may not
|
||||
|
||||
@@ -38,5 +38,15 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
If your application or framework manages its own `EvaluationContext`, you may need to
|
||||
manually configure a `StandardTypeLocator` with a specific `ClassLoader` to ensure that
|
||||
the SpEL expression parser is able to reliably locate user types.
|
||||
|
||||
For example, the `StandardBeanExpressionResolver` in the `spring-context` module
|
||||
configures a `StandardTypeLocator` using the bean `ClassLoader` of the corresponding
|
||||
`BeanFactory`.
|
||||
====
|
||||
|
||||
|
||||
|
||||
@@ -268,6 +268,8 @@ Java::
|
||||
@Service
|
||||
public class MyService {
|
||||
|
||||
private final ConversionService conversionService;
|
||||
|
||||
public MyService(ConversionService conversionService) {
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
@@ -151,10 +151,10 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
The last example we show here is for typical JDBC support. You could have the
|
||||
`DataSource` injected into an initialization method or a constructor, where you would create a
|
||||
`JdbcTemplate` and other data access support classes (such as `SimpleJdbcCall` and others) by using
|
||||
this `DataSource`. The following example autowires a `DataSource`:
|
||||
The last example we show here is for typical JDBC support. You could have the `DataSource`
|
||||
injected into an initialization method or a constructor, where you would create a `JdbcTemplate`
|
||||
and other data access support classes (such as `SimpleJdbcCall` and others) by using this
|
||||
`DataSource`. The following example autowires a `DataSource`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
||||
@@ -9,13 +9,13 @@ to the database.
|
||||
[[jdbc-batch-classic]]
|
||||
== Basic Batch Operations with `JdbcTemplate`
|
||||
|
||||
You accomplish `JdbcTemplate` batch processing by implementing two methods of a special
|
||||
interface, `BatchPreparedStatementSetter`, and passing that implementation in as the second parameter
|
||||
You accomplish `JdbcTemplate` batch processing by implementing two methods of a special interface,
|
||||
`BatchPreparedStatementSetter`, and passing that implementation in as the second parameter
|
||||
in your `batchUpdate` method call. You can use the `getBatchSize` method to provide the size of
|
||||
the current batch. You can use the `setValues` method to set the values for the parameters of
|
||||
the prepared statement. This method is called the number of times that you
|
||||
specified in the `getBatchSize` call. The following example updates the `t_actor` table
|
||||
based on entries in a list, and the entire list is used as the batch:
|
||||
the prepared statement. This method is called the number of times that you specified in the
|
||||
`getBatchSize` call. The following example updates the `t_actor` table based on entries in a list,
|
||||
and the entire list is used as the batch:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
||||
@@ -10,7 +10,7 @@ This section covers:
|
||||
* xref:data-access/jdbc/connections.adoc#jdbc-SingleConnectionDataSource[Using `SingleConnectionDataSource`]
|
||||
* xref:data-access/jdbc/connections.adoc#jdbc-DriverManagerDataSource[Using `DriverManagerDataSource`]
|
||||
* xref:data-access/jdbc/connections.adoc#jdbc-TransactionAwareDataSourceProxy[Using `TransactionAwareDataSourceProxy`]
|
||||
* xref:data-access/jdbc/connections.adoc#jdbc-DataSourceTransactionManager[Using `DataSourceTransactionManager`]
|
||||
* xref:data-access/jdbc/connections.adoc#jdbc-DataSourceTransactionManager[Using `DataSourceTransactionManager` / `JdbcTransactionManager`]
|
||||
|
||||
|
||||
[[jdbc-datasource]]
|
||||
@@ -125,8 +125,12 @@ The following example shows C3P0 configuration:
|
||||
== Using `DataSourceUtils`
|
||||
|
||||
The `DataSourceUtils` class is a convenient and powerful helper class that provides
|
||||
`static` methods to obtain connections from JNDI and close connections if necessary. It
|
||||
supports thread-bound connections with, for example, `DataSourceTransactionManager`.
|
||||
`static` methods to obtain connections from JNDI and close connections if necessary.
|
||||
It supports a thread-bound JDBC `Connection` with `DataSourceTransactionManager` but
|
||||
also with `JtaTransactionManager` and `JpaTransactionManager`.
|
||||
|
||||
Note that `JdbcTemplate` implies `DataSourceUtils` connection access, using it
|
||||
behind every JDBC operation, implicitly participating in an ongoing transaction.
|
||||
|
||||
|
||||
[[jdbc-SmartDataSource]]
|
||||
@@ -165,7 +169,6 @@ In contrast to `DriverManagerDataSource`, it reuses the same connection all the
|
||||
avoiding excessive creation of physical connections.
|
||||
|
||||
|
||||
|
||||
[[jdbc-DriverManagerDataSource]]
|
||||
== Using `DriverManagerDataSource`
|
||||
|
||||
@@ -201,29 +204,44 @@ javadoc for more details.
|
||||
|
||||
|
||||
[[jdbc-DataSourceTransactionManager]]
|
||||
== Using `DataSourceTransactionManager`
|
||||
== Using `DataSourceTransactionManager` / `JdbcTransactionManager`
|
||||
|
||||
The `DataSourceTransactionManager` class is a `PlatformTransactionManager`
|
||||
implementation for single JDBC data sources. It binds a JDBC connection from the
|
||||
specified data source to the currently executing thread, potentially allowing for one
|
||||
thread connection per data source.
|
||||
implementation for a single JDBC `DataSource`. It binds a JDBC `Connection`
|
||||
from the specified `DataSource` to the currently executing thread, potentially
|
||||
allowing for one thread-bound `Connection` per `DataSource`.
|
||||
|
||||
Application code is required to retrieve the JDBC connection through
|
||||
`DataSourceUtils.getConnection(DataSource)` instead of Jakarta EE's standard
|
||||
Application code is required to retrieve the JDBC `Connection` through
|
||||
`DataSourceUtils.getConnection(DataSource)` instead of Java EE's standard
|
||||
`DataSource.getConnection`. It throws unchecked `org.springframework.dao` exceptions
|
||||
instead of checked `SQLExceptions`. All framework classes (such as `JdbcTemplate`) use this
|
||||
strategy implicitly. If not used with this transaction manager, the lookup strategy
|
||||
behaves exactly like the common one. Thus, it can be used in any case.
|
||||
instead of checked `SQLExceptions`. All framework classes (such as `JdbcTemplate`) use
|
||||
this strategy implicitly. If not used with a transaction manager, the lookup strategy
|
||||
behaves exactly like `DataSource.getConnection` and can therefore be used in any case.
|
||||
|
||||
The `DataSourceTransactionManager` class supports custom isolation levels and timeouts
|
||||
that get applied as appropriate JDBC statement query timeouts. To support the latter,
|
||||
application code must either use `JdbcTemplate` or call the
|
||||
`DataSourceUtils.applyTransactionTimeout(..)` method for each created statement.
|
||||
The `DataSourceTransactionManager` class supports savepoints (`PROPAGATION_NESTED`),
|
||||
custom isolation levels, and timeouts that get applied as appropriate JDBC statement
|
||||
query timeouts. To support the latter, application code must either use `JdbcTemplate` or
|
||||
call the `DataSourceUtils.applyTransactionTimeout(..)` method for each created statement.
|
||||
|
||||
You can use this implementation instead of `JtaTransactionManager` in the single-resource
|
||||
case, as it does not require the container to support JTA. Switching between
|
||||
both is just a matter of configuration, provided you stick to the required connection lookup
|
||||
pattern. JTA does not support custom isolation levels.
|
||||
You can use `DataSourceTransactionManager` instead of `JtaTransactionManager` in the
|
||||
single-resource case, as it does not require the container to support a JTA transaction
|
||||
coordinator. Switching between these transaction managers is just a matter of configuration,
|
||||
provided you stick to the required connection lookup pattern. Note that JTA does not support
|
||||
savepoints or custom isolation levels and has a different timeout mechanism but otherwise
|
||||
exposes similar behavior in terms of JDBC resources and JDBC commit/rollback management.
|
||||
|
||||
NOTE: As of 5.3, Spring provides an extended `JdbcTransactionManager` variant which adds
|
||||
exception translation capabilities on commit/rollback (aligned with `JdbcTemplate`).
|
||||
Where `DataSourceTransactionManager` will only ever throw `TransactionSystemException`
|
||||
(analogous to JTA), `JdbcTransactionManager` translates database locking failures etc to
|
||||
corresponding `DataAccessException` subclasses. Note that application code needs to be
|
||||
prepared for such exceptions, not exclusively expecting `TransactionSystemException`.
|
||||
In scenarios where that is the case, `JdbcTransactionManager` is the recommended choice.
|
||||
|
||||
In terms of exception behavior, `JdbcTransactionManager` is roughly equivalent to
|
||||
`JpaTransactionManager` and also to `R2dbcTransactionManager`, serving as an immediate
|
||||
companion/replacement for each other. `DataSourceTransactionManager` on the other hand
|
||||
is equivalent to `JtaTransactionManager` and can serve as a direct replacement there.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -501,8 +501,8 @@ extend from it, your sub-class inherits a `setDataSource(..)` method from the
|
||||
Regardless of which of the above template initialization styles you choose to use (or
|
||||
not), it is seldom necessary to create a new instance of a `JdbcTemplate` class each
|
||||
time you want to run SQL. Once configured, a `JdbcTemplate` instance is thread-safe.
|
||||
If your application accesses multiple
|
||||
databases, you may want multiple `JdbcTemplate` instances, which requires multiple `DataSources` and, subsequently, multiple differently
|
||||
If your application accesses multiple databases, you may want multiple `JdbcTemplate`
|
||||
instances, which requires multiple `DataSources` and, subsequently, multiple differently
|
||||
configured `JdbcTemplate` instances.
|
||||
|
||||
|
||||
@@ -531,11 +531,8 @@ Java::
|
||||
}
|
||||
|
||||
public int countOfActorsByFirstName(String firstName) {
|
||||
|
||||
String sql = "select count(*) from T_ACTOR where first_name = :first_name";
|
||||
|
||||
String sql = "select count(*) from t_actor where first_name = :first_name";
|
||||
SqlParameterSource namedParameters = new MapSqlParameterSource("first_name", firstName);
|
||||
|
||||
return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
|
||||
}
|
||||
----
|
||||
@@ -547,7 +544,7 @@ Kotlin::
|
||||
private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)
|
||||
|
||||
fun countOfActorsByFirstName(firstName: String): Int {
|
||||
val sql = "select count(*) from T_ACTOR where first_name = :first_name"
|
||||
val sql = "select count(*) from t_actor where first_name = :first_name"
|
||||
val namedParameters = MapSqlParameterSource("first_name", firstName)
|
||||
return namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Int::class.java)!!
|
||||
}
|
||||
@@ -579,12 +576,9 @@ Java::
|
||||
}
|
||||
|
||||
public int countOfActorsByFirstName(String firstName) {
|
||||
|
||||
String sql = "select count(*) from T_ACTOR where first_name = :first_name";
|
||||
|
||||
String sql = "select count(*) from t_actor where first_name = :first_name";
|
||||
Map<String, String> namedParameters = Collections.singletonMap("first_name", firstName);
|
||||
|
||||
return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
|
||||
return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
|
||||
}
|
||||
----
|
||||
|
||||
@@ -596,7 +590,7 @@ Kotlin::
|
||||
private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)
|
||||
|
||||
fun countOfActorsByFirstName(firstName: String): Int {
|
||||
val sql = "select count(*) from T_ACTOR where first_name = :first_name"
|
||||
val sql = "select count(*) from t_actor where first_name = :first_name"
|
||||
val namedParameters = mapOf("first_name" to firstName)
|
||||
return namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Int::class.java)!!
|
||||
}
|
||||
@@ -644,7 +638,6 @@ Java::
|
||||
}
|
||||
|
||||
// setters omitted...
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
@@ -673,12 +666,9 @@ Java::
|
||||
}
|
||||
|
||||
public int countOfActors(Actor exampleActor) {
|
||||
|
||||
// notice how the named parameters match the properties of the above 'Actor' class
|
||||
String sql = "select count(*) from T_ACTOR where first_name = :firstName and last_name = :lastName";
|
||||
|
||||
String sql = "select count(*) from t_actor where first_name = :firstName and last_name = :lastName";
|
||||
SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(exampleActor);
|
||||
|
||||
return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
|
||||
}
|
||||
----
|
||||
@@ -694,7 +684,7 @@ Kotlin::
|
||||
|
||||
fun countOfActors(exampleActor: Actor): Int {
|
||||
// notice how the named parameters match the properties of the above 'Actor' class
|
||||
val sql = "select count(*) from T_ACTOR where first_name = :firstName and last_name = :lastName"
|
||||
val sql = "select count(*) from t_actor where first_name = :firstName and last_name = :lastName"
|
||||
val namedParameters = BeanPropertySqlParameterSource(exampleActor)
|
||||
return namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Int::class.java)!!
|
||||
}
|
||||
@@ -707,8 +697,8 @@ functionality that is present only in the `JdbcTemplate` class, you can use the
|
||||
`getJdbcOperations()` method to access the wrapped `JdbcTemplate` through the
|
||||
`JdbcOperations` interface.
|
||||
|
||||
See also xref:data-access/jdbc/core.adoc#jdbc-JdbcTemplate-idioms[`JdbcTemplate` Best Practices] for guidelines on using the
|
||||
`NamedParameterJdbcTemplate` class in the context of an application.
|
||||
See also xref:data-access/jdbc/core.adoc#jdbc-JdbcTemplate-idioms[`JdbcTemplate` Best Practices]
|
||||
for guidelines on using the `NamedParameterJdbcTemplate` class in the context of an application.
|
||||
|
||||
|
||||
[[jdbc-SQLExceptionTranslator]]
|
||||
@@ -718,12 +708,22 @@ See also xref:data-access/jdbc/core.adoc#jdbc-JdbcTemplate-idioms[`JdbcTemplate`
|
||||
between ``SQLException``s and Spring's own `org.springframework.dao.DataAccessException`,
|
||||
which is agnostic in regard to data access strategy. Implementations can be generic (for
|
||||
example, using SQLState codes for JDBC) or proprietary (for example, using Oracle error
|
||||
codes) for greater precision.
|
||||
codes) for greater precision. This exception translation mechanism is used behind the
|
||||
the common `JdbcTemplate` and `JdbcTransactionManager` entry points which do not
|
||||
propagate `SQLException` but rather `DataAccessException`.
|
||||
|
||||
NOTE: As of 6.0, the default exception translator is `SQLExceptionSubclassTranslator`,
|
||||
detecting JDBC 4 `SQLException` subclasses with a few extra checks, and with a fallback
|
||||
to `SQLState` introspection through `SQLStateSQLExceptionTranslator`. This is usually
|
||||
sufficient for common database access and does not require vendor-specific detection.
|
||||
For backwards compatibility, consider using `SQLErrorCodeSQLExceptionTranslator` as
|
||||
described below, potentially with custom error code mappings.
|
||||
|
||||
`SQLErrorCodeSQLExceptionTranslator` is the implementation of `SQLExceptionTranslator`
|
||||
that is used by default. This implementation uses specific vendor codes. It is more
|
||||
precise than the `SQLState` implementation. The error code translations are based on
|
||||
codes held in a JavaBean type class called `SQLErrorCodes`. This class is created and
|
||||
that is used by default when a file named `sql-error-codes.xml` is present in the root
|
||||
of the classpath. This implementation uses specific vendor codes. It is more precise than
|
||||
`SQLState` or `SQLException` subclass translation. The error code translations are based
|
||||
on codes held in a JavaBean type class called `SQLErrorCodes`. This class is created and
|
||||
populated by an `SQLErrorCodesFactory`, which (as the name suggests) is a factory for
|
||||
creating `SQLErrorCodes` based on the contents of a configuration file named
|
||||
`sql-error-codes.xml`. This file is populated with vendor codes and based on the
|
||||
@@ -744,8 +744,8 @@ The `SQLErrorCodeSQLExceptionTranslator` applies matching rules in the following
|
||||
translator. If this translation is not available, the next fallback translator is
|
||||
the `SQLStateSQLExceptionTranslator`.
|
||||
|
||||
NOTE: The `SQLErrorCodesFactory` is used by default to define `Error` codes and custom exception
|
||||
translations. They are looked up in a file named `sql-error-codes.xml` from the
|
||||
NOTE: The `SQLErrorCodesFactory` is used by default to define error codes and custom
|
||||
exception translations. They are looked up in a file named `sql-error-codes.xml` from the
|
||||
classpath, and the matching `SQLErrorCodes` instance is located based on the database
|
||||
name from the database metadata of the database in use.
|
||||
|
||||
@@ -784,12 +784,12 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
In the preceding example, the specific error code (`-12345`) is translated, while other errors are
|
||||
left to be translated by the default translator implementation. To use this custom
|
||||
translator, you must pass it to the `JdbcTemplate` through the method
|
||||
`setExceptionTranslator`, and you must use this `JdbcTemplate` for all of the data access
|
||||
processing where this translator is needed. The following example shows how you can use this custom
|
||||
translator:
|
||||
In the preceding example, the specific error code (`-12345`) is translated while
|
||||
other errors are left to be translated by the default translator implementation.
|
||||
To use this custom translator, you must pass it to the `JdbcTemplate` through the
|
||||
method `setExceptionTranslator`, and you must use this `JdbcTemplate` for all of the
|
||||
data access processing where this translator is needed. The following example shows
|
||||
how you can use this custom translator:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -800,7 +800,6 @@ Java::
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
|
||||
// create a JdbcTemplate and set data source
|
||||
this.jdbcTemplate = new JdbcTemplate();
|
||||
this.jdbcTemplate.setDataSource(dataSource);
|
||||
@@ -809,7 +808,6 @@ Java::
|
||||
CustomSQLErrorCodesTranslator tr = new CustomSQLErrorCodesTranslator();
|
||||
tr.setDataSource(dataSource);
|
||||
this.jdbcTemplate.setExceptionTranslator(tr);
|
||||
|
||||
}
|
||||
|
||||
public void updateShippingCharge(long orderId, long pct) {
|
||||
|
||||
@@ -3,30 +3,30 @@
|
||||
|
||||
The Spring Framework's JDBC abstraction framework consists of four different packages:
|
||||
|
||||
* `core`: The `org.springframework.jdbc.core` package contains the `JdbcTemplate` class and its
|
||||
various callback interfaces, plus a variety of related classes. A subpackage named
|
||||
`org.springframework.jdbc.core.simple` contains the `SimpleJdbcInsert` and
|
||||
* `core`: The `org.springframework.jdbc.core` package contains the `JdbcTemplate` class
|
||||
and its various callback interfaces, plus a variety of related classes. A subpackage
|
||||
named `org.springframework.jdbc.core.simple` contains the `SimpleJdbcInsert` and
|
||||
`SimpleJdbcCall` classes. Another subpackage named
|
||||
`org.springframework.jdbc.core.namedparam` contains the `NamedParameterJdbcTemplate`
|
||||
class and the related support classes. See xref:data-access/jdbc/core.adoc[Using the JDBC Core Classes to Control Basic JDBC Processing and Error Handling], xref:data-access/jdbc/advanced.adoc[JDBC Batch Operations], and
|
||||
xref:data-access/jdbc/simple.adoc[Simplifying JDBC Operations with the `SimpleJdbc` Classes].
|
||||
|
||||
* `datasource`: The `org.springframework.jdbc.datasource` package contains a utility class for easy
|
||||
`DataSource` access and various simple `DataSource` implementations that you can use for
|
||||
testing and running unmodified JDBC code outside of a Jakarta EE container. A subpackage
|
||||
named `org.springfamework.jdbc.datasource.embedded` provides support for creating
|
||||
* `datasource`: The `org.springframework.jdbc.datasource` package contains a utility class
|
||||
for easy `DataSource` access and various simple `DataSource` implementations that you can
|
||||
use for testing and running unmodified JDBC code outside of a Jakarta EE container. A subpackage
|
||||
named `org.springframework.jdbc.datasource.embedded` provides support for creating
|
||||
embedded databases by using Java database engines, such as HSQL, H2, and Derby. See
|
||||
xref:data-access/jdbc/connections.adoc[Controlling Database Connections] and xref:data-access/jdbc/embedded-database-support.adoc[Embedded Database Support].
|
||||
|
||||
* `object`: The `org.springframework.jdbc.object` package contains classes that represent RDBMS
|
||||
queries, updates, and stored procedures as thread-safe, reusable objects. See
|
||||
* `object`: The `org.springframework.jdbc.object` package contains classes that represent
|
||||
RDBMS queries, updates, and stored procedures as thread-safe, reusable objects. See
|
||||
xref:data-access/jdbc/object.adoc[Modeling JDBC Operations as Java Objects]. This approach is modeled by JDO, although objects returned by queries
|
||||
are naturally disconnected from the database. This higher-level of JDBC abstraction
|
||||
depends on the lower-level abstraction in the `org.springframework.jdbc.core` package.
|
||||
|
||||
* `support`: The `org.springframework.jdbc.support` package provides `SQLException` translation
|
||||
functionality and some utility classes. Exceptions thrown during JDBC processing are
|
||||
translated to exceptions defined in the `org.springframework.dao` package. This means
|
||||
* `support`: The `org.springframework.jdbc.support` package provides `SQLException`
|
||||
translation functionality and some utility classes. Exceptions thrown during JDBC processing
|
||||
are translated to exceptions defined in the `org.springframework.dao` package. This means
|
||||
that code using the Spring JDBC abstraction layer does not need to implement JDBC or
|
||||
RDBMS-specific error handling. All translated exceptions are unchecked, which gives you
|
||||
the option of catching the exceptions from which you can recover while letting other
|
||||
|
||||
@@ -121,18 +121,17 @@ Kotlin::
|
||||
<3> Using the method `setBlobAsBinaryStream` to pass in the contents of the BLOB.
|
||||
======
|
||||
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
If you invoke the `setBlobAsBinaryStream`, `setClobAsAsciiStream`, or
|
||||
`setClobAsCharacterStream` method on the `LobCreator` returned from
|
||||
`DefaultLobHandler.getLobCreator()`, you can optionally specify a negative value for the
|
||||
`contentLength` argument. If the specified content length is negative, the
|
||||
`DefaultLobHandler.getLobCreator()`, you can optionally specify a negative value
|
||||
for the `contentLength` argument. If the specified content length is negative, the
|
||||
`DefaultLobHandler` uses the JDBC 4.0 variants of the set-stream methods without a
|
||||
length parameter. Otherwise, it passes the specified length on to the driver.
|
||||
|
||||
See the documentation for the JDBC driver you use to verify that it supports streaming a
|
||||
LOB without providing the content length.
|
||||
See the documentation for the JDBC driver you use to verify that it supports streaming
|
||||
a LOB without providing the content length.
|
||||
====
|
||||
|
||||
Now it is time to read the LOB data from the database. Again, you use a `JdbcTemplate`
|
||||
@@ -184,15 +183,15 @@ variable list of values. A typical example would be `select * from T_ACTOR where
|
||||
JDBC standard. You cannot declare a variable number of placeholders. You need a number
|
||||
of variations with the desired number of placeholders prepared, or you need to generate
|
||||
the SQL string dynamically once you know how many placeholders are required. The named
|
||||
parameter support provided in the `NamedParameterJdbcTemplate` and `JdbcTemplate` takes
|
||||
the latter approach. You can pass in the values as a `java.util.List` of primitive objects. This
|
||||
list is used to insert the required placeholders and pass in the values during
|
||||
statement execution.
|
||||
parameter support provided in the `NamedParameterJdbcTemplate` takes the latter approach.
|
||||
You can pass in the values as a `java.util.List` (or any `Iterable`) of simple values.
|
||||
This list is used to insert the required placeholders into the actual SQL statement
|
||||
and pass in the values during statement execution.
|
||||
|
||||
NOTE: Be careful when passing in many values. The JDBC standard does not guarantee that you
|
||||
can use more than 100 values for an `in` expression list. Various databases exceed this
|
||||
number, but they usually have a hard limit for how many values are allowed. For example, Oracle's
|
||||
limit is 1000.
|
||||
NOTE: Be careful when passing in many values. The JDBC standard does not guarantee that
|
||||
you can use more than 100 values for an `IN` expression list. Various databases exceed
|
||||
this number, but they usually have a hard limit for how many values are allowed.
|
||||
For example, Oracle's limit is 1000.
|
||||
|
||||
In addition to the primitive values in the value list, you can create a `java.util.List`
|
||||
of object arrays. This list can support multiple expressions being defined for the `in`
|
||||
|
||||
@@ -88,12 +88,6 @@ You can use this option for full JPA capabilities in a Spring-based application
|
||||
This includes web containers such as Tomcat, stand-alone applications, and
|
||||
integration tests with sophisticated persistence requirements.
|
||||
|
||||
NOTE: If you want to specifically configure a Hibernate setup, an immediate alternative
|
||||
is to set up a native Hibernate `LocalSessionFactoryBean` instead of a plain JPA
|
||||
`LocalContainerEntityManagerFactoryBean`, letting it interact with JPA access code
|
||||
as well as native Hibernate access code.
|
||||
See xref:data-access/orm/jpa.adoc#orm-jpa-hibernate[Native Hibernate setup for JPA interaction] for details.
|
||||
|
||||
The `LocalContainerEntityManagerFactoryBean` gives full control over
|
||||
`EntityManagerFactory` configuration and is appropriate for environments where
|
||||
fine-grained customization is required. The `LocalContainerEntityManagerFactoryBean`
|
||||
@@ -187,6 +181,7 @@ and automatic propagation of the weaver to all weaver-aware beans:
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<context:load-time-weaver/>
|
||||
|
||||
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
|
||||
...
|
||||
</bean>
|
||||
@@ -281,8 +276,8 @@ Spring Data JPA, make sure to set up deferred bootstrapping for its repositories
|
||||
[[orm-jpa-dao]]
|
||||
== Implementing DAOs Based on JPA: `EntityManagerFactory` and `EntityManager`
|
||||
|
||||
NOTE: Although `EntityManagerFactory` instances are thread-safe, `EntityManager` instances are
|
||||
not. The injected JPA `EntityManager` behaves like an `EntityManager` fetched from an
|
||||
NOTE: Although `EntityManagerFactory` instances are thread-safe, `EntityManager` instances
|
||||
are not. The injected JPA `EntityManager` behaves like an `EntityManager` fetched from an
|
||||
application server's JNDI environment, as defined by the JPA specification. It delegates
|
||||
all calls to the current transactional `EntityManager`, if any. Otherwise, it falls back
|
||||
to a newly created `EntityManager` per operation, in effect making its usage thread-safe.
|
||||
@@ -290,8 +285,8 @@ to a newly created `EntityManager` per operation, in effect making its usage thr
|
||||
It is possible to write code against the plain JPA without any Spring dependencies, by
|
||||
using an injected `EntityManagerFactory` or `EntityManager`. Spring can understand the
|
||||
`@PersistenceUnit` and `@PersistenceContext` annotations both at the field and the method level
|
||||
if a `PersistenceAnnotationBeanPostProcessor` is enabled. The following example shows a plain JPA DAO implementation
|
||||
that uses the `@PersistenceUnit` annotation:
|
||||
if a `PersistenceAnnotationBeanPostProcessor` is enabled. The following example shows a plain
|
||||
JPA DAO implementation that uses the `@PersistenceUnit` annotation:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -384,9 +379,9 @@ Consider the following example:
|
||||
----
|
||||
|
||||
The main problem with such a DAO is that it always creates a new `EntityManager` through
|
||||
the factory. You can avoid this by requesting a transactional `EntityManager` (also
|
||||
called a "`shared EntityManager`" because it is a shared, thread-safe proxy for the actual
|
||||
transactional EntityManager) to be injected instead of the factory. The following example shows how to do so:
|
||||
the factory. You can avoid this by requesting a transactional `EntityManager` (also called a
|
||||
"`shared EntityManager`" because it is a shared, thread-safe proxy for the actual transactional
|
||||
EntityManager) to be injected instead of the factory. The following example shows how to do so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -425,24 +420,24 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
The `@PersistenceContext` annotation has an optional attribute called `type`, which defaults to
|
||||
`PersistenceContextType.TRANSACTION`. You can use this default to receive a shared
|
||||
The `@PersistenceContext` annotation has an optional attribute called `type`, which defaults
|
||||
to `PersistenceContextType.TRANSACTION`. You can use this default to receive a shared
|
||||
`EntityManager` proxy. The alternative, `PersistenceContextType.EXTENDED`, is a completely
|
||||
different affair. This results in a so-called extended `EntityManager`, which is not
|
||||
thread-safe and, hence, must not be used in a concurrently accessed component, such as a
|
||||
Spring-managed singleton bean. Extended `EntityManager` instances are only supposed to be used in
|
||||
stateful components that, for example, reside in a session, with the lifecycle of the
|
||||
Spring-managed singleton bean. Extended `EntityManager` instances are only supposed to be used
|
||||
in stateful components that, for example, reside in a session, with the lifecycle of the
|
||||
`EntityManager` not tied to a current transaction but rather being completely up to the
|
||||
application.
|
||||
|
||||
.Method- and field-level Injection
|
||||
****
|
||||
You can apply annotations that indicate dependency injections (such as `@PersistenceUnit` and
|
||||
`@PersistenceContext`) on field or methods inside a class -- hence the
|
||||
expressions "`method-level injection`" and "`field-level injection`". Field-level
|
||||
annotations are concise and easier to use while method-level annotations allow for further
|
||||
processing of the injected dependency. In both cases, the member visibility (public,
|
||||
protected, or private) does not matter.
|
||||
You can apply annotations that indicate dependency injections (such as `@PersistenceUnit`
|
||||
and `@PersistenceContext`) on field or methods inside a class -- hence the expressions
|
||||
"`method-level injection`" and "`field-level injection`". Field-level annotations are
|
||||
concise and easier to use while method-level annotations allow for further processing of the
|
||||
injected dependency. In both cases, the member visibility (public, protected, or private)
|
||||
does not matter.
|
||||
|
||||
What about class-level annotations?
|
||||
|
||||
@@ -451,21 +446,62 @@ injection.
|
||||
****
|
||||
|
||||
The injected `EntityManager` is Spring-managed (aware of the ongoing transaction).
|
||||
Even though the new DAO implementation uses method-level
|
||||
injection of an `EntityManager` instead of an `EntityManagerFactory`, no change is
|
||||
required in the application context XML, due to annotation usage.
|
||||
Even though the new DAO implementation uses method-level injection of an `EntityManager`
|
||||
instead of an `EntityManagerFactory`, no change is required in the bean definition
|
||||
due to annotation usage.
|
||||
|
||||
The main advantage of this DAO style is that it depends only on the Java Persistence API.
|
||||
No import of any Spring class is required. Moreover, as the JPA annotations are understood,
|
||||
the injections are applied automatically by the Spring container. This is appealing from
|
||||
a non-invasiveness perspective and can feel more natural to JPA developers.
|
||||
|
||||
[[orm-jpa-dao-autowired]]
|
||||
=== Implementing DAOs Based on `@Autowired` (typically with constructor-based injection)
|
||||
|
||||
`@PersistenceUnit` and `@PersistenceContext` can only be declared on methods and fields.
|
||||
What about providing JPA resources via constructors and other `@Autowired` injection points?
|
||||
|
||||
`EntityManagerFactory` can easily be injected via constructors and `@Autowired` fields/methods
|
||||
as long as the target is defined as a bean, e.g. via `LocalContainerEntityManagerFactoryBean`.
|
||||
The injection point matches the original `EntityManagerFactory` definition by type as-is.
|
||||
|
||||
However, an `@PersistenceContext`-style shared `EntityManager` reference is not available for
|
||||
regular dependency injection out of the box. In order to make it available for type-based
|
||||
matching as required by `@Autowired`, consider defining a `SharedEntityManagerBean` as a
|
||||
companion for your `EntityManagerFactory` definition:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
|
||||
...
|
||||
</bean>
|
||||
|
||||
<bean id="em" class="org.springframework.orm.jpa.support.SharedEntityManagerBean">
|
||||
<property name="entityManagerFactory" ref="emf"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
Alternatively, you may define an `@Bean` method based on `SharedEntityManagerCreator`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Bean("em")
|
||||
public static EntityManager sharedEntityManager(EntityManagerFactory emf) {
|
||||
return SharedEntityManagerCreator.createSharedEntityManager(emf);
|
||||
}
|
||||
----
|
||||
|
||||
In case of multiple persistence units, each `EntityManagerFactory` definition needs to be
|
||||
accompanied by a corresponding `EntityManager` bean definition, ideally with qualifiers
|
||||
that match with the distinct `EntityManagerFactory` definition in order to distinguish
|
||||
the persistence units via `@Autowired @Qualifier("...")`.
|
||||
|
||||
|
||||
[[orm-jpa-tx]]
|
||||
== Spring-driven JPA transactions
|
||||
== Spring-driven JPA Transactions
|
||||
|
||||
NOTE: We strongly encourage you to read xref:data-access/transaction/declarative.adoc[Declarative Transaction Management], if you have not
|
||||
already done so, to get more detailed coverage of Spring's declarative transaction support.
|
||||
NOTE: We strongly encourage you to read xref:data-access/transaction/declarative.adoc[Declarative Transaction Management],
|
||||
if you have not already done so, to get more detailed coverage of Spring's declarative transaction support.
|
||||
|
||||
The recommended strategy for JPA is local transactions through JPA's native transaction
|
||||
support. Spring's `JpaTransactionManager` provides many capabilities known from local
|
||||
@@ -478,11 +514,6 @@ to JDBC access code that accesses the same `DataSource`, provided that the regis
|
||||
Spring provides dialects for the EclipseLink and Hibernate JPA implementations.
|
||||
See the xref:data-access/orm/jpa.adoc#orm-jpa-dialect[next section] for details on the `JpaDialect` mechanism.
|
||||
|
||||
NOTE: As an immediate alternative, Spring's native `HibernateTransactionManager` is capable
|
||||
of interacting with JPA access code, adapting to several Hibernate specifics and providing
|
||||
JDBC interaction. This makes particular sense in combination with `LocalSessionFactoryBean`
|
||||
setup. See xref:data-access/orm/jpa.adoc#orm-jpa-hibernate[Native Hibernate Setup for JPA Interaction] for details.
|
||||
|
||||
|
||||
[[orm-jpa-dialect]]
|
||||
== Understanding `JpaDialect` and `JpaVendorAdapter`
|
||||
@@ -495,7 +526,7 @@ features supported by Spring, usually in a vendor-specific manner:
|
||||
* Applying specific transaction semantics (such as custom isolation level or transaction
|
||||
timeout)
|
||||
* Retrieving the transactional JDBC `Connection` (for exposure to JDBC-based DAOs)
|
||||
* Advanced translation of `PersistenceExceptions` to Spring `DataAccessExceptions`
|
||||
* Advanced translation of `PersistenceException` to Spring's `DataAccessException`
|
||||
|
||||
This is particularly valuable for special transaction semantics and for advanced
|
||||
translation of exception. The default implementation (`DefaultJpaDialect`) does
|
||||
|
||||
@@ -11,11 +11,13 @@ specification effort to standardize access to SQL databases using reactive patte
|
||||
The Spring Framework's R2DBC abstraction framework consists of two different packages:
|
||||
|
||||
* `core`: The `org.springframework.r2dbc.core` package contains the `DatabaseClient`
|
||||
class plus a variety of related classes. See xref:data-access/r2dbc.adoc#r2dbc-core[Using the R2DBC Core Classes to Control Basic R2DBC Processing and Error Handling].
|
||||
class plus a variety of related classes. See
|
||||
xref:data-access/r2dbc.adoc#r2dbc-core[Using the R2DBC Core Classes to Control Basic R2DBC Processing and Error Handling].
|
||||
|
||||
* `connection`: The `org.springframework.r2dbc.connection` package contains a utility class
|
||||
for easy `ConnectionFactory` access and various simple `ConnectionFactory` implementations
|
||||
that you can use for testing and running unmodified R2DBC. See xref:data-access/r2dbc.adoc#r2dbc-connections[Controlling Database Connections].
|
||||
that you can use for testing and running unmodified R2DBC. See
|
||||
xref:data-access/r2dbc.adoc#r2dbc-connections[Controlling Database Connections].
|
||||
|
||||
|
||||
[[r2dbc-core]]
|
||||
@@ -31,6 +33,7 @@ including error handling. It includes the following topics:
|
||||
* xref:data-access/r2dbc.adoc#r2dbc-DatabaseClient-filter[Statement Filters]
|
||||
* xref:data-access/r2dbc.adoc#r2dbc-auto-generated-keys[Retrieving Auto-generated Keys]
|
||||
|
||||
|
||||
[[r2dbc-DatabaseClient]]
|
||||
=== Using `DatabaseClient`
|
||||
|
||||
@@ -43,8 +46,9 @@ SQL and extract results. The `DatabaseClient` class:
|
||||
* Runs SQL queries
|
||||
* Update statements and stored procedure calls
|
||||
* Performs iteration over `Result` instances
|
||||
* Catches R2DBC exceptions and translates them to the generic, more informative, exception
|
||||
hierarchy defined in the `org.springframework.dao` package. (See xref:data-access/dao.adoc#dao-exceptions[Consistent Exception Hierarchy].)
|
||||
* Catches R2DBC exceptions and translates them to the generic, more informative,
|
||||
exception hierarchy defined in the `org.springframework.dao` package.
|
||||
(See xref:data-access/dao.adoc#dao-exceptions[Consistent Exception Hierarchy].)
|
||||
|
||||
The client has a functional, fluent API using reactive types for declarative composition.
|
||||
|
||||
@@ -250,7 +254,6 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[r2dbc-DatabaseClient-mapping-null]]
|
||||
.What about `null`?
|
||||
****
|
||||
@@ -315,10 +318,10 @@ 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")
|
||||
.bind("age", 34);
|
||||
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
|
||||
.bind("id", "joe")
|
||||
.bind("name", "Joe")
|
||||
.bind("age", 34);
|
||||
----
|
||||
|
||||
.R2DBC Native Bind Markers
|
||||
@@ -327,7 +330,7 @@ R2DBC uses database-native bind markers that depend on the actual database vendo
|
||||
As an example, Postgres uses indexed markers, such as `$1`, `$2`, `$n`.
|
||||
Another example is SQL Server, which uses named bind markers prefixed with `@`.
|
||||
|
||||
This is different from JDBC, which requires `?` as bind markers.
|
||||
This is different from JDBC which requires `?` as bind markers.
|
||||
In JDBC, the actual drivers translate `?` bind markers to database-native
|
||||
markers as part of their statement execution.
|
||||
|
||||
@@ -363,7 +366,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::
|
||||
@@ -375,7 +378,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)
|
||||
----
|
||||
======
|
||||
|
||||
@@ -390,7 +393,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
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::
|
||||
@@ -402,7 +405,7 @@ Kotlin::
|
||||
tuples.add(arrayOf("Ann", 50))
|
||||
|
||||
client.sql("SELECT id, name, state FROM table WHERE age IN (:ages)")
|
||||
.bind("tuples", arrayOf(35, 50))
|
||||
.bind("tuples", arrayOf(35, 50))
|
||||
----
|
||||
======
|
||||
|
||||
@@ -429,9 +432,9 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
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::
|
||||
@@ -439,9 +442,9 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
|
||||
.filter { s: Statement, next: ExecuteFunction -> next.execute(s.returnGeneratedValues("id")) }
|
||||
.bind("name", …)
|
||||
.bind("state", …)
|
||||
.filter { s: Statement, next: ExecuteFunction -> next.execute(s.returnGeneratedValues("id")) }
|
||||
.bind("name", …)
|
||||
.bind("state", …)
|
||||
----
|
||||
======
|
||||
|
||||
@@ -455,10 +458,10 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
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::
|
||||
@@ -466,10 +469,10 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
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) }
|
||||
----
|
||||
======
|
||||
|
||||
@@ -593,7 +596,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
Mono<Integer> generatedId = client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
|
||||
.filter(statement -> s.returnGeneratedValues("id"))
|
||||
.filter(statement -> s.returnGeneratedValues("id"))
|
||||
.map(row -> row.get("id", Integer.class))
|
||||
.first();
|
||||
|
||||
@@ -605,7 +608,7 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val generatedId = client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
|
||||
.filter { statement -> s.returnGeneratedValues("id") }
|
||||
.filter { statement -> s.returnGeneratedValues("id") }
|
||||
.map { row -> row.get("id", Integer.class) }
|
||||
.awaitOne()
|
||||
|
||||
@@ -672,7 +675,6 @@ Kotlin::
|
||||
[[r2dbc-ConnectionFactoryUtils]]
|
||||
=== Using `ConnectionFactoryUtils`
|
||||
|
||||
|
||||
The `ConnectionFactoryUtils` class is a convenient and powerful helper class
|
||||
that provides `static` methods to obtain connections from `ConnectionFactory`
|
||||
and close connections (if necessary).
|
||||
@@ -718,19 +720,15 @@ javadoc for more details.
|
||||
=== Using `R2dbcTransactionManager`
|
||||
|
||||
The `R2dbcTransactionManager` class is a `ReactiveTransactionManager` implementation for
|
||||
single R2DBC data sources. It binds an R2DBC connection from the specified connection factory
|
||||
to the subscriber `Context`, potentially allowing for one subscriber connection for each
|
||||
connection factory.
|
||||
a single R2DBC `ConnectionFactory`. It binds an R2DBC `Connection` from the specified
|
||||
`ConnectionFactory` to the subscriber `Context`, potentially allowing for one subscriber
|
||||
`Connection` for each `ConnectionFactory`.
|
||||
|
||||
Application code is required to retrieve the R2DBC connection through
|
||||
Application code is required to retrieve the R2DBC `Connection` through
|
||||
`ConnectionFactoryUtils.getConnection(ConnectionFactory)`, instead of R2DBC's standard
|
||||
`ConnectionFactory.create()`.
|
||||
|
||||
All framework classes (such as `DatabaseClient`) use this strategy implicitly.
|
||||
If not used with this transaction manager, the lookup strategy behaves exactly like the common one.
|
||||
Thus, it can be used in any case.
|
||||
|
||||
The `R2dbcTransactionManager` class supports custom isolation levels that get applied to the connection.
|
||||
`ConnectionFactory.create()`. All framework classes (such as `DatabaseClient`) use this
|
||||
strategy implicitly. If not used with a transaction manager, the lookup strategy behaves
|
||||
exactly like `ConnectionFactory.create()` and can therefore be used in any case.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -12,19 +12,16 @@ management that delivers the following benefits:
|
||||
than complex transaction APIs, such as JTA.
|
||||
* Excellent integration with Spring's data access abstractions.
|
||||
|
||||
The following sections describe the Spring Framework's transaction features and
|
||||
technologies:
|
||||
The following sections describe the Spring Framework's transaction features and technologies:
|
||||
|
||||
* xref:data-access/transaction/motivation.adoc[Advantages of the Spring Framework's transaction support model]
|
||||
describes why you would use the Spring Framework's transaction abstraction
|
||||
instead of EJB Container-Managed Transactions (CMT) or choosing to drive local
|
||||
transactions through a proprietary API, such as Hibernate.
|
||||
* xref:data-access/transaction/motivation.adoc[Advantages of the Spring Framework's transaction support model]
|
||||
describes why you would use the Spring Framework's transaction abstraction instead of EJB
|
||||
Container-Managed Transactions (CMT) or choosing to drive transactions through a proprietary API.
|
||||
* xref:data-access/transaction/strategies.adoc[Understanding the Spring Framework transaction abstraction]
|
||||
outlines the core classes and describes how to configure and obtain `DataSource`
|
||||
instances from a variety of sources.
|
||||
* xref:data-access/transaction/tx-resource-synchronization.adoc[Synchronizing resources with transactions] describes
|
||||
how the application code ensures that resources are created, reused, and cleaned up
|
||||
properly.
|
||||
outlines the core classes and describes how to configure and obtain `DataSource` instances
|
||||
from a variety of sources.
|
||||
* xref:data-access/transaction/tx-resource-synchronization.adoc[Synchronizing resources with transactions]
|
||||
describes how the application code ensures that resources are created, reused, and cleaned up properly.
|
||||
* xref:data-access/transaction/declarative.adoc[Declarative transaction management] describes support for
|
||||
declarative transaction management.
|
||||
* xref:data-access/transaction/programmatic.adoc[Programmatic transaction management] covers support for
|
||||
|
||||
+1
-33
@@ -13,39 +13,7 @@ javadoc for details.
|
||||
Spring's `JtaTransactionManager` is the standard choice to run on Jakarta EE application
|
||||
servers and is known to work on all common servers. Advanced functionality, such as
|
||||
transaction suspension, works on many servers as well (including GlassFish, JBoss and
|
||||
Geronimo) without any special configuration required. However, for fully supported
|
||||
transaction suspension and further advanced integration, Spring includes special adapters
|
||||
for WebLogic Server and WebSphere. These adapters are discussed in the following
|
||||
sections.
|
||||
|
||||
For standard scenarios, including WebLogic Server and WebSphere, consider using the
|
||||
convenient `<tx:jta-transaction-manager/>` configuration element. When configured,
|
||||
this element automatically detects the underlying server and chooses the best
|
||||
transaction manager available for the platform. This means that you need not explicitly
|
||||
configure server-specific adapter classes (as discussed in the following sections).
|
||||
Rather, they are chosen automatically, with the standard
|
||||
`JtaTransactionManager` as the default fallback.
|
||||
|
||||
|
||||
[[transaction-application-server-integration-websphere]]
|
||||
== IBM WebSphere
|
||||
|
||||
On WebSphere 6.1.0.9 and above, the recommended Spring JTA transaction manager to use is
|
||||
`WebSphereUowTransactionManager`. This special adapter uses IBM's `UOWManager` API,
|
||||
which is available in WebSphere Application Server 6.1.0.9 and later. With this adapter,
|
||||
Spring-driven transaction suspension (suspend and resume as initiated by
|
||||
`PROPAGATION_REQUIRES_NEW`) is officially supported by IBM.
|
||||
|
||||
|
||||
[[transaction-application-server-integration-weblogic]]
|
||||
== Oracle WebLogic Server
|
||||
|
||||
On WebLogic Server 9.0 or above, you would typically use the
|
||||
`WebLogicJtaTransactionManager` instead of the stock `JtaTransactionManager` class. This
|
||||
special WebLogic-specific subclass of the normal `JtaTransactionManager` supports the
|
||||
full power of Spring's transaction definitions in a WebLogic-managed transaction
|
||||
environment, beyond standard JTA semantics. Features include transaction names,
|
||||
per-transaction isolation levels, and proper resuming of transactions in all cases.
|
||||
Geronimo) without any special configuration required.
|
||||
|
||||
|
||||
|
||||
|
||||
+46
-60
@@ -76,7 +76,7 @@ Kotlin::
|
||||
|
||||
Used at the class level as above, the annotation indicates a default for all methods of
|
||||
the declaring class (as well as its subclasses). Alternatively, each method can be
|
||||
annotated individually. See xref:data-access/transaction/declarative/annotations.adoc#transaction-declarative-annotations-method-visibility[null] for
|
||||
annotated individually. See xref:data-access/transaction/declarative/annotations.adoc#transaction-declarative-annotations-method-visibility[method visibility] for
|
||||
further details on which methods Spring considers transactional. Note that a class-level
|
||||
annotation does not apply to ancestor classes up the class hierarchy; in such a scenario,
|
||||
inherited methods need to be locally redeclared in order to participate in a
|
||||
@@ -124,7 +124,6 @@ In XML configuration, the `<tx:annotation-driven/>` tag provides similar conveni
|
||||
----
|
||||
<1> The line that makes the bean instance transactional.
|
||||
|
||||
|
||||
TIP: You can omit the `transaction-manager` attribute in the `<tx:annotation-driven/>`
|
||||
tag if the bean name of the `TransactionManager` that you want to wire in has the name
|
||||
`transactionManager`. If the `TransactionManager` bean that you want to dependency-inject
|
||||
@@ -194,74 +193,63 @@ Kotlin::
|
||||
======
|
||||
|
||||
Note that there are special considerations for the returned `Publisher` with regards to
|
||||
Reactive Streams cancellation signals. See the xref:data-access/transaction/programmatic.adoc#tx-prog-operator-cancel[Cancel Signals] section under
|
||||
"Using the TransactionalOperator" for more details.
|
||||
|
||||
Reactive Streams cancellation signals. See the
|
||||
xref:data-access/transaction/programmatic.adoc#tx-prog-operator-cancel[Cancel Signals]
|
||||
section under "Using the TransactionalOperator" for more details.
|
||||
|
||||
[[transaction-declarative-annotations-method-visibility]]
|
||||
.Method visibility and `@Transactional`
|
||||
.Method visibility and `@Transactional` in proxy mode
|
||||
[NOTE]
|
||||
====
|
||||
When you use transactional proxies with Spring's standard configuration, you should apply
|
||||
the `@Transactional` annotation only to methods with `public` visibility. If you do
|
||||
annotate `protected`, `private`, or package-visible methods with the `@Transactional`
|
||||
annotation, no error is raised, but the annotated method does not exhibit the configured
|
||||
transactional settings. If you need to annotate non-public methods, consider the tip in
|
||||
the following paragraph for class-based proxies or consider using AspectJ compile-time or
|
||||
load-time weaving (described later).
|
||||
The `@Transactional` annotation is typically used on methods with `public` visibility.
|
||||
As of 6.0, `protected` or package-visible methods can also be made transactional for
|
||||
class-based proxies by default. Note that transactional methods in interface-based
|
||||
proxies must always be `public` and defined in the proxied interface. For both kinds
|
||||
of proxies, only external method calls coming in through the proxy are intercepted.
|
||||
|
||||
When using `@EnableTransactionManagement` in a `@Configuration` class, `protected` or
|
||||
package-visible methods can also be made transactional for class-based proxies by
|
||||
registering a custom `transactionAttributeSource` bean like in the following example.
|
||||
Note, however, that transactional methods in interface-based proxies must always be
|
||||
`public` and defined in the proxied interface.
|
||||
If you prefer consistent treatment of method visibility across the different kinds of
|
||||
proxies (which was the default up until 5.3), consider specifying `publicMethodsOnly`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
/**
|
||||
* Register a custom AnnotationTransactionAttributeSource with the
|
||||
* publicMethodsOnly flag set to false to enable support for
|
||||
* protected and package-private @Transactional methods in
|
||||
* class-based proxies.
|
||||
*
|
||||
* publicMethodsOnly flag set to true to consistently ignore non-public methods.
|
||||
* @see ProxyTransactionManagementConfiguration#transactionAttributeSource()
|
||||
*/
|
||||
@Bean
|
||||
TransactionAttributeSource transactionAttributeSource() {
|
||||
return new AnnotationTransactionAttributeSource(false);
|
||||
return new AnnotationTransactionAttributeSource(true);
|
||||
}
|
||||
----
|
||||
|
||||
The _Spring TestContext Framework_ supports non-private `@Transactional` test methods by
|
||||
default. See xref:testing/testcontext-framework/tx.adoc[Transaction Management] in the testing
|
||||
chapter for examples.
|
||||
The _Spring TestContext Framework_ supports non-private `@Transactional` test methods
|
||||
by default as well. See xref:testing/testcontext-framework/tx.adoc[Transaction Management]
|
||||
in the testing chapter for examples.
|
||||
====
|
||||
|
||||
You can apply the `@Transactional` annotation to an interface definition, a method
|
||||
on an interface, a class definition, or a method on a class. However, the
|
||||
mere presence of the `@Transactional` annotation is not enough to activate the
|
||||
transactional behavior. The `@Transactional` annotation is merely metadata that can
|
||||
be consumed by some runtime infrastructure that is `@Transactional`-aware and that
|
||||
can use the metadata to configure the appropriate beans with transactional behavior.
|
||||
In the preceding example, the `<tx:annotation-driven/>` element switches on the
|
||||
transactional behavior.
|
||||
on an interface, a class definition, or a method on a class. However, the mere presence
|
||||
of the `@Transactional` annotation is not enough to activate the transactional behavior.
|
||||
The `@Transactional` annotation is merely metadata that can be consumed by corresponding
|
||||
runtime infrastructure which uses that metadata to configure the appropriate beans with
|
||||
transactional behavior. In the preceding example, the `<tx:annotation-driven/>` element
|
||||
switches on actual transaction management at runtime.
|
||||
|
||||
TIP: The Spring team recommends that you annotate only concrete classes (and methods of
|
||||
concrete classes) with the `@Transactional` annotation, as opposed to annotating interfaces.
|
||||
You certainly can place the `@Transactional` annotation on an interface (or an interface
|
||||
method), but this works only as you would expect it to if you use interface-based
|
||||
proxies. The fact that Java annotations are not inherited from interfaces means that,
|
||||
if you use class-based proxies (`proxy-target-class="true"`) or the weaving-based
|
||||
aspect (`mode="aspectj"`), the transaction settings are not recognized by the proxying
|
||||
and weaving infrastructure, and the object is not wrapped in a transactional proxy.
|
||||
TIP: The Spring team recommends that you annotate methods of concrete classes with the
|
||||
`@Transactional` annotation, rather than relying on annotated methods in interfaces,
|
||||
even if the latter does work for interface-based and target-class proxies as of 5.0.
|
||||
Since Java annotations are not inherited from interfaces, interface-declared annotations
|
||||
are still not recognized by the weaving infrastructure when using AspectJ mode, so the
|
||||
aspect does not get applied. As a consequence, your transaction annotations may be
|
||||
silently ignored: Your code might appear to "work" until you test a rollback scenario.
|
||||
|
||||
NOTE: In proxy mode (which is the default), only external method calls coming in through
|
||||
the proxy are intercepted. This means that self-invocation (in effect, a method within
|
||||
the target object calling another method of the target object) does not lead to an actual
|
||||
transaction at runtime even if the invoked method is marked with `@Transactional`. Also,
|
||||
the proxy must be fully initialized to provide the expected behavior, so you should not
|
||||
rely on this feature in your initialization code -- for example, in a `@PostConstruct`
|
||||
method.
|
||||
rely on this feature in your initialization code -- e.g. in a `@PostConstruct` method.
|
||||
|
||||
Consider using AspectJ mode (see the `mode` attribute in the following table) if you
|
||||
expect self-invocations to be wrapped with transactions as well. In this case, there is
|
||||
@@ -286,20 +274,20 @@ is modified) to support `@Transactional` runtime behavior on any kind of method.
|
||||
framework (following proxy semantics, as discussed earlier, applying to method calls
|
||||
coming in through the proxy only). The alternative mode (`aspectj`) instead weaves the
|
||||
affected classes with Spring's AspectJ transaction aspect, modifying the target class
|
||||
byte code to apply to any kind of method call. AspectJ weaving requires
|
||||
`spring-aspects.jar` in the classpath as well as having load-time weaving (or compile-time
|
||||
weaving) enabled. (See xref:core/aop/using-aspectj.adoc#aop-aj-ltw-spring[Spring configuration]
|
||||
for details on how to set up load-time weaving.)
|
||||
byte code to apply to any kind of method call. AspectJ weaving requires `spring-aspects.jar`
|
||||
in the classpath as well as having load-time weaving (or compile-time weaving) enabled.
|
||||
(See xref:core/aop/using-aspectj.adoc#aop-aj-ltw-spring[Spring configuration] for details
|
||||
on how to set up load-time weaving.)
|
||||
|
||||
| `proxy-target-class`
|
||||
| `proxyTargetClass`
|
||||
| `false`
|
||||
| Applies to `proxy` mode only. Controls what type of transactional proxies are created
|
||||
for classes annotated with the `@Transactional` annotation. If the
|
||||
`proxy-target-class` attribute is set to `true`, class-based proxies are created.
|
||||
If `proxy-target-class` is `false` or if the attribute is omitted, then standard JDK
|
||||
interface-based proxies are created. (See xref:core/aop/proxying.adoc[Proxying Mechanisms]
|
||||
for a detailed examination of the different proxy types.)
|
||||
for classes annotated with the `@Transactional` annotation. If the `proxy-target-class`
|
||||
attribute is set to `true`, class-based proxies are created. If `proxy-target-class` is
|
||||
`false` or if the attribute is omitted, then standard JDK interface-based proxies are
|
||||
created. (See xref:core/aop/proxying.adoc[Proxying Mechanisms] for a detailed examination
|
||||
of the different proxy types.)
|
||||
|
||||
| `order`
|
||||
| `order`
|
||||
@@ -375,7 +363,6 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[transaction-declarative-attransactional-settings]]
|
||||
== `@Transactional` Settings
|
||||
|
||||
@@ -454,10 +441,9 @@ on rollback rule semantics, patterns, and warnings regarding possible unintentio
|
||||
matches for pattern-based rollback rules.
|
||||
|
||||
Currently, you cannot have explicit control over the name of a transaction, where 'name'
|
||||
means the transaction name that appears in a transaction monitor, if applicable
|
||||
(for example, WebLogic's transaction monitor), and in logging output. For declarative
|
||||
transactions, the transaction name is always the fully-qualified class name + `.`
|
||||
+ the method name of the transactionally advised class. For example, if the
|
||||
means the transaction name that appears in a transaction monitor and in logging output.
|
||||
For declarative transactions, the transaction name is always the fully-qualified class
|
||||
name + `.` + the method name of the transactionally advised class. For example, if the
|
||||
`handlePayment(..)` method of the `BusinessService` class started a transaction, the
|
||||
name of the transaction would be: `com.example.BusinessService.handlePayment`.
|
||||
|
||||
@@ -522,17 +508,17 @@ The following listing shows the bean declarations:
|
||||
----
|
||||
<tx:annotation-driven/>
|
||||
|
||||
<bean id="transactionManager1" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
|
||||
<bean id="transactionManager1" class="org.springframework.jdbc.support.JdbcTransactionManager">
|
||||
...
|
||||
<qualifier value="order"/>
|
||||
</bean>
|
||||
|
||||
<bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
|
||||
<bean id="transactionManager2" class="org.springframework.jdbc.support.JdbcTransactionManager">
|
||||
...
|
||||
<qualifier value="account"/>
|
||||
</bean>
|
||||
|
||||
<bean id="transactionManager3" class="org.springframework.data.r2dbc.connectionfactory.R2dbcTransactionManager">
|
||||
<bean id="transactionManager3" class="org.springframework.data.r2dbc.connection.R2dbcTransactionManager">
|
||||
...
|
||||
<qualifier value="reactive-account"/>
|
||||
</bean>
|
||||
|
||||
+8
@@ -59,6 +59,14 @@ status and with an inner transaction's locks released immediately after its comp
|
||||
Such an independent inner transaction can also declare its own isolation level, timeout,
|
||||
and read-only settings and not inherit an outer transaction's characteristics.
|
||||
|
||||
NOTE: The resources attached to the outer transaction will remain bound there while
|
||||
the inner transaction acquires its own resources such as a new database connection.
|
||||
This may lead to exhaustion of the connection pool and potentially to a deadlock if
|
||||
several threads have an active outer transaction and wait to acquire a new connection
|
||||
for their inner transaction, with the pool not being able to hand out any such inner
|
||||
connection anymore. Do not use `PROPAGATION_REQUIRES_NEW` unless your connection pool
|
||||
is appropriately sized, exceeding the number of concurrent threads by at least 1.
|
||||
|
||||
[[tx-propagation-nested]]
|
||||
== Understanding `PROPAGATION_NESTED`
|
||||
|
||||
|
||||
@@ -98,9 +98,9 @@ through its `key` attribute. You can use xref:core/expressions.adoc[SpEL] to pic
|
||||
arguments of interest (or their nested properties), perform operations, or even
|
||||
invoke arbitrary methods without having to write any code or implement any interface.
|
||||
This is the recommended approach over the
|
||||
xref:integration/cache/annotations.adoc#cache-annotations-cacheable-default-key[default generator], since methods tend to be
|
||||
quite different in signatures as the code base grows. While the default strategy might
|
||||
work for some methods, it rarely works for all methods.
|
||||
xref:integration/cache/annotations.adoc#cache-annotations-cacheable-default-key[default generator],
|
||||
since methods tend to be quite different in signatures as the code base grows. While the
|
||||
default strategy might work for some methods, it rarely works for all methods.
|
||||
|
||||
The following examples use various SpEL declarations (if you are not familiar with SpEL,
|
||||
do yourself a favor and read xref:core/expressions.adoc[Spring Expression Language]):
|
||||
@@ -137,9 +137,8 @@ that specifies both results in an exception.
|
||||
[[cache-annotations-cacheable-default-cache-resolver]]
|
||||
=== Default Cache Resolution
|
||||
|
||||
The caching abstraction uses a simple `CacheResolver` that
|
||||
retrieves the caches defined at the operation level by using the configured
|
||||
`CacheManager`.
|
||||
The caching abstraction uses a simple `CacheResolver` that retrieves the caches
|
||||
defined at the operation level by using the configured `CacheManager`.
|
||||
|
||||
To provide a different default cache resolver, you need to implement the
|
||||
`org.springframework.cache.interceptor.CacheResolver` interface.
|
||||
@@ -160,12 +159,11 @@ For applications that work with several cache managers, you can set the
|
||||
----
|
||||
<1> Specifying `anotherCacheManager`.
|
||||
|
||||
|
||||
You can also replace the `CacheResolver` entirely in a fashion similar to that of
|
||||
replacing xref:integration/cache/annotations.adoc#cache-annotations-cacheable-key[key generation]. The resolution is
|
||||
requested for every cache operation, letting the implementation actually resolve
|
||||
the caches to use based on runtime arguments. The following example shows how to
|
||||
specify a `CacheResolver`:
|
||||
replacing xref:integration/cache/annotations.adoc#cache-annotations-cacheable-key[key generation].
|
||||
The resolution is requested for every cache operation, letting the implementation
|
||||
actually resolve the caches to use based on runtime arguments. The following example
|
||||
shows how to specify a `CacheResolver`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -174,7 +172,6 @@ specify a `CacheResolver`:
|
||||
----
|
||||
<1> Specifying the `CacheResolver`.
|
||||
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Since Spring 4.1, the `value` attribute of the cache annotations are no longer
|
||||
@@ -229,7 +226,6 @@ argument `name` has a length shorter than 32:
|
||||
----
|
||||
<1> Setting a condition on `@Cacheable`.
|
||||
|
||||
|
||||
In addition to the `condition` parameter, you can use the `unless` parameter to veto the
|
||||
adding of a value to the cache. Unlike `condition`, `unless` expressions are evaluated
|
||||
after the method has been invoked. To expand on the previous example, perhaps we only
|
||||
@@ -242,7 +238,6 @@ want to cache paperback books, as the following example does:
|
||||
----
|
||||
<1> Using the `unless` attribute to block hardbacks.
|
||||
|
||||
|
||||
The cache abstraction supports `java.util.Optional` return types. If an `Optional` value
|
||||
is _present_, it will be stored in the associated cache. If an `Optional` value is not
|
||||
present, `null` will be stored in the associated cache. `#result` always refers to the
|
||||
@@ -344,7 +339,7 @@ confirm the exclusion.
|
||||
|
||||
|
||||
[[cache-annotations-evict]]
|
||||
== The `@CacheEvict` annotation
|
||||
== The `@CacheEvict` Annotation
|
||||
|
||||
The cache abstraction allows not just population of a cache store but also eviction.
|
||||
This process is useful for removing stale or unused data from the cache. As opposed to
|
||||
@@ -402,7 +397,7 @@ The following example uses two `@CacheEvict` annotations:
|
||||
|
||||
|
||||
[[cache-annotations-config]]
|
||||
== The `@CacheConfig` annotation
|
||||
== The `@CacheConfig` Annotation
|
||||
|
||||
So far, we have seen that caching operations offer many customization options and that
|
||||
you can set these options for each operation. However, some of the customization options
|
||||
@@ -429,10 +424,13 @@ Placing this annotation on the class does not turn on any caching operation.
|
||||
An operation-level customization always overrides a customization set on `@CacheConfig`.
|
||||
Therefore, this gives three levels of customizations for each cache operation:
|
||||
|
||||
* Globally configured, available for `CacheManager`, `KeyGenerator`.
|
||||
* Globally configured, e.g. through `CachingConfigurer`: see next section.
|
||||
* At the class level, using `@CacheConfig`.
|
||||
* At the operation level.
|
||||
|
||||
NOTE: Provider-specific settings are typically available on the `CacheManager` bean,
|
||||
e.g. on `CaffeineCacheManager`. These are effectively also global.
|
||||
|
||||
|
||||
[[cache-annotation-enable]]
|
||||
== Enabling Caching Annotations
|
||||
@@ -451,6 +449,13 @@ To enable caching annotations add the annotation `@EnableCaching` to one of your
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
public class AppConfig {
|
||||
|
||||
@Bean
|
||||
CacheManager cacheManager() {
|
||||
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
|
||||
cacheManager.setCacheSpecification(...);
|
||||
return cacheManager;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
@@ -465,7 +470,11 @@ Alternatively, for XML configuration you can use the `cache:annotation-driven` e
|
||||
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/cache https://www.springframework.org/schema/cache/spring-cache.xsd">
|
||||
|
||||
<cache:annotation-driven/>
|
||||
<cache:annotation-driven/>
|
||||
|
||||
<bean id="cacheManager" class="org.springframework.cache.caffeine.CaffeineCacheManager">
|
||||
<property name="cacheSpecification" value="..."/>
|
||||
</bean>
|
||||
</beans>
|
||||
----
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ email when someone places an order:
|
||||
|
||||
// Call the collaborators to persist the order...
|
||||
|
||||
// Create a thread safe "copy" of the template message and customize it
|
||||
// Create a thread-safe "copy" of the template message and customize it
|
||||
SimpleMailMessage msg = new SimpleMailMessage(this.templateMessage);
|
||||
msg.setTo(order.getCustomer().getEmailAddress());
|
||||
msg.setText(
|
||||
|
||||
@@ -56,14 +56,13 @@ locally, as the following example shows:
|
||||
----
|
||||
|
||||
The specified `WorkManager` can also point to an environment-specific thread pool --
|
||||
typically through a `SimpleTaskWorkManager` instance's `asyncTaskExecutor` property. Consider
|
||||
defining a shared thread pool for all your `ResourceAdapter` instances if you happen to
|
||||
use multiple adapters.
|
||||
typically through a `SimpleTaskWorkManager` instance's `asyncTaskExecutor` property.
|
||||
Consider defining a shared thread pool for all your `ResourceAdapter` instances
|
||||
if you happen to use multiple adapters.
|
||||
|
||||
In some environments (such as WebLogic 9 or above), you can instead obtain the entire `ResourceAdapter` object
|
||||
from JNDI (by using `<jee:jndi-lookup>`). The Spring-based message
|
||||
listeners can then interact with the server-hosted `ResourceAdapter`, which also use the
|
||||
server's built-in `WorkManager`.
|
||||
In some environments, you can instead obtain the entire `ResourceAdapter` object from JNDI
|
||||
(by using `<jee:jndi-lookup>`). The Spring-based message listeners can then interact with
|
||||
the server-hosted `ResourceAdapter`, which also use the server's built-in `WorkManager`.
|
||||
|
||||
See the javadoc for {api-spring-framework}/jms/listener/endpoint/JmsMessageEndpointManager.html[`JmsMessageEndpointManager`],
|
||||
{api-spring-framework}/jms/listener/endpoint/JmsActivationSpecConfig.html[`JmsActivationSpecConfig`],
|
||||
|
||||
@@ -183,18 +183,21 @@ as the following example shows:
|
||||
|
||||
If you configure a bean with an `MBeanExporter` that is also configured for lazy
|
||||
initialization, the `MBeanExporter` does not break this contract and avoids
|
||||
instantiating the bean. Instead, it registers a proxy with the `MBeanServer` and
|
||||
defers obtaining the bean from the container until the first invocation on the proxy
|
||||
occurs.
|
||||
instantiating the bean. Instead, it registers a proxy with the `MBeanServer` and defers
|
||||
obtaining the bean from the container until the first invocation on the proxy occurs.
|
||||
|
||||
This also affects `FactoryBean` resolution where `MBeanExporter` will regularly
|
||||
introspect the produced object, effectively triggering `FactoryBean.getObject()`.
|
||||
In order to avoid this, mark the corresponding bean definition as lazy-init.
|
||||
|
||||
|
||||
[[jmx-exporting-auto]]
|
||||
== Automatic Registration of MBeans
|
||||
|
||||
Any beans that are exported through the `MBeanExporter` and are already valid MBeans are
|
||||
registered as-is with the `MBeanServer` without further intervention from Spring. You can cause MBeans
|
||||
to be automatically detected by the `MBeanExporter` by setting the `autodetect`
|
||||
property to `true`, as the following example shows:
|
||||
Any beans that are exported through the `MBeanExporter` and are already valid MBeans
|
||||
are registered as-is with the `MBeanServer` without further intervention from Spring.
|
||||
You can cause MBeans to be automatically detected by the `MBeanExporter` by setting
|
||||
the `autodetect` property to `true`, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
= Observability Support
|
||||
|
||||
Micrometer defines an https://micrometer.io/docs/observation[Observation concept that enables both Metrics and Traces] in applications.
|
||||
Metrics support offers a way to create timers, gauges or counters for collecting statistics about the runtime behavior of your application.
|
||||
Metrics can help you to track error rates, usage patterns, performance and more.
|
||||
Metrics support offers a way to create timers, gauges, or counters for collecting statistics about the runtime behavior of your application.
|
||||
Metrics can help you to track error rates, usage patterns, performance, and more.
|
||||
Traces provide a holistic view of an entire system, crossing application boundaries; you can zoom in on particular user requests and follow their entire completion across applications.
|
||||
|
||||
Spring Framework instruments various parts of its own codebase to publish observations if an `ObservationRegistry` is configured.
|
||||
@@ -36,16 +36,16 @@ https://micrometer.io/docs/concepts#_naming_meters[to the format preferred by th
|
||||
[[observability.concepts]]
|
||||
== Micrometer Observation concepts
|
||||
|
||||
If you are not familiar with Micrometer Observation, here's a quick summary of the new concepts you should know about.
|
||||
If you are not familiar with Micrometer Observation, here's a quick summary of the concepts you should know about.
|
||||
|
||||
* `Observation` is the actual recording of something happening in your application. This is processed by `ObservationHandler` implementations to produce metrics or traces.
|
||||
* Each observation has a corresponding `ObservationContext` implementation; this type holds all the relevant information for extracting metadata for it.
|
||||
In the case of an HTTP server observation, the context implementation could hold the HTTP request, the HTTP response, any Exception thrown during processing...
|
||||
* Each `Observation` holds `KeyValues` metadata. In the case of a server HTTP observation, this could be the HTTP request method, the HTTP response status...
|
||||
In the case of an HTTP server observation, the context implementation could hold the HTTP request, the HTTP response, any exception thrown during processing, and so forth.
|
||||
* Each `Observation` holds `KeyValues` metadata. In the case of an HTTP server observation, this could be the HTTP request method, the HTTP response status, and so forth.
|
||||
This metadata is contributed by `ObservationConvention` implementations which should declare the type of `ObservationContext` they support.
|
||||
* `KeyValues` are said to be "low cardinality" if there is a low, bounded number of possible values for the `KeyValue` tuple (HTTP method is a good example).
|
||||
Low cardinality values are contributed to metrics only.
|
||||
High cardinality values are on the other hand unbounded (for example, HTTP request URIs) and are only contributed to Traces.
|
||||
Conversely, "high cardinality" values are unbounded (for example, HTTP request URIs) and are only contributed to traces.
|
||||
* An `ObservationDocumentation` documents all observations in a particular domain, listing the expected key names and their meaning.
|
||||
|
||||
|
||||
@@ -63,16 +63,16 @@ Each instrumented component will provide two extension points:
|
||||
=== Using custom Observation conventions
|
||||
|
||||
Let's take the example of the Spring MVC "http.server.requests" metrics instrumentation with the `ServerHttpObservationFilter`.
|
||||
This observation is using a `ServerRequestObservationConvention` with a `ServerRequestObservationContext`; custom conventions can be configured on the Servlet filter.
|
||||
This observation uses a `ServerRequestObservationConvention` with a `ServerRequestObservationContext`; custom conventions can be configured on the Servlet filter.
|
||||
If you would like to customize the metadata produced with the observation, you can extend the `DefaultServerRequestObservationConvention` for your requirements:
|
||||
|
||||
include-code::./ExtendedServerRequestObservationConvention[]
|
||||
|
||||
If you want full control, you can then implement the entire convention contract for the observation you're interested in:
|
||||
If you want full control, you can implement the entire convention contract for the observation you're interested in:
|
||||
|
||||
include-code::./CustomServerRequestObservationConvention[]
|
||||
|
||||
You can also achieve similar goals using a custom `ObservationFilter` - adding or removing key values for an observation.
|
||||
You can also achieve similar goals using a custom `ObservationFilter` – adding or removing key values for an observation.
|
||||
Filters do not replace the default convention and are used as a post-processing component.
|
||||
|
||||
include-code::./ServerRequestObservationFilter[]
|
||||
@@ -83,20 +83,24 @@ You can configure `ObservationFilter` instances on the `ObservationRegistry`.
|
||||
[[observability.http-server]]
|
||||
== HTTP Server instrumentation
|
||||
|
||||
HTTP server exchanges observations are created with the name `"http.server.requests"` for Servlet and Reactive applications.
|
||||
HTTP server exchange observations are created with the name `"http.server.requests"` for Servlet and Reactive applications.
|
||||
|
||||
[[observability.http-server.servlet]]
|
||||
=== Servlet applications
|
||||
|
||||
Applications need to configure the `org.springframework.web.filter.ServerHttpObservationFilter` Servlet filter in their application.
|
||||
It is using the `org.springframework.http.server.observation.DefaultServerRequestObservationConvention` by default, backed by the `ServerRequestObservationContext`.
|
||||
It uses the `org.springframework.http.server.observation.DefaultServerRequestObservationConvention` by default, backed by the `ServerRequestObservationContext`.
|
||||
|
||||
This will only record an observation as an error if the `Exception` has not been handled by the web Framework and has bubbled up to the Servlet filter.
|
||||
This will only record an observation as an error if the `Exception` has not been handled by the web framework and has bubbled up to the Servlet filter.
|
||||
Typically, all exceptions handled by Spring MVC's `@ExceptionHandler` and xref:web/webmvc/mvc-ann-rest-exceptions.adoc[`ProblemDetail` support] will not be recorded with the observation.
|
||||
You can, at any point during request processing, set the error field on the `ObservationContext` yourself:
|
||||
|
||||
include-code::./UserController[]
|
||||
|
||||
NOTE: Because the instrumentation is done at the Servlet Filter level, the observation scope only covers the filters ordered after this one as well as the handling of the request.
|
||||
Typically, Servlet container error handling is performed at a lower level and won't have any active observation or span.
|
||||
For this use case, a container-specific implementation is required, such as a `org.apache.catalina.Valve` for Tomcat; this is outside of the scope of this project.
|
||||
|
||||
By default, the following `KeyValues` are created:
|
||||
|
||||
.Low cardinality Keys
|
||||
@@ -104,7 +108,7 @@ By default, the following `KeyValues` are created:
|
||||
|===
|
||||
|Name | Description
|
||||
|`exception` _(required)_|Name of the exception thrown during the exchange, or `KeyValue#NONE_VALUE`} if no exception happened.
|
||||
|`method` _(required)_|Name of HTTP request method or `"none"` if the request was not received properly.
|
||||
|`method` _(required)_|Name of HTTP request method or `"none"` if not a well-known method.
|
||||
|`outcome` _(required)_|Outcome of the HTTP server exchange.
|
||||
|`status` _(required)_|HTTP response raw status code, or `"UNKNOWN"` if no response was created.
|
||||
|`uri` _(required)_|URI pattern for the matching handler if available, falling back to `REDIRECTION` for 3xx responses, `NOT_FOUND` for 404 responses, `root` for requests with no path info, and `UNKNOWN` for all other requests.
|
||||
@@ -122,9 +126,9 @@ By default, the following `KeyValues` are created:
|
||||
=== Reactive applications
|
||||
|
||||
Applications need to configure the `org.springframework.web.filter.reactive.ServerHttpObservationFilter` reactive `WebFilter` in their application.
|
||||
It is using the `org.springframework.http.server.reactive.observation.DefaultServerRequestObservationConvention` by default, backed by the `ServerRequestObservationContext`.
|
||||
It uses the `org.springframework.http.server.reactive.observation.DefaultServerRequestObservationConvention` by default, backed by the `ServerRequestObservationContext`.
|
||||
|
||||
This will only record an observation as an error if the `Exception` has not been handled by the web Framework and has bubbled up to the `WebFilter`.
|
||||
This will only record an observation as an error if the `Exception` has not been handled by the web framework and has bubbled up to the `WebFilter`.
|
||||
Typically, all exceptions handled by Spring WebFlux's `@ExceptionHandler` and xref:web/webflux/ann-rest-exceptions.adoc[`ProblemDetail` support] will not be recorded with the observation.
|
||||
You can, at any point during request processing, set the error field on the `ObservationContext` yourself:
|
||||
|
||||
@@ -137,7 +141,7 @@ By default, the following `KeyValues` are created:
|
||||
|===
|
||||
|Name | Description
|
||||
|`exception` _(required)_|Name of the exception thrown during the exchange, or `"none"` if no exception happened.
|
||||
|`method` _(required)_|Name of HTTP request method or `"none"` if the request was not received properly.
|
||||
|`method` _(required)_|Name of HTTP request method or `"none"` if not a well-known method.
|
||||
|`outcome` _(required)_|Outcome of the HTTP server exchange.
|
||||
|`status` _(required)_|HTTP response raw status code, or `"UNKNOWN"` if no response was created.
|
||||
|`uri` _(required)_|URI pattern for the matching handler if available, falling back to `REDIRECTION` for 3xx responses, `NOT_FOUND` for 404 responses, `root` for requests with no path info, and `UNKNOWN` for all other requests.
|
||||
@@ -153,9 +157,9 @@ By default, the following `KeyValues` are created:
|
||||
|
||||
|
||||
[[observability.http-client]]
|
||||
== HTTP Client instrumentation
|
||||
== HTTP Client Instrumentation
|
||||
|
||||
HTTP client exchanges observations are created with the name `"http.client.requests"` for blocking and reactive clients.
|
||||
HTTP client exchange observations are created with the name `"http.client.requests"` for blocking and reactive clients.
|
||||
Unlike their server counterparts, the instrumentation is implemented directly in the client so the only required step is to configure an `ObservationRegistry` on the client.
|
||||
|
||||
[[observability.http-client.resttemplate]]
|
||||
@@ -164,13 +168,13 @@ Unlike their server counterparts, the instrumentation is implemented directly in
|
||||
Applications must configure an `ObservationRegistry` on `RestTemplate` instances to enable the instrumentation; without that, observations are "no-ops".
|
||||
Spring Boot will auto-configure `RestTemplateBuilder` beans with the observation registry already set.
|
||||
|
||||
Instrumentation is using the `org.springframework.http.client.observation.ClientRequestObservationConvention` by default, backed by the `ClientRequestObservationContext`.
|
||||
Instrumentation uses the `org.springframework.http.client.observation.ClientRequestObservationConvention` by default, backed by the `ClientRequestObservationContext`.
|
||||
|
||||
.Low cardinality Keys
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`method` _(required)_|Name of HTTP request method or `"none"` if the request could not be created.
|
||||
|`method` _(required)_|Name of HTTP request method or `"none"` if not a well-known method.
|
||||
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. Only the path part of the URI is considered.
|
||||
|`client.name` _(required)_|Client name derived from the request URI host.
|
||||
|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received.
|
||||
@@ -193,13 +197,13 @@ Instrumentation is using the `org.springframework.http.client.observation.Client
|
||||
Applications must configure an `ObservationRegistry` on the `WebClient` builder to enable the instrumentation; without that, observations are "no-ops".
|
||||
Spring Boot will auto-configure `WebClient.Builder` beans with the observation registry already set.
|
||||
|
||||
Instrumentation is using the `org.springframework.web.reactive.function.client.ClientRequestObservationConvention` by default, backed by the `ClientRequestObservationContext`.
|
||||
Instrumentation uses the `org.springframework.web.reactive.function.client.ClientRequestObservationConvention` by default, backed by the `ClientRequestObservationContext`.
|
||||
|
||||
.Low cardinality Keys
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`method` _(required)_|Name of HTTP request method or `"none"` if the request could not be created.
|
||||
|`method` _(required)_|Name of HTTP request method or `"none"` if not a well-known method.
|
||||
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. Only the path part of the URI is considered.
|
||||
|`client.name` _(required)_|Client name derived from the request URI host.
|
||||
|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received.
|
||||
|
||||
@@ -97,11 +97,7 @@ preparation and response extraction through callback interfaces.
|
||||
|
||||
The default constructor uses `java.net.HttpURLConnection` to perform requests. You can
|
||||
switch to a different HTTP library with an implementation of `ClientHttpRequestFactory`.
|
||||
There is built-in support for the following:
|
||||
|
||||
* Apache HttpComponents
|
||||
* Netty
|
||||
* OkHttp
|
||||
Currently, there is also built-in support for Apache HttpComponents and OkHttp.
|
||||
|
||||
For example, to switch to Apache HttpComponents, you can use the following:
|
||||
|
||||
|
||||
@@ -5,16 +5,11 @@ The Spring Framework provides abstractions for the asynchronous execution and sc
|
||||
tasks with the `TaskExecutor` and `TaskScheduler` interfaces, respectively. Spring also
|
||||
features implementations of those interfaces that support thread pools or delegation to
|
||||
CommonJ within an application server environment. Ultimately, the use of these
|
||||
implementations behind the common interfaces abstracts away the differences between Java
|
||||
SE 5, Java SE 6, and Jakarta EE environments.
|
||||
implementations behind the common interfaces abstracts away the differences between
|
||||
Java SE and Jakarta EE environments.
|
||||
|
||||
Spring also features integration classes to support scheduling with the `Timer`
|
||||
(part of the JDK since 1.3) and the https://www.quartz-scheduler.org/[Quartz Scheduler].
|
||||
You can set up both of those schedulers by using a `FactoryBean` with optional references to
|
||||
`Timer` or `Trigger` instances, respectively. Furthermore, a convenience class for both
|
||||
the Quartz Scheduler and the `Timer` is available that lets you invoke a method of
|
||||
an existing target object (analogous to the normal `MethodInvokingFactoryBean`
|
||||
operation).
|
||||
Spring also features integration classes to support scheduling with the
|
||||
https://www.quartz-scheduler.org/[Quartz Scheduler].
|
||||
|
||||
|
||||
|
||||
@@ -62,10 +57,10 @@ The variants that Spring provides are as follows:
|
||||
`ConcurrentTaskExecutor` directly. However, if the `ThreadPoolTaskExecutor` is not
|
||||
flexible enough for your needs, `ConcurrentTaskExecutor` is an alternative.
|
||||
* `ThreadPoolTaskExecutor`:
|
||||
This implementation is most commonly used. It exposes bean properties for
|
||||
configuring a `java.util.concurrent.ThreadPoolExecutor` and wraps it in a `TaskExecutor`.
|
||||
If you need to adapt to a different kind of `java.util.concurrent.Executor`, we
|
||||
recommend that you use a `ConcurrentTaskExecutor` instead.
|
||||
This implementation is most commonly used. It exposes bean properties for configuring
|
||||
a `java.util.concurrent.ThreadPoolExecutor` and wraps it in a `TaskExecutor`.
|
||||
If you need to adapt to a different kind of `java.util.concurrent.Executor`,
|
||||
we recommend that you use a `ConcurrentTaskExecutor` instead.
|
||||
* `DefaultManagedTaskExecutor`:
|
||||
This implementation uses a JNDI-obtained `ManagedExecutorService` in a JSR-236
|
||||
compatible runtime environment (such as a Jakarta EE application server),
|
||||
@@ -75,9 +70,9 @@ The variants that Spring provides are as follows:
|
||||
[[scheduling-task-executor-usage]]
|
||||
=== Using a `TaskExecutor`
|
||||
|
||||
Spring's `TaskExecutor` implementations are used as simple JavaBeans. In the following example,
|
||||
we define a bean that uses the `ThreadPoolTaskExecutor` to asynchronously print
|
||||
out a set of messages:
|
||||
Spring's `TaskExecutor` implementations are commonly used with dependency injection.
|
||||
In the following example, we define a bean that uses the `ThreadPoolTaskExecutor`
|
||||
to asynchronously print out a set of messages:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -227,8 +222,8 @@ fixed delay, those methods should be used directly whenever possible. The value
|
||||
`PeriodicTrigger` implementation is that you can use it within components that rely on
|
||||
the `Trigger` abstraction. For example, it may be convenient to allow periodic triggers,
|
||||
cron-based triggers, and even custom trigger implementations to be used interchangeably.
|
||||
Such a component could take advantage of dependency injection so that you can configure such `Triggers`
|
||||
externally and, therefore, easily modify or extend them.
|
||||
Such a component could take advantage of dependency injection so that you can configure
|
||||
such `Triggers` externally and, therefore, easily modify or extend them.
|
||||
|
||||
|
||||
[[scheduling-task-scheduler-implementations]]
|
||||
@@ -238,10 +233,8 @@ As with Spring's `TaskExecutor` abstraction, the primary benefit of the `TaskSch
|
||||
arrangement is that an application's scheduling needs are decoupled from the deployment
|
||||
environment. This abstraction level is particularly relevant when deploying to an
|
||||
application server environment where threads should not be created directly by the
|
||||
application itself. For such scenarios, Spring provides a `TimerManagerTaskScheduler`
|
||||
that delegates to a CommonJ `TimerManager` on WebLogic or WebSphere as well as a more recent
|
||||
`DefaultManagedTaskScheduler` that delegates to a JSR-236 `ManagedScheduledExecutorService`
|
||||
in a Jakarta EE environment. Both are typically configured with a JNDI lookup.
|
||||
application itself. For such scenarios, Spring provides a `DefaultManagedTaskScheduler`
|
||||
that delegates to a JSR-236 `ManagedScheduledExecutorService` in a Jakarta EE environment.
|
||||
|
||||
Whenever external thread management is not a requirement, a simpler alternative is
|
||||
a local `ScheduledExecutorService` setup within the application, which can be adapted
|
||||
@@ -263,8 +256,8 @@ execution.
|
||||
[[scheduling-enable-annotation-support]]
|
||||
=== Enable Scheduling Annotations
|
||||
|
||||
To enable support for `@Scheduled` and `@Async` annotations, you can add `@EnableScheduling` and
|
||||
`@EnableAsync` to one of your `@Configuration` classes, as the following example shows:
|
||||
To enable support for `@Scheduled` and `@Async` annotations, you can add `@EnableScheduling`
|
||||
and `@EnableAsync` to one of your `@Configuration` classes, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -338,7 +331,7 @@ For example, the previous example can also be written as follows.
|
||||
|
||||
If you need a fixed-rate execution, you can use the `fixedRate` attribute within the
|
||||
annotation. The following method is invoked every five seconds (measured between the
|
||||
successive start times of each invocation).
|
||||
successive start times of each invocation):
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -348,9 +341,9 @@ successive start times of each invocation).
|
||||
}
|
||||
----
|
||||
|
||||
For fixed-delay and fixed-rate tasks, you can specify an initial delay by indicating the
|
||||
amount of time to wait before the first execution of the method, as the following
|
||||
`fixedRate` example shows.
|
||||
For fixed-delay and fixed-rate tasks, you can specify an initial delay by indicating
|
||||
the amount of time to wait before the first execution of the method, as the following
|
||||
`fixedRate` example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -380,6 +373,12 @@ Notice that the methods to be scheduled must have void returns and must not acce
|
||||
arguments. If the method needs to interact with other objects from the application
|
||||
context, those would typically have been provided through dependency injection.
|
||||
|
||||
`@Scheduled` can be used as a repeatable annotation. If several scheduled declarations
|
||||
are found on the same method, each of them will be processed independently, with a
|
||||
separate trigger firing for each of them. As a consequence, such co-located schedules
|
||||
may overlap and execute multiple times in parallel or in immediate succession.
|
||||
Please make sure that your specified cron expressions etc do not accidentally overlap.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
As of Spring Framework 4.3, `@Scheduled` methods are supported on beans of any scope.
|
||||
@@ -413,8 +412,8 @@ to a method that returns `void`, as the following example shows:
|
||||
|
||||
Unlike the methods annotated with the `@Scheduled` annotation, these methods can expect
|
||||
arguments, because they are invoked in the "`normal`" way by callers at runtime rather
|
||||
than from a scheduled task being managed by the container. For example, the following code is
|
||||
a legitimate application of the `@Async` annotation:
|
||||
than from a scheduled task being managed by the container. For example, the following
|
||||
code is a legitimate application of the `@Async` annotation:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -438,15 +437,15 @@ that returns a value:
|
||||
}
|
||||
----
|
||||
|
||||
TIP: `@Async` methods may not only declare a regular `java.util.concurrent.Future` return type
|
||||
but also Spring's `org.springframework.util.concurrent.ListenableFuture` or, as of Spring
|
||||
4.2, JDK 8's `java.util.concurrent.CompletableFuture`, for richer interaction with the
|
||||
asynchronous task and for immediate composition with further processing steps.
|
||||
TIP: `@Async` methods may not only declare a regular `java.util.concurrent.Future` return
|
||||
type but also Spring's `org.springframework.util.concurrent.ListenableFuture` or, as of
|
||||
Spring 4.2, JDK 8's `java.util.concurrent.CompletableFuture`, for richer interaction with
|
||||
the asynchronous task and for immediate composition with further processing steps.
|
||||
|
||||
You can not use `@Async` in conjunction with lifecycle callbacks such as
|
||||
`@PostConstruct`. To asynchronously initialize Spring beans, you currently have to use
|
||||
a separate initializing Spring bean that then invokes the `@Async` annotated method on the
|
||||
target, as the following example shows:
|
||||
You can not use `@Async` in conjunction with lifecycle callbacks such as `@PostConstruct`.
|
||||
To asynchronously initialize Spring beans, you currently have to use a separate
|
||||
initializing Spring bean that then invokes the `@Async` annotated method on the target,
|
||||
as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -500,8 +499,8 @@ used when executing a given method. The following example shows how to do so:
|
||||
----
|
||||
|
||||
In this case, `"otherExecutor"` can be the name of any `Executor` bean in the Spring
|
||||
container, or it may be the name of a qualifier associated with any `Executor` (for example, as
|
||||
specified with the `<qualifier>` element or Spring's `@Qualifier` annotation).
|
||||
container, or it may be the name of a qualifier associated with any `Executor` (for example,
|
||||
as specified with the `<qualifier>` element or Spring's `@Qualifier` annotation).
|
||||
|
||||
|
||||
[[scheduling-annotation-support-exception]]
|
||||
@@ -669,14 +668,15 @@ invoked on that object. The following listing shows a simple example:
|
||||
----
|
||||
|
||||
The scheduler is referenced by the outer element, and each individual
|
||||
task includes the configuration of its trigger metadata. In the preceding example, that
|
||||
metadata defines a periodic trigger with a fixed delay indicating the number of
|
||||
task includes the configuration of its trigger metadata. In the preceding example,
|
||||
that metadata defines a periodic trigger with a fixed delay indicating the number of
|
||||
milliseconds to wait after each task execution has completed. Another option is
|
||||
`fixed-rate`, indicating how often the method should be run regardless of how long
|
||||
any previous execution takes. Additionally, for both `fixed-delay` and `fixed-rate` tasks, you can specify an
|
||||
'initial-delay' parameter, indicating the number of milliseconds to wait
|
||||
before the first execution of the method. For more control, you can instead provide a `cron` attribute
|
||||
to provide a xref:integration/scheduling.adoc#scheduling-cron-expression[cron expression].
|
||||
any previous execution takes. Additionally, for both `fixed-delay` and `fixed-rate`
|
||||
tasks, you can specify an 'initial-delay' parameter, indicating the number of
|
||||
milliseconds to wait before the first execution of the method. For more control,
|
||||
you can instead provide a `cron` attribute to provide a
|
||||
xref:integration/scheduling.adoc#scheduling-cron-expression[cron expression].
|
||||
The following example shows these other options:
|
||||
|
||||
[source,xml,indent=0]
|
||||
@@ -699,9 +699,8 @@ The following example shows these other options:
|
||||
All Spring cron expressions have to conform to the same format, whether you are using them in
|
||||
xref:integration/scheduling.adoc#scheduling-annotation-support-scheduled[`@Scheduled` annotations],
|
||||
xref:integration/scheduling.adoc#scheduling-task-namespace-scheduled-tasks[`task:scheduled-tasks` elements],
|
||||
or someplace else.
|
||||
A well-formed cron expression, such as `* * * * * *`, consists of six space-separated time and date
|
||||
fields, each with its own range of valid values:
|
||||
or someplace else. A well-formed cron expression, such as `* * * * * *`, consists of six
|
||||
space-separated time and date fields, each with its own range of valid values:
|
||||
|
||||
|
||||
....
|
||||
@@ -766,9 +765,10 @@ Here are some examples:
|
||||
[[macros]]
|
||||
=== Macros
|
||||
|
||||
Expressions such as `0 0 * * * *` are hard for humans to parse and are, therefore, hard to fix in case of bugs.
|
||||
To improve readability, Spring supports the following macros, which represent commonly used sequences.
|
||||
You can use these macros instead of the six-digit value, thus: `@Scheduled(cron = "@hourly")`.
|
||||
Expressions such as `0 0 * * * *` are hard for humans to parse and are, therefore,
|
||||
hard to fix in case of bugs. To improve readability, Spring supports the following
|
||||
macros, which represent commonly used sequences. You can use these macros instead
|
||||
of the six-digit value, thus: `@Scheduled(cron = "@hourly")`.
|
||||
|
||||
|===
|
||||
|Macro | Meaning
|
||||
@@ -785,8 +785,8 @@ You can use these macros instead of the six-digit value, thus: `@Scheduled(cron
|
||||
[[scheduling-quartz]]
|
||||
== Using the Quartz Scheduler
|
||||
|
||||
Quartz uses `Trigger`, `Job`, and `JobDetail` objects to realize scheduling of all kinds
|
||||
of jobs. For the basic concepts behind Quartz, see the
|
||||
Quartz uses `Trigger`, `Job`, and `JobDetail` objects to realize scheduling of all
|
||||
kinds of jobs. For the basic concepts behind Quartz, see the
|
||||
https://www.quartz-scheduler.org/[Quartz Web site]. For convenience purposes, Spring
|
||||
offers a couple of classes that simplify using Quartz within Spring-based applications.
|
||||
|
||||
@@ -794,9 +794,9 @@ offers a couple of classes that simplify using Quartz within Spring-based applic
|
||||
[[scheduling-quartz-jobdetail]]
|
||||
=== Using the `JobDetailFactoryBean`
|
||||
|
||||
Quartz `JobDetail` objects contain all the information needed to run a job. Spring provides a
|
||||
`JobDetailFactoryBean`, which provides bean-style properties for XML configuration purposes.
|
||||
Consider the following example:
|
||||
Quartz `JobDetail` objects contain all the information needed to run a job. Spring
|
||||
provides a `JobDetailFactoryBean`, which provides bean-style properties for XML
|
||||
configuration purposes. Consider the following example:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -813,9 +813,9 @@ Consider the following example:
|
||||
The job detail configuration has all the information it needs to run the job (`ExampleJob`).
|
||||
The timeout is specified in the job data map. The job data map is available through the
|
||||
`JobExecutionContext` (passed to you at execution time), but the `JobDetail` also gets
|
||||
its properties from the job data mapped to properties of the job instance. So, in the following example,
|
||||
the `ExampleJob` contains a bean property named `timeout`, and the `JobDetail`
|
||||
has it applied automatically:
|
||||
its properties from the job data mapped to properties of the job instance. So, in the
|
||||
following example, the `ExampleJob` contains a bean property named `timeout`, and the
|
||||
`JobDetail` has it applied automatically:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
@@ -908,8 +908,8 @@ NOTE: By default, jobs will run in a concurrent fashion.
|
||||
[[scheduling-quartz-cron]]
|
||||
=== Wiring up Jobs by Using Triggers and `SchedulerFactoryBean`
|
||||
|
||||
We have created job details and jobs. We have also reviewed the convenience bean that lets
|
||||
you invoke a method on a specific object. Of course, we still need to schedule the
|
||||
We have created job details and jobs. We have also reviewed the convenience bean that
|
||||
lets you invoke a method on a specific object. Of course, we still need to schedule the
|
||||
jobs themselves. This is done by using triggers and a `SchedulerFactoryBean`. Several
|
||||
triggers are available within Quartz, and Spring offers two Quartz `FactoryBean`
|
||||
implementations with convenient defaults: `CronTriggerFactoryBean` and
|
||||
@@ -940,9 +940,9 @@ The following listing uses both a `SimpleTriggerFactoryBean` and a `CronTriggerF
|
||||
</bean>
|
||||
----
|
||||
|
||||
The preceding example sets up two triggers, one running every 50 seconds with a starting delay of 10
|
||||
seconds and one running every morning at 6 AM. To finalize everything, we need to set up the
|
||||
`SchedulerFactoryBean`, as the following example shows:
|
||||
The preceding example sets up two triggers, one running every 50 seconds with a starting
|
||||
delay of 10 seconds and one running every morning at 6 AM. To finalize everything,
|
||||
we need to set up the `SchedulerFactoryBean`, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
|
||||
@@ -236,6 +236,42 @@ be matched, not only the `GET` method.
|
||||
|
||||
|
||||
|
||||
[[declaration-site-variance]]
|
||||
== Declaration-site variance
|
||||
|
||||
Dealing with generic types in Spring applications written in Kotlin may require, for some use cases, to understand
|
||||
Kotlin https://kotlinlang.org/docs/generics.html#declaration-site-variance[declaration-site variance]
|
||||
which allows to define the variance when declaring a type, which is not possible in Java which supports only use-site
|
||||
variance.
|
||||
|
||||
For example, declaring `List<Foo>` in Kotlin is conceptually equivalent to `java.util.List<? extends Foo>` because
|
||||
`kotlin.collections.List` is declared as
|
||||
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/[`interface List<out E> : kotlin.collections.Collection<E>`].
|
||||
|
||||
This needs to be taken in account by using the `out` Kotlin keyword on generic types when using Java classes,
|
||||
for example when writing a `org.springframework.core.convert.converter.Converter` from a Kotlin type to a Java type.
|
||||
|
||||
[source,kotlin,indent=0]
|
||||
----
|
||||
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<*>> {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
NOTE: Spring Framework does not leverage yet declaration-site variance type information for injecting beans,
|
||||
subscribe to https://github.com/spring-projects/spring-framework/issues/22313[spring-framework#22313] to track related
|
||||
progresses.
|
||||
|
||||
|
||||
|
||||
[[testing]]
|
||||
== Testing
|
||||
|
||||
|
||||
@@ -1023,7 +1023,7 @@ Two, create a proxy that will perform the declared RSocket exchanges:
|
||||
RSocketRequester requester = ... ;
|
||||
RSocketServiceProxyFactory factory = RSocketServiceProxyFactory.builder(requester).build();
|
||||
|
||||
RepositoryService service = factory.createClient(RadarService.class);
|
||||
RadarService service = factory.createClient(RadarService.class);
|
||||
----
|
||||
|
||||
|
||||
|
||||
@@ -3,13 +3,4 @@
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
This section covers annotations that you can use when you test Spring applications.
|
||||
It includes the following topics:
|
||||
|
||||
* xref:testing/annotations/integration-standard.adoc[Standard Annotation Support]
|
||||
* xref:testing/annotations/integration-spring.adoc[Spring Testing Annotations]
|
||||
* xref:testing/annotations/integration-junit4.adoc[Spring JUnit 4 Testing Annotations]
|
||||
* xref:testing/annotations/integration-junit-jupiter.adoc[Spring JUnit Jupiter Testing Annotations]
|
||||
* xref:testing/annotations/integration-meta.adoc[Meta-Annotation Support for Testing]
|
||||
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ Resource locations are typically XML configuration files or Groovy scripts locat
|
||||
classpath, while component classes are typically `@Configuration` classes. However,
|
||||
resource locations can also refer to files and scripts in the file system, and component
|
||||
classes can be `@Component` classes, `@Service` classes, and so on. See
|
||||
xref:testing/testcontext-framework/ctx-management/javaconfig.adoc#testcontext-ctx-management-javaconfig-component-classes[null] for further details.
|
||||
xref:testing/testcontext-framework/ctx-management/javaconfig.adoc#testcontext-ctx-management-javaconfig-component-classes[Component Classes] for further details.
|
||||
|
||||
The following example shows a `@ContextConfiguration` annotation that refers to an XML
|
||||
file:
|
||||
|
||||
@@ -14,7 +14,7 @@ following features.
|
||||
testing annotations -- as long as the tests are run using a JUnit Platform
|
||||
`TestEngine` that is registered for the current project.
|
||||
* Build-time AOT processing: each unique test `ApplicationContext` in the current project
|
||||
will be xref:core/aot.adoc#refresh[refreshed for AOT processing].
|
||||
will be xref:core/aot.adoc#aot.refresh[refreshed for AOT processing].
|
||||
* Runtime AOT support: when executing in AOT runtime mode, a Spring integration test will
|
||||
use an AOT-optimized `ApplicationContext` that participates transparently with the
|
||||
xref:testing/testcontext-framework/ctx-management/caching.adoc[context cache].
|
||||
@@ -35,7 +35,7 @@ the following options.
|
||||
via {api-spring-framework}/context/annotation/ImportRuntimeHints.html[`@ImportRuntimeHints`].
|
||||
* Annotate a test class with {api-spring-framework}/aot/hint/annotation/Reflective.html[`@Reflective`] or
|
||||
{api-spring-framework}/aot/hint/annotation/RegisterReflectionForBinding.html[`@RegisterReflectionForBinding`].
|
||||
* See xref:core/aot.adoc#hints[Runtime Hints] for details on Spring's core runtime hints
|
||||
* See xref:core/aot.adoc#aot.hints[Runtime Hints] for details on Spring's core runtime hints
|
||||
and annotation support.
|
||||
|
||||
[TIP]
|
||||
|
||||
+2
-2
@@ -35,8 +35,8 @@ in which the test class is defined. A path starting with a slash is treated as a
|
||||
absolute classpath resource (for example: `"/org/example/test.xml"`). A path that
|
||||
references a URL (for example, a path prefixed with `classpath:`, `file:`, or `http:`) is
|
||||
loaded by using the specified resource protocol. Resource location wildcards (such as
|
||||
`**/*.properties`) are not permitted: Each location must evaluate to exactly one
|
||||
`.properties` or `.xml` resource.
|
||||
`{asterisk}{asterisk}/{asterisk}.properties`) are not permitted: Each location must
|
||||
evaluate to exactly one `.properties` or `.xml` resource.
|
||||
|
||||
The following example uses a test properties file:
|
||||
|
||||
|
||||
@@ -75,6 +75,35 @@ To learn more from the source or to make advanced customizations, see:
|
||||
|
||||
|
||||
|
||||
[[webflux-cors-credentialed-requests]]
|
||||
== Credentialed Requests
|
||||
[.small]#xref:web/webmvc-cors.adoc#mvc-cors-credentialed-requests[See equivalent in the Servlet stack]#
|
||||
|
||||
Using CORS with credentialed requests requires enabling `allowedCredentials`. Be aware that
|
||||
this option establishes a high level of trust with the configured domains and also increases
|
||||
the surface of attack of the web application by exposing sensitive user-specific information
|
||||
such as cookies and CSRF tokens.
|
||||
|
||||
Enabling credentials also impacts how the configured `"*"` CORS wildcards are processed:
|
||||
|
||||
* Wildcards are not authorized in `allowOrigins`, but alternatively
|
||||
the `allowOriginPatterns` property may be used to match to a dynamic set of origins.
|
||||
* When set on `allowedHeaders` or `allowedMethods`, the `Access-Control-Allow-Headers`
|
||||
and `Access-Control-Allow-Methods` response headers are handled by copying the related
|
||||
headers and method specified in the CORS preflight request.
|
||||
* When set on `exposedHeaders`, `Access-Control-Expose-Headers` response header is set
|
||||
either to the configured list of headers or to the wildcard character. While the CORS spec
|
||||
does not allow the wildcard character when `Access-Control-Allow-Credentials` is set to
|
||||
`true`, most browsers support it and the response headers are not all available during the
|
||||
CORS processing, so as a consequence the wildcard character is the header value used when
|
||||
specified regardless of the value of the `allowCredentials` property.
|
||||
|
||||
WARNING: While such wildcard configuration can be handy, it is recommended when possible to configure
|
||||
a finite set of values instead to provide a higher level of security.
|
||||
|
||||
|
||||
|
||||
|
||||
[[webflux-cors-controller]]
|
||||
== `@CrossOrigin`
|
||||
[.small]#xref:web/webmvc-cors.adoc#mvc-cors-controller[See equivalent in the Servlet stack]#
|
||||
|
||||
+4
-1
@@ -166,4 +166,7 @@ By default, any argument that is not a simple value type (as determined by
|
||||
and is not resolved by any other argument resolver is treated as if it were annotated
|
||||
with `@ModelAttribute`.
|
||||
|
||||
|
||||
WARNING: When compiling to a native image with GraalVM, the implicit `@ModelAttribute`
|
||||
support described above does not allow proper ahead-of-time inference of related data
|
||||
binding reflection hints. As a consequence, it is recommended to explicitly annotate
|
||||
method parameters with `@ModelAttribute` for use in a GraalVM native image.
|
||||
|
||||
@@ -189,6 +189,9 @@ lets applications use the Servlet API directly if they need to. Spring WebFlux
|
||||
relies on Servlet non-blocking I/O and uses the Servlet API behind a low-level
|
||||
adapter. It is not exposed for direct use.
|
||||
|
||||
NOTE: It is strongly advised not to map Servlet filters or directly manipulate the Servlet API in the context of a WebFlux application.
|
||||
For the reasons listed above, mixing blocking I/O and non-blocking I/O in the same context will cause runtime issues.
|
||||
|
||||
For Undertow, Spring WebFlux uses Undertow APIs directly without the Servlet API.
|
||||
|
||||
|
||||
@@ -197,9 +200,9 @@ For Undertow, Spring WebFlux uses Undertow APIs directly without the Servlet API
|
||||
== Performance
|
||||
|
||||
Performance has many characteristics and meanings. Reactive and non-blocking generally
|
||||
do not make applications run faster. They can, in some cases, (for example, if using the
|
||||
`WebClient` to run remote calls in parallel). On the whole, it requires more work to do
|
||||
things the non-blocking way and that can slightly increase the required processing time.
|
||||
do not make applications run faster. They can in some cases – for example, if using the
|
||||
`WebClient` to run remote calls in parallel. However, it requires more work to do
|
||||
things the non-blocking way, and that can slightly increase the required processing time.
|
||||
|
||||
The key expected benefit of reactive and non-blocking is the ability to scale with a small,
|
||||
fixed number of threads and less memory. That makes applications more resilient under load,
|
||||
@@ -221,10 +224,10 @@ block the current thread, (for example, for remote calls). For this reason, serv
|
||||
use a large thread pool to absorb potential blocking during request handling.
|
||||
|
||||
In Spring WebFlux (and non-blocking servers in general), it is assumed that applications
|
||||
do not block. Therefore, non-blocking servers use a small, fixed-size thread pool
|
||||
do not block. Therefore, non-blocking servers use a small, fixed-size thread pool
|
||||
(event loop workers) to handle requests.
|
||||
|
||||
TIP: "`To scale`" and "`small number of threads`" may sound contradictory but to never block the
|
||||
TIP: "`To scale`" and "`small number of threads`" may sound contradictory, but to never block the
|
||||
current thread (and rely on callbacks instead) means that you do not need extra threads, as
|
||||
there are no blocking calls to absorb.
|
||||
|
||||
@@ -250,7 +253,7 @@ application code within that pipeline is never invoked concurrently.
|
||||
|
||||
What threads should you expect to see on a server running with Spring WebFlux?
|
||||
|
||||
* On a "`vanilla`" Spring WebFlux server (for example, no data access nor other optional
|
||||
* On a "`vanilla`" Spring WebFlux server (for example, no data access or other optional
|
||||
dependencies), you can expect one thread for the server and several others for request
|
||||
processing (typically as many as the number of CPU cores). Servlet containers, however,
|
||||
may start with more threads (for example, 10 on Tomcat), in support of both servlet (blocking) I/O
|
||||
|
||||
@@ -25,6 +25,35 @@ powerful workarounds based on IFRAME or JSONP.
|
||||
|
||||
|
||||
|
||||
[[mvc-cors-credentialed-requests]]
|
||||
== Credentialed Requests
|
||||
[.small]#xref:web/webflux-cors.adoc#webflux-cors-credentialed-requests[See equivalent in the Reactive stack]#
|
||||
|
||||
Using CORS with credentialed requests requires enabling `allowedCredentials`. Be aware that
|
||||
this option establishes a high level of trust with the configured domains and also increases
|
||||
the surface of attack of the web application by exposing sensitive user-specific information
|
||||
such as cookies and CSRF tokens.
|
||||
|
||||
Enabling credentials also impacts how the configured `"*"` CORS wildcards are processed:
|
||||
|
||||
* Wildcards are not authorized in `allowOrigins`, but alternatively
|
||||
the `allowOriginPatterns` property may be used to match to a dynamic set of origins.
|
||||
* When set on `allowedHeaders` or `allowedMethods`, the `Access-Control-Allow-Headers`
|
||||
and `Access-Control-Allow-Methods` response headers are handled by copying the related
|
||||
headers and method specified in the CORS preflight request.
|
||||
* When set on `exposedHeaders`, `Access-Control-Expose-Headers` response header is set
|
||||
either to the configured list of headers or to the wildcard character. While the CORS spec
|
||||
does not allow the wildcard character when `Access-Control-Allow-Credentials` is set to
|
||||
`true`, most browsers support it and the response headers are not all available during the
|
||||
CORS processing, so as a consequence the wildcard character is the header value used when
|
||||
specified regardless of the value of the `allowCredentials` property.
|
||||
|
||||
WARNING: While such wildcard configuration can be handy, it is recommended when possible to configure
|
||||
a finite set of values instead to provide a higher level of security.
|
||||
|
||||
|
||||
|
||||
|
||||
[[mvc-cors-processing]]
|
||||
== Processing
|
||||
[.small]#xref:web/webflux-cors.adoc#webflux-cors-processing[See equivalent in the Reactive stack]#
|
||||
|
||||
@@ -69,9 +69,11 @@ written to the response and computing an MD5 hash from it. The next time a clien
|
||||
it does the same, but it also compares the computed value against the `If-None-Match`
|
||||
request header and, if the two are equal, returns a 304 (NOT_MODIFIED).
|
||||
|
||||
This strategy saves network bandwidth but not CPU, as the full response must be computed
|
||||
for each request. Other strategies at the controller level, described earlier, can avoid
|
||||
the computation. See xref:web/webmvc/mvc-caching.adoc[HTTP Caching].
|
||||
This strategy saves network bandwidth but not CPU, as the full response must be computed for each request.
|
||||
State-changing HTTP methods and other HTTP conditional request headers such as `If-Match` and
|
||||
`If-Unmodified-Since` are outside the scope of this filter. Other strategies at the controller level
|
||||
can avoid the computation and have a broader support for HTTP conditional requests.
|
||||
See xref:web/webmvc/mvc-caching.adoc[HTTP Caching].
|
||||
|
||||
This filter has a `writeWeakETag` parameter that configures the filter to write weak ETags
|
||||
similar to the following: `W/"02a2d595e6ed9a0b24f027f2b63b134d6"` (as defined in
|
||||
|
||||
@@ -52,7 +52,7 @@ The following example shows how to achieve the same configuration in XML:
|
||||
</mvc:interceptors>
|
||||
----
|
||||
|
||||
NOTE: Mapped interceptors are not ideally suited as a security layer due to the potential
|
||||
NOTE: Interceptors are not ideally suited as a security layer due to the potential
|
||||
for a mismatch with annotated controller path matching, which can also match trailing
|
||||
slashes and path extensions transparently, along with other path matching options. Many
|
||||
of these options have been deprecated but the potential for a mismatch remains.
|
||||
@@ -61,6 +61,10 @@ https://docs.spring.io/spring-security/reference/servlet/integrations/mvc.html#m
|
||||
to align with Spring MVC path matching and also has a security firewall that blocks many
|
||||
unwanted characters in URL paths.
|
||||
|
||||
|
||||
|
||||
|
||||
NOTE: The XML config declares interceptors as `MappedInterceptor` beans, and those are in
|
||||
turn detected by any `HandlerMapping` bean, including those from other frameworks.
|
||||
By contrast, the Java config passes interceptors only to the `HandlerMapping` beans it manages.
|
||||
To re-use the same interceptors across Spring MVC and other framework `HandlerMapping`
|
||||
beans with the MVC Java config, either declare `MappedInterceptor` beans (and don't
|
||||
manually add them in the Java config), or configure the same interceptors in both
|
||||
the Java config and in other `HandlerMapping` beans.
|
||||
|
||||
+4
-1
@@ -216,4 +216,7 @@ By default, any argument that is not a simple value type (as determined by
|
||||
and is not resolved by any other argument resolver is treated as if it were annotated
|
||||
with `@ModelAttribute`.
|
||||
|
||||
|
||||
WARNING: When compiling to a native image with GraalVM, the implicit `@ModelAttribute`
|
||||
support described above does not allow proper ahead-of-time inference of related data
|
||||
binding reflection hints. As a consequence, it is recommended to explicitly annotate
|
||||
method parameters with `@ModelAttribute` for use in a GraalVM native image.
|
||||
|
||||
@@ -220,7 +220,7 @@ one of the following depending on whether use of parsed `PathPattern` is enabled
|
||||
* {api-spring-framework}/web/util/pattern/PathPattern.html#SPECIFICITY_COMPARATOR[`PathPattern.SPECIFICITY_COMPARATOR`]
|
||||
* {api-spring-framework}/util/AntPathMatcher.html#getPatternComparator-java.lang.String-[`AntPathMatcher.getPatternComparator(String path)`]
|
||||
|
||||
Both help to sort patterns with more specific ones on top. A pattern is less specific if
|
||||
Both help to sort patterns with more specific ones on top. A pattern is more specific if
|
||||
it has a lower count of URI variables (counted as 1), single wildcards (counted as 1),
|
||||
and double wildcards (counted as 2). Given an equal score, the longer pattern is chosen.
|
||||
Given the same score and length, the pattern with more URI variables than wildcards is
|
||||
|
||||
@@ -229,34 +229,27 @@ Java initialization API. The following example shows how to do so:
|
||||
|
||||
|
||||
[[websocket-server-runtime-configuration]]
|
||||
== Server Configuration
|
||||
== Configuring the Server
|
||||
[.small]#xref:web/webflux-websocket.adoc#webflux-websocket-server-config[See equivalent in the Reactive stack]#
|
||||
|
||||
Each underlying WebSocket engine exposes configuration properties that control
|
||||
runtime characteristics, such as the size of message buffer sizes, idle timeout,
|
||||
and others.
|
||||
You can configure of the underlying WebSocket server such as input message buffer size,
|
||||
idle timeout, and more.
|
||||
|
||||
For Tomcat, WildFly, and GlassFish, you can add a `ServletServerContainerFactoryBean` to your
|
||||
WebSocket Java config, as the following example shows:
|
||||
For Jakarta WebSocket servers, you can add a `ServletServerContainerFactoryBean` to your
|
||||
Java configuration. For example:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSocket
|
||||
public class WebSocketConfig implements WebSocketConfigurer {
|
||||
|
||||
@Bean
|
||||
public ServletServerContainerFactoryBean createWebSocketContainer() {
|
||||
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
|
||||
container.setMaxTextMessageBufferSize(8192);
|
||||
container.setMaxBinaryMessageBufferSize(8192);
|
||||
return container;
|
||||
}
|
||||
|
||||
}
|
||||
@Bean
|
||||
public ServletServerContainerFactoryBean createWebSocketContainer() {
|
||||
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
|
||||
container.setMaxTextMessageBufferSize(8192);
|
||||
container.setMaxBinaryMessageBufferSize(8192);
|
||||
return container;
|
||||
}
|
||||
----
|
||||
|
||||
The following example shows the XML configuration equivalent of the preceding example:
|
||||
Or to your XML configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes,attributes"]
|
||||
----
|
||||
@@ -277,12 +270,11 @@ The following example shows the XML configuration equivalent of the preceding ex
|
||||
</beans>
|
||||
----
|
||||
|
||||
NOTE: For client-side WebSocket configuration, you should use `WebSocketContainerFactoryBean`
|
||||
(XML) or `ContainerProvider.getWebSocketContainer()` (Java configuration).
|
||||
NOTE: For client Jakarta WebSocket configuration, use
|
||||
ContainerProvider.getWebSocketContainer() in Java configuration, or
|
||||
`WebSocketContainerFactoryBean` in XML.
|
||||
|
||||
For Jetty, you need to supply a pre-configured Jetty `WebSocketServerFactory` and plug
|
||||
that into Spring's `DefaultHandshakeHandler` through your WebSocket Java config.
|
||||
The following example shows how to do so:
|
||||
For Jetty, you can supply a `Consumer` callback to configure the WebSocket server:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -298,11 +290,9 @@ The following example shows how to do so:
|
||||
|
||||
@Bean
|
||||
public DefaultHandshakeHandler handshakeHandler() {
|
||||
|
||||
WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
|
||||
policy.setInputBufferSize(8192);
|
||||
policy.setIdleTimeout(600000);
|
||||
|
||||
return new DefaultHandshakeHandler(
|
||||
new JettyRequestUpgradeStrategy(new WebSocketServerFactory(policy)));
|
||||
}
|
||||
@@ -349,6 +339,10 @@ The following example shows the XML configuration equivalent of the preceding ex
|
||||
</beans>
|
||||
----
|
||||
|
||||
TIP: When using STOMP over WebSocket, you will also need to configure
|
||||
xref:web/websocket/stomp/server-config.adoc[STOMP WebSocket transport]
|
||||
properties.
|
||||
|
||||
|
||||
|
||||
[[websocket-server-allowed-origins]]
|
||||
|
||||
+3
-3
@@ -103,9 +103,9 @@ You can also use the WebSocket transport configuration shown earlier to configur
|
||||
maximum allowed size for incoming STOMP messages. In theory, a WebSocket
|
||||
message can be almost unlimited in size. In practice, WebSocket servers impose
|
||||
limits -- for example, 8K on Tomcat and 64K on Jetty. For this reason, STOMP clients
|
||||
(such as the JavaScript https://github.com/JSteunou/webstomp-client[webstomp-client]
|
||||
and others) split larger STOMP messages at 16K boundaries and send them as multiple
|
||||
WebSocket messages, which requires the server to buffer and re-assemble.
|
||||
such as https://github.com/stomp-js/stompjs[`stomp-js/stompjs`] and others split larger
|
||||
STOMP messages at 16K boundaries and send them as multiple WebSocket messages,
|
||||
which requires the server to buffer and re-assemble.
|
||||
|
||||
Spring's STOMP-over-WebSocket support does this ,so applications can configure the
|
||||
maximum size for STOMP messages irrespective of WebSocket server-specific message
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
STOMP over WebSocket support is available in the `spring-messaging` and
|
||||
`spring-websocket` modules. Once you have those dependencies, you can expose a STOMP
|
||||
endpoint over WebSocket with xref:web/websocket/fallback.adoc[SockJS Fallback], as the following example shows:
|
||||
endpoint over WebSocket, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -16,7 +16,7 @@ endpoint over WebSocket with xref:web/websocket/fallback.adoc[SockJS Fallback],
|
||||
|
||||
@Override
|
||||
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
||||
registry.addEndpoint("/portfolio").withSockJS(); // <1>
|
||||
registry.addEndpoint("/portfolio"); // <1>
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -49,9 +49,7 @@ The following example shows the XML configuration equivalent of the preceding ex
|
||||
https://www.springframework.org/schema/websocket/spring-websocket.xsd">
|
||||
|
||||
<websocket:message-broker application-destination-prefix="/app">
|
||||
<websocket:stomp-endpoint path="/portfolio">
|
||||
<websocket:sockjs/>
|
||||
</websocket:stomp-endpoint>
|
||||
<websocket:stomp-endpoint path="/portfolio" />
|
||||
<websocket:simple-broker prefix="/topic, /queue"/>
|
||||
</websocket:message-broker>
|
||||
|
||||
@@ -64,34 +62,27 @@ messaging (that is, many subscribers versus one consumer). When you use an exter
|
||||
check the STOMP page of the broker to understand what kind of STOMP destinations and
|
||||
prefixes it supports.
|
||||
|
||||
To connect from a browser, for SockJS, you can use the
|
||||
https://github.com/sockjs/sockjs-client[`sockjs-client`]. For STOMP, many applications have
|
||||
used the https://github.com/jmesnil/stomp-websocket[jmesnil/stomp-websocket] library
|
||||
(also known as stomp.js), which is feature-complete and has been used in production for
|
||||
years but is no longer maintained. At present the
|
||||
https://github.com/JSteunou/webstomp-client[JSteunou/webstomp-client] is the most
|
||||
actively maintained and evolving successor of that library. The following example code
|
||||
is based on it:
|
||||
To connect from a browser, for STOMP, you can use
|
||||
https://github.com/stomp-js/stompjs[`stomp-js/stompjs`] which is the most
|
||||
actively maintained JavaScript library.
|
||||
|
||||
The following example code is based on it:
|
||||
|
||||
[source,javascript,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
var socket = new SockJS("/spring-websocket-portfolio/portfolio");
|
||||
var stompClient = webstomp.over(socket);
|
||||
|
||||
stompClient.connect({}, function(frame) {
|
||||
}
|
||||
const stompClient = new StompJs.Client({
|
||||
brokerURL: 'ws://domain.com/portfolio',
|
||||
onConnect: () => {
|
||||
// ...
|
||||
}
|
||||
});
|
||||
----
|
||||
|
||||
Alternatively, if you connect through WebSocket (without SockJS), you can use the following code:
|
||||
|
||||
[source,javascript,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
var socket = new WebSocket("/spring-websocket-portfolio/portfolio");
|
||||
var stompClient = Stomp.over(socket);
|
||||
|
||||
stompClient.connect({}, function(frame) {
|
||||
}
|
||||
----
|
||||
Alternatively, if you connect through SockJS, you can enable the
|
||||
xref:web/websocket/fallback.adoc[SockJS Fallback] on server-side with
|
||||
`registry.addEndpoint("/portfolio").withSockJS()` and on JavaScript side,
|
||||
by following
|
||||
https://stomp-js.github.io/guide/stompjs/rx-stomp/using-stomp-with-sockjs.html[those instructions].
|
||||
|
||||
Note that `stompClient` in the preceding example does not need to specify `login`
|
||||
and `passcode` headers. Even if it did, they would be ignored (or, rather,
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
[[websocket-stomp-server-config]]
|
||||
= WebSocket Server
|
||||
= WebSocket Transport
|
||||
|
||||
To configure the underlying WebSocket server, the information in
|
||||
xref:web/websocket/server.adoc#websocket-server-runtime-configuration[Server Configuration] applies. For Jetty, however you need to set
|
||||
the `HandshakeHandler` and `WebSocketPolicy` through the `StompEndpointRegistry`:
|
||||
This section explains how to configure the underlying WebSocket server transport.
|
||||
|
||||
For Jakarta WebSocket servers, add a `ServletServerContainerFactoryBean` to your
|
||||
configuration. For examples, see
|
||||
xref:web/websocket/server.adoc#websocket-server-runtime-configuration[Configuring the Server]
|
||||
under the WebSocket section.
|
||||
|
||||
For Jetty WebSocket servers, customize the `JettyRequestUpgradeStrategy` as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -29,5 +34,20 @@ the `HandshakeHandler` and `WebSocketPolicy` through the `StompEndpointRegistry`
|
||||
}
|
||||
----
|
||||
|
||||
In addition to WebSocket server properties, there are also STOMP WebSocket transport properties
|
||||
to customize as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSocketMessageBroker
|
||||
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureWebSocketTransport(WebSocketTransportRegistration registry) {
|
||||
registry.setMessageSizeLimit(4 * 8192);
|
||||
registry.setTimeToFirstMessage(30000);
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
@@ -8,30 +8,30 @@ javaPlatform {
|
||||
|
||||
dependencies {
|
||||
api(platform("com.fasterxml.jackson:jackson-bom:2.14.3"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.10.8"))
|
||||
api(platform("io.netty:netty-bom:4.1.93.Final"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.10.13"))
|
||||
api(platform("io.netty:netty-bom:4.1.104.Final"))
|
||||
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
|
||||
api(platform("io.projectreactor:reactor-bom:2022.0.8"))
|
||||
api(platform("io.projectreactor:reactor-bom:2022.0.15"))
|
||||
api(platform("io.rsocket:rsocket-bom:1.1.3"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.12"))
|
||||
api(platform("org.apache.logging.log4j:log4j-bom:2.20.0"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:11.0.15"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.17"))
|
||||
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:11.0.19"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.4"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.4.0"))
|
||||
api(platform("org.junit:junit-bom:5.9.3"))
|
||||
api(platform("org.mockito:mockito-bom:5.3.1"))
|
||||
api(platform("org.mockito:mockito-bom:5.8.0"))
|
||||
|
||||
constraints {
|
||||
api("com.fasterxml:aalto-xml:1.3.2")
|
||||
api("com.fasterxml.woodstox:woodstox-core:6.5.1")
|
||||
api("com.github.ben-manes.caffeine:caffeine:3.1.6")
|
||||
api("com.github.librepdf:openpdf:1.3.30")
|
||||
api("com.github.ben-manes.caffeine:caffeine:3.1.8")
|
||||
api("com.github.librepdf:openpdf:1.3.36")
|
||||
api("com.google.code.findbugs:findbugs:3.0.1")
|
||||
api("com.google.code.findbugs:jsr305:3.0.2")
|
||||
api("com.google.code.gson:gson:2.10.1")
|
||||
api("com.google.protobuf:protobuf-java-util:3.23.2")
|
||||
api("com.googlecode.protobuf-java-format:protobuf-java-format:1.4")
|
||||
api("com.h2database:h2:2.1.214")
|
||||
api("com.h2database:h2:2.2.224")
|
||||
api("com.jayway.jsonpath:json-path:2.8.0")
|
||||
api("com.rometools:rome:1.19.0")
|
||||
api("com.squareup.okhttp3:mockwebserver:3.14.9")
|
||||
@@ -43,21 +43,20 @@ dependencies {
|
||||
api("com.sun.xml.bind:jaxb-xjc:3.0.2")
|
||||
api("com.thoughtworks.qdox:qdox:2.0.3")
|
||||
api("com.thoughtworks.xstream:xstream:1.4.20")
|
||||
api("commons-io:commons-io:2.11.0")
|
||||
api("commons-io:commons-io:2.15.0")
|
||||
api("de.bechte.junit:junit-hierarchicalcontextrunner:4.12.2")
|
||||
api("info.picocli:picocli:4.7.4")
|
||||
api("io.micrometer:context-propagation:1.0.3")
|
||||
api("io.micrometer:context-propagation:1.0.6")
|
||||
api("io.mockk:mockk:1.13.4")
|
||||
api("io.projectreactor.netty:reactor-netty5-http:2.0.0-M3")
|
||||
api("io.projectreactor.tools:blockhound:1.0.8.RELEASE")
|
||||
api("io.r2dbc:r2dbc-h2:1.0.0.RELEASE")
|
||||
api("io.r2dbc:r2dbc-spi-test:1.0.0.RELEASE")
|
||||
api("io.r2dbc:r2dbc-spi:1.0.0.RELEASE")
|
||||
api("io.reactivex.rxjava3:rxjava:3.1.6")
|
||||
api("io.reactivex.rxjava3:rxjava:3.1.8")
|
||||
api("io.smallrye.reactive:mutiny:1.9.0")
|
||||
api("io.undertow:undertow-core:2.3.7.Final")
|
||||
api("io.undertow:undertow-servlet:2.3.7.Final")
|
||||
api("io.undertow:undertow-websockets-jsr:2.3.7.Final")
|
||||
api("io.undertow:undertow-core:2.3.10.Final")
|
||||
api("io.undertow:undertow-servlet:2.3.10.Final")
|
||||
api("io.undertow:undertow-websockets-jsr:2.3.10.Final")
|
||||
api("io.vavr:vavr:0.10.4")
|
||||
api("jakarta.activation:jakarta.activation-api:2.0.1")
|
||||
api("jakarta.annotation:jakarta.annotation-api:2.0.0")
|
||||
@@ -82,6 +81,7 @@ dependencies {
|
||||
api("jakarta.websocket:jakarta.websocket-api:2.1.0")
|
||||
api("jakarta.websocket:jakarta.websocket-client-api:2.1.0")
|
||||
api("jakarta.xml.bind:jakarta.xml.bind-api:3.0.1")
|
||||
api("javax.annotation:javax.annotation-api:1.3.2")
|
||||
api("javax.cache:cache-api:1.1.1")
|
||||
api("javax.money:money-api:1.1")
|
||||
api("jaxen:jaxen:1.2.0")
|
||||
@@ -89,46 +89,44 @@ dependencies {
|
||||
api("net.sf.jopt-simple:jopt-simple:5.0.4")
|
||||
api("net.sourceforge.htmlunit:htmlunit:2.70.0")
|
||||
api("org.apache-extras.beanshell:bsh:2.0b6")
|
||||
api("org.apache.activemq:activemq-broker:5.17.4")
|
||||
api("org.apache.activemq:activemq-kahadb-store:5.17.4")
|
||||
api("org.apache.activemq:activemq-stomp:5.17.4")
|
||||
api("org.apache.activemq:activemq-broker:5.17.6")
|
||||
api("org.apache.activemq:activemq-kahadb-store:5.17.6")
|
||||
api("org.apache.activemq:activemq-stomp:5.17.6")
|
||||
api("org.apache.commons:commons-pool2:2.9.0")
|
||||
api("org.apache.derby:derby:10.16.1.1")
|
||||
api("org.apache.derby:derbyclient:10.16.1.1")
|
||||
api("org.apache.derby:derbytools:10.16.1.1")
|
||||
api("org.apache.httpcomponents.client5:httpclient5:5.2.1")
|
||||
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.2.1")
|
||||
api("org.apache.poi:poi-ooxml:5.2.3")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.9")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.9")
|
||||
api("org.apache.tomcat:tomcat-util:10.1.8")
|
||||
api("org.apache.tomcat:tomcat-websocket:10.1.8")
|
||||
api("org.aspectj:aspectjrt:1.9.19")
|
||||
api("org.aspectj:aspectjtools:1.9.19")
|
||||
api("org.aspectj:aspectjweaver:1.9.19")
|
||||
api("org.apache.httpcomponents.client5:httpclient5:5.2.3")
|
||||
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.2.4")
|
||||
api("org.apache.poi:poi-ooxml:5.2.5")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.15")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.15")
|
||||
api("org.apache.tomcat:tomcat-util:10.1.15")
|
||||
api("org.apache.tomcat:tomcat-websocket:10.1.15")
|
||||
api("org.aspectj:aspectjrt:1.9.20.1")
|
||||
api("org.aspectj:aspectjtools:1.9.20.1")
|
||||
api("org.aspectj:aspectjweaver:1.9.20.1")
|
||||
api("org.assertj:assertj-core:3.24.2")
|
||||
api("org.awaitility:awaitility:4.2.0")
|
||||
api("org.bouncycastle:bcpkix-jdk18on:1.72")
|
||||
api("org.codehaus.jettison:jettison:1.5.4")
|
||||
api("org.dom4j:dom4j:2.1.4")
|
||||
api("org.eclipse.jetty:jetty-reactive-httpclient:3.0.8")
|
||||
api("org.eclipse.persistence:org.eclipse.persistence.jpa:3.0.3")
|
||||
api("org.eclipse.jetty:jetty-reactive-httpclient:3.0.10")
|
||||
api("org.eclipse.persistence:org.eclipse.persistence.jpa:3.0.4")
|
||||
api("org.eclipse:yasson:2.0.4")
|
||||
api("org.ehcache:ehcache:3.10.8")
|
||||
api("org.ehcache:jcache:1.0.1")
|
||||
api("org.freemarker:freemarker:2.3.32")
|
||||
// Substitute for "javax.management:jmxremote_optional:1.0.1_04" which
|
||||
// is not available on Maven Central
|
||||
api("org.glassfish.external:opendmk_jmxremote_optional_jar:1.0-b01-ea")
|
||||
api("org.glassfish.tyrus:tyrus-container-servlet:2.1.3")
|
||||
api("org.glassfish:jakarta.el:4.0.2")
|
||||
api("org.glassfish.tyrus:tyrus-container-servlet:2.1.3")
|
||||
api("org.graalvm.sdk:graal-sdk:22.3.1")
|
||||
api("org.hamcrest:hamcrest:2.2")
|
||||
api("org.hibernate:hibernate-core-jakarta:5.6.15.Final")
|
||||
api("org.hibernate:hibernate-validator:7.0.5.Final")
|
||||
api("org.hsqldb:hsqldb:2.7.1")
|
||||
api("org.hsqldb:hsqldb:2.7.2")
|
||||
api("org.javamoney:moneta:1.4.2")
|
||||
api("org.jruby:jruby:9.4.3.0")
|
||||
api("org.jruby:jruby:9.4.5.0")
|
||||
api("org.junit.support:testng-engine:1.0.4")
|
||||
api("org.mozilla:rhino:1.7.14")
|
||||
api("org.ogce:xpp3:1.1.6")
|
||||
@@ -137,10 +135,10 @@ dependencies {
|
||||
api("org.seleniumhq.selenium:htmlunit-driver:2.70.0")
|
||||
api("org.seleniumhq.selenium:selenium-java:3.141.59")
|
||||
api("org.skyscreamer:jsonassert:1.5.1")
|
||||
api("org.slf4j:slf4j-api:2.0.7")
|
||||
api("org.slf4j:slf4j-api:2.0.11")
|
||||
api("org.testng:testng:7.8.0")
|
||||
api("org.webjars:underscorejs:1.8.3")
|
||||
api("org.webjars:webjars-locator-core:0.52")
|
||||
api("org.webjars:webjars-locator-core:0.55")
|
||||
api("org.xmlunit:xmlunit-assertj:2.9.1")
|
||||
api("org.xmlunit:xmlunit-matchers:2.9.1")
|
||||
api("org.yaml:snakeyaml:1.33")
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
version=6.0.10-SNAPSHOT
|
||||
version=6.0.16
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+2
-1
@@ -1,6 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -83,7 +83,8 @@ done
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
@@ -130,10 +131,13 @@ location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
@@ -141,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
@@ -149,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
@@ -198,11 +202,11 @@ fi
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_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.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
|
||||
+8
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,11 +21,15 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.assertj.core.api.InstanceOfAssertFactories;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.expression.MethodExecutor;
|
||||
import org.springframework.expression.TypedValue;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class Spr7538Tests {
|
||||
|
||||
@@ -49,8 +53,9 @@ class Spr7538Tests {
|
||||
ReflectiveMethodResolver resolver = new ReflectiveMethodResolver();
|
||||
MethodExecutor executor = resolver.resolve(context, target, "checkCompleteness", argumentTypes);
|
||||
|
||||
Object result = executor.execute(context, target, arguments);
|
||||
System.out.println("Result: " + result);
|
||||
TypedValue typedValue = executor.execute(context, target, arguments);
|
||||
assertThat(typedValue.getValue()).asInstanceOf(InstanceOfAssertFactories.BOOLEAN).isTrue();
|
||||
assertThat(typedValue.getTypeDescriptor().getType()).isEqualTo(Boolean.class);
|
||||
}
|
||||
|
||||
static class AlwaysTrueReleaseStrategy {
|
||||
|
||||
+5
-5
@@ -60,8 +60,8 @@ class ScheduledAndTransactionalAnnotationIntegrationTests {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
|
||||
ctx.register(Config.class, JdkProxyTxConfig.class, RepoConfigA.class);
|
||||
assertThatExceptionOfType(BeanCreationException.class)
|
||||
.isThrownBy(ctx::refresh)
|
||||
.withCauseInstanceOf(IllegalStateException.class);
|
||||
.isThrownBy(ctx::refresh)
|
||||
.withCauseInstanceOf(IllegalStateException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -70,7 +70,7 @@ class ScheduledAndTransactionalAnnotationIntegrationTests {
|
||||
ctx.register(Config.class, SubclassProxyTxConfig.class, RepoConfigA.class);
|
||||
ctx.refresh();
|
||||
|
||||
Thread.sleep(100); // allow @Scheduled method to be called several times
|
||||
Thread.sleep(200); // allow @Scheduled method to be called several times
|
||||
|
||||
MyRepository repository = ctx.getBean(MyRepository.class);
|
||||
CallCountingTransactionManager txManager = ctx.getBean(CallCountingTransactionManager.class);
|
||||
@@ -85,7 +85,7 @@ class ScheduledAndTransactionalAnnotationIntegrationTests {
|
||||
ctx.register(Config.class, JdkProxyTxConfig.class, RepoConfigB.class);
|
||||
ctx.refresh();
|
||||
|
||||
Thread.sleep(100); // allow @Scheduled method to be called several times
|
||||
Thread.sleep(200); // allow @Scheduled method to be called several times
|
||||
|
||||
MyRepositoryWithScheduledMethod repository = ctx.getBean(MyRepositoryWithScheduledMethod.class);
|
||||
CallCountingTransactionManager txManager = ctx.getBean(CallCountingTransactionManager.class);
|
||||
@@ -100,7 +100,7 @@ class ScheduledAndTransactionalAnnotationIntegrationTests {
|
||||
ctx.register(AspectConfig.class, MyRepositoryWithScheduledMethodImpl.class);
|
||||
ctx.refresh();
|
||||
|
||||
Thread.sleep(100); // allow @Scheduled method to be called several times
|
||||
Thread.sleep(200); // allow @Scheduled method to be called several times
|
||||
|
||||
MyRepositoryWithScheduledMethod repository = ctx.getBean(MyRepositoryWithScheduledMethod.class);
|
||||
assertThat(AopUtils.isCglibProxy(repository)).isTrue();
|
||||
|
||||
+2
-1
@@ -47,7 +47,8 @@ rootProject.children.each {project ->
|
||||
settings.gradle.projectsLoaded {
|
||||
gradleEnterprise {
|
||||
buildScan {
|
||||
File buildDir = settings.gradle.rootProject.getBuildDir()
|
||||
File buildDir = settings.gradle.rootProject
|
||||
.getLayout().getBuildDirectory().getAsFile().get()
|
||||
buildDir.mkdirs()
|
||||
new File(buildDir, "build-scan-uri.txt").text = "(build scan not generated)"
|
||||
buildScanPublished { scan ->
|
||||
|
||||
@@ -3,11 +3,12 @@ description = "Spring AOP"
|
||||
dependencies {
|
||||
api(project(":spring-beans"))
|
||||
api(project(":spring-core"))
|
||||
optional("org.aspectj:aspectjweaver")
|
||||
optional("org.apache.commons:commons-pool2")
|
||||
optional("org.aspectj:aspectjweaver")
|
||||
testFixturesImplementation(testFixtures(project(":spring-beans")))
|
||||
testFixturesImplementation(testFixtures(project(":spring-core")))
|
||||
testFixturesImplementation("com.google.code.findbugs:jsr305")
|
||||
testImplementation(project(":spring-core-test"))
|
||||
testImplementation(testFixtures(project(":spring-beans")))
|
||||
testImplementation(testFixtures(project(":spring-core")))
|
||||
testFixturesImplementation(testFixtures(project(":spring-beans")))
|
||||
testFixturesImplementation(testFixtures(project(":spring-core")))
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,18 +17,23 @@
|
||||
package org.springframework.aop;
|
||||
|
||||
/**
|
||||
* Filter that restricts matching of a pointcut or introduction to
|
||||
* a given set of target classes.
|
||||
* Filter that restricts matching of a pointcut or introduction to a given set
|
||||
* of target classes.
|
||||
*
|
||||
* <p>Can be used as part of a {@link Pointcut} or for the entire
|
||||
* targeting of an {@link IntroductionAdvisor}.
|
||||
* <p>Can be used as part of a {@link Pointcut} or for the entire targeting of
|
||||
* an {@link IntroductionAdvisor}.
|
||||
*
|
||||
* <p>Concrete implementations of this interface typically should provide proper
|
||||
* implementations of {@link Object#equals(Object)} and {@link Object#hashCode()}
|
||||
* in order to allow the filter to be used in caching scenarios — for
|
||||
* example, in proxies generated by CGLIB.
|
||||
* <p><strong>WARNING</strong>: Concrete implementations of this interface must
|
||||
* provide proper implementations of {@link Object#equals(Object)},
|
||||
* {@link Object#hashCode()}, and {@link Object#toString()} in order to allow the
|
||||
* filter to be used in caching scenarios — for example, in proxies generated
|
||||
* by CGLIB. As of Spring Framework 6.0.13, the {@code toString()} implementation
|
||||
* must generate a unique string representation that aligns with the logic used
|
||||
* to implement {@code equals()}. See concrete implementations of this interface
|
||||
* within the framework for examples.
|
||||
*
|
||||
* @author Rod Johnson
|
||||
* @author Sam Brannen
|
||||
* @see Pointcut
|
||||
* @see MethodMatcher
|
||||
*/
|
||||
@@ -44,7 +49,7 @@ public interface ClassFilter {
|
||||
|
||||
|
||||
/**
|
||||
* Canonical instance of a ClassFilter that matches all classes.
|
||||
* Canonical instance of a {@code ClassFilter} that matches all classes.
|
||||
*/
|
||||
ClassFilter TRUE = TrueClassFilter.INSTANCE;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,31 +21,37 @@ import java.lang.reflect.Method;
|
||||
/**
|
||||
* Part of a {@link Pointcut}: Checks whether the target method is eligible for advice.
|
||||
*
|
||||
* <p>A MethodMatcher may be evaluated <b>statically</b> or at <b>runtime</b> (dynamically).
|
||||
* Static matching involves method and (possibly) method attributes. Dynamic matching
|
||||
* also makes arguments for a particular call available, and any effects of running
|
||||
* previous advice applying to the joinpoint.
|
||||
* <p>A {@code MethodMatcher} may be evaluated <b>statically</b> or at <b>runtime</b>
|
||||
* (dynamically). Static matching involves a method and (possibly) method attributes.
|
||||
* Dynamic matching also makes arguments for a particular call available, and any
|
||||
* effects of running previous advice applying to the joinpoint.
|
||||
*
|
||||
* <p>If an implementation returns {@code false} from its {@link #isRuntime()}
|
||||
* method, evaluation can be performed statically, and the result will be the same
|
||||
* for all invocations of this method, whatever their arguments. This means that
|
||||
* if the {@link #isRuntime()} method returns {@code false}, the 3-arg
|
||||
* {@link #matches(java.lang.reflect.Method, Class, Object[])} method will never be invoked.
|
||||
* {@link #matches(Method, Class, Object[])} method will never be invoked.
|
||||
*
|
||||
* <p>If an implementation returns {@code true} from its 2-arg
|
||||
* {@link #matches(java.lang.reflect.Method, Class)} method and its {@link #isRuntime()} method
|
||||
* returns {@code true}, the 3-arg {@link #matches(java.lang.reflect.Method, Class, Object[])}
|
||||
* method will be invoked <i>immediately before each potential execution of the related advice</i>,
|
||||
* to decide whether the advice should run. All previous advice, such as earlier interceptors
|
||||
* in an interceptor chain, will have run, so any state changes they have produced in
|
||||
* parameters or ThreadLocal state will be available at the time of evaluation.
|
||||
* {@link #matches(Method, Class)} method and its {@link #isRuntime()} method
|
||||
* returns {@code true}, the 3-arg {@link #matches(Method, Class, Object[])}
|
||||
* method will be invoked <i>immediately before each potential execution of the
|
||||
* related advice</i> to decide whether the advice should run. All previous advice,
|
||||
* such as earlier interceptors in an interceptor chain, will have run, so any
|
||||
* state changes they have produced in parameters or {@code ThreadLocal} state will
|
||||
* be available at the time of evaluation.
|
||||
*
|
||||
* <p>Concrete implementations of this interface typically should provide proper
|
||||
* implementations of {@link Object#equals(Object)} and {@link Object#hashCode()}
|
||||
* in order to allow the matcher to be used in caching scenarios — for
|
||||
* example, in proxies generated by CGLIB.
|
||||
* <p><strong>WARNING</strong>: Concrete implementations of this interface must
|
||||
* provide proper implementations of {@link Object#equals(Object)},
|
||||
* {@link Object#hashCode()}, and {@link Object#toString()} in order to allow the
|
||||
* matcher to be used in caching scenarios — for example, in proxies generated
|
||||
* by CGLIB. As of Spring Framework 6.0.13, the {@code toString()} implementation
|
||||
* must generate a unique string representation that aligns with the logic used
|
||||
* to implement {@code equals()}. See concrete implementations of this interface
|
||||
* within the framework for examples.
|
||||
*
|
||||
* @author Rod Johnson
|
||||
* @author Sam Brannen
|
||||
* @since 11.11.2003
|
||||
* @see Pointcut
|
||||
* @see ClassFilter
|
||||
@@ -53,11 +59,10 @@ import java.lang.reflect.Method;
|
||||
public interface MethodMatcher {
|
||||
|
||||
/**
|
||||
* Perform static checking whether the given method matches.
|
||||
* <p>If this returns {@code false} or if the {@link #isRuntime()}
|
||||
* method returns {@code false}, no runtime check (i.e. no
|
||||
* {@link #matches(java.lang.reflect.Method, Class, Object[])} call)
|
||||
* will be made.
|
||||
* Perform static checking to determine whether the given method matches.
|
||||
* <p>If this method returns {@code false} or if {@link #isRuntime()}
|
||||
* returns {@code false}, no runtime check (i.e. no
|
||||
* {@link #matches(Method, Class, Object[])} call) will be made.
|
||||
* @param method the candidate method
|
||||
* @param targetClass the target class
|
||||
* @return whether this method matches statically
|
||||
@@ -65,36 +70,35 @@ public interface MethodMatcher {
|
||||
boolean matches(Method method, Class<?> targetClass);
|
||||
|
||||
/**
|
||||
* Is this MethodMatcher dynamic, that is, must a final call be made on the
|
||||
* {@link #matches(java.lang.reflect.Method, Class, Object[])} method at
|
||||
* runtime even if the 2-arg matches method returns {@code true}?
|
||||
* Is this {@code MethodMatcher} dynamic, that is, must a final check be made
|
||||
* via the {@link #matches(Method, Class, Object[])} method at runtime even
|
||||
* if {@link #matches(Method, Class)} returns {@code true}?
|
||||
* <p>Can be invoked when an AOP proxy is created, and need not be invoked
|
||||
* again before each method invocation,
|
||||
* @return whether a runtime match via the 3-arg
|
||||
* {@link #matches(java.lang.reflect.Method, Class, Object[])} method
|
||||
* again before each method invocation.
|
||||
* @return whether a runtime match via {@link #matches(Method, Class, Object[])}
|
||||
* is required if static matching passed
|
||||
*/
|
||||
boolean isRuntime();
|
||||
|
||||
/**
|
||||
* Check whether there a runtime (dynamic) match for this method,
|
||||
* which must have matched statically.
|
||||
* <p>This method is invoked only if the 2-arg matches method returns
|
||||
* {@code true} for the given method and target class, and if the
|
||||
* {@link #isRuntime()} method returns {@code true}. Invoked
|
||||
* immediately before potential running of the advice, after any
|
||||
* Check whether there is a runtime (dynamic) match for this method, which
|
||||
* must have matched statically.
|
||||
* <p>This method is invoked only if {@link #matches(Method, Class)} returns
|
||||
* {@code true} for the given method and target class, and if
|
||||
* {@link #isRuntime()} returns {@code true}.
|
||||
* <p>Invoked immediately before potential running of the advice, after any
|
||||
* advice earlier in the advice chain has run.
|
||||
* @param method the candidate method
|
||||
* @param targetClass the target class
|
||||
* @param args arguments to the method
|
||||
* @return whether there's a runtime match
|
||||
* @see MethodMatcher#matches(Method, Class)
|
||||
* @see #matches(Method, Class)
|
||||
*/
|
||||
boolean matches(Method method, Class<?> targetClass, Object... args);
|
||||
|
||||
|
||||
/**
|
||||
* Canonical instance that matches all methods.
|
||||
* Canonical instance of a {@code MethodMatcher} that matches all methods.
|
||||
*/
|
||||
MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
|
||||
|
||||
|
||||
@@ -714,13 +714,8 @@ public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedence
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (!(other instanceof AdviceExcludingMethodMatcher otherMm)) {
|
||||
return false;
|
||||
}
|
||||
return this.adviceMethod.equals(otherMm.adviceMethod);
|
||||
return (this == other || (other instanceof AdviceExcludingMethodMatcher that &&
|
||||
this.adviceMethod.equals(that.adviceMethod)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+14
-19
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -243,8 +243,8 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
||||
|
||||
/**
|
||||
* If a pointcut expression has been specified in XML, the user cannot
|
||||
* write {@code and} as "&&" (though && will work).
|
||||
* We also allow {@code and} between two pointcut sub-expressions.
|
||||
* write "and" as "&&" (though {@code &&} will work).
|
||||
* <p>We also allow "and" between two pointcut sub-expressions.
|
||||
* <p>This method converts back to {@code &&} for the AspectJ pointcut parser.
|
||||
*/
|
||||
private String replaceBooleanOperators(String pcExpr) {
|
||||
@@ -333,12 +333,12 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
||||
Object targetObject = null;
|
||||
Object thisObject = null;
|
||||
try {
|
||||
MethodInvocation mi = ExposeInvocationInterceptor.currentInvocation();
|
||||
targetObject = mi.getThis();
|
||||
if (!(mi instanceof ProxyMethodInvocation _pmi)) {
|
||||
throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
|
||||
MethodInvocation curr = ExposeInvocationInterceptor.currentInvocation();
|
||||
targetObject = curr.getThis();
|
||||
if (!(curr instanceof ProxyMethodInvocation currPmi)) {
|
||||
throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + curr);
|
||||
}
|
||||
pmi = _pmi;
|
||||
pmi = currPmi;
|
||||
thisObject = pmi.getProxy();
|
||||
}
|
||||
catch (IllegalStateException ex) {
|
||||
@@ -516,21 +516,16 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (!(other instanceof AspectJExpressionPointcut otherPc)) {
|
||||
return false;
|
||||
}
|
||||
return ObjectUtils.nullSafeEquals(this.getExpression(), otherPc.getExpression()) &&
|
||||
ObjectUtils.nullSafeEquals(this.pointcutDeclarationScope, otherPc.pointcutDeclarationScope) &&
|
||||
ObjectUtils.nullSafeEquals(this.pointcutParameterNames, otherPc.pointcutParameterNames) &&
|
||||
ObjectUtils.nullSafeEquals(this.pointcutParameterTypes, otherPc.pointcutParameterTypes);
|
||||
return (this == other || (other instanceof AspectJExpressionPointcut that &&
|
||||
ObjectUtils.nullSafeEquals(getExpression(), that.getExpression()) &&
|
||||
ObjectUtils.nullSafeEquals(this.pointcutDeclarationScope, that.pointcutDeclarationScope) &&
|
||||
ObjectUtils.nullSafeEquals(this.pointcutParameterNames, that.pointcutParameterNames) &&
|
||||
ObjectUtils.nullSafeEquals(this.pointcutParameterTypes, that.pointcutParameterTypes)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hashCode = ObjectUtils.nullSafeHashCode(this.getExpression());
|
||||
int hashCode = ObjectUtils.nullSafeHashCode(getExpression());
|
||||
hashCode = 31 * hashCode + ObjectUtils.nullSafeHashCode(this.pointcutDeclarationScope);
|
||||
hashCode = 31 * hashCode + ObjectUtils.nullSafeHashCode(this.pointcutParameterNames);
|
||||
hashCode = 31 * hashCode + ObjectUtils.nullSafeHashCode(this.pointcutParameterTypes);
|
||||
|
||||
+3
-8
@@ -25,7 +25,7 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* AspectJPointcutAdvisor that adapts an {@link AbstractAspectJAdvice}
|
||||
* AspectJ {@link PointcutAdvisor} that adapts an {@link AbstractAspectJAdvice}
|
||||
* to the {@link org.springframework.aop.PointcutAdvisor} interface.
|
||||
*
|
||||
* @author Adrian Colyer
|
||||
@@ -89,13 +89,8 @@ public class AspectJPointcutAdvisor implements PointcutAdvisor, Ordered {
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (!(other instanceof AspectJPointcutAdvisor otherAdvisor)) {
|
||||
return false;
|
||||
}
|
||||
return this.advice.equals(otherAdvisor.advice);
|
||||
return (this == other || (other instanceof AspectJPointcutAdvisor that &&
|
||||
this.advice.equals(that.advice)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-2
@@ -117,8 +117,8 @@ public class TypePatternClassFilter implements ClassFilter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
return (this == obj || (obj instanceof TypePatternClassFilter that &&
|
||||
public boolean equals(@Nullable Object other) {
|
||||
return (this == other || (other instanceof TypePatternClassFilter that &&
|
||||
ObjectUtils.nullSafeEquals(this.typePattern, that.typePattern)));
|
||||
}
|
||||
|
||||
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.aop.aspectj.annotation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.aop.Advisor;
|
||||
import org.springframework.aop.aspectj.AbstractAspectJAdvice;
|
||||
import org.springframework.aot.generate.GenerationContext;
|
||||
import org.springframework.aot.hint.ExecutableMode;
|
||||
import org.springframework.aot.hint.ReflectionHints;
|
||||
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
|
||||
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
|
||||
import org.springframework.beans.factory.aot.BeanFactoryInitializationCode;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* {@link BeanFactoryInitializationAotProcessor} implementation responsible for registering
|
||||
* hints for AOP advices.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0.11
|
||||
*/
|
||||
class AspectJBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor {
|
||||
|
||||
private static final boolean aspectJPresent = ClassUtils.isPresent("org.aspectj.lang.annotation.Pointcut",
|
||||
AspectJBeanFactoryInitializationAotProcessor.class.getClassLoader());
|
||||
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
|
||||
if (aspectJPresent) {
|
||||
return AspectDelegate.processAheadOfTime(beanFactory);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inner class to avoid a hard dependency on AspectJ at runtime.
|
||||
*/
|
||||
private static class AspectDelegate {
|
||||
|
||||
@Nullable
|
||||
private static AspectContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
|
||||
BeanFactoryAspectJAdvisorsBuilder builder = new BeanFactoryAspectJAdvisorsBuilder(beanFactory);
|
||||
List<Advisor> advisors = builder.buildAspectJAdvisors();
|
||||
return (advisors.isEmpty() ? null : new AspectContribution(advisors));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class AspectContribution implements BeanFactoryInitializationAotContribution {
|
||||
|
||||
private final List<Advisor> advisors;
|
||||
|
||||
public AspectContribution(List<Advisor> advisors) {
|
||||
this.advisors = advisors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyTo(GenerationContext generationContext, BeanFactoryInitializationCode beanFactoryInitializationCode) {
|
||||
ReflectionHints reflectionHints = generationContext.getRuntimeHints().reflection();
|
||||
for (Advisor advisor : this.advisors) {
|
||||
if (advisor.getAdvice() instanceof AbstractAspectJAdvice aspectJAdvice) {
|
||||
reflectionHints.registerMethod(aspectJAdvice.getAspectJAdviceMethod(), ExecutableMode.INVOKE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+25
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -32,6 +32,7 @@ import org.springframework.aop.aspectj.annotation.AbstractAspectJAdvisorFactory.
|
||||
import org.springframework.aop.support.DynamicMethodMatcherPointcut;
|
||||
import org.springframework.aop.support.Pointcuts;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Internal implementation of AspectJPointcutAdvisor.
|
||||
@@ -40,6 +41,7 @@ import org.springframework.lang.Nullable;
|
||||
*
|
||||
* @author Rod Johnson
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @since 2.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
@@ -291,12 +293,33 @@ final class InstantiationModelAwarePointcutAdvisorImpl
|
||||
@Override
|
||||
public boolean matches(Method method, Class<?> targetClass, Object... args) {
|
||||
// This can match only on declared pointcut.
|
||||
return (isAspectMaterialized() && this.declaredPointcut.matches(method, targetClass));
|
||||
return (isAspectMaterialized() && this.declaredPointcut.matches(method, targetClass, args));
|
||||
}
|
||||
|
||||
private boolean isAspectMaterialized() {
|
||||
return (this.aspectInstanceFactory == null || this.aspectInstanceFactory.isMaterialized());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
// For equivalence, we only need to compare the preInstantiationPointcut fields since
|
||||
// they include the declaredPointcut fields. In addition, we should not compare the
|
||||
// aspectInstanceFactory fields since LazySingletonAspectInstanceFactoryDecorator does
|
||||
// not implement equals().
|
||||
return (this == other || (other instanceof PerTargetInstantiationModelPointcut that &&
|
||||
ObjectUtils.nullSafeEquals(this.preInstantiationPointcut, that.preInstantiationPointcut)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return ObjectUtils.nullSafeHashCode(this.declaredPointcut.getExpression());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return PerTargetInstantiationModelPointcut.class.getName() + ": " + this.declaredPointcut.getExpression();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -48,7 +48,8 @@ import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Base class for AOP proxy configuration managers.
|
||||
* These are not themselves AOP proxies, but subclasses of this class are
|
||||
*
|
||||
* <p>These are not themselves AOP proxies, but subclasses of this class are
|
||||
* normally factories from which AOP proxy instances are obtained directly.
|
||||
*
|
||||
* <p>This class frees subclasses of the housekeeping of Advices
|
||||
@@ -56,10 +57,12 @@ import org.springframework.util.ObjectUtils;
|
||||
* methods, which are provided by subclasses.
|
||||
*
|
||||
* <p>This class is serializable; subclasses need not be.
|
||||
* This class is used to hold snapshots of proxies.
|
||||
*
|
||||
* <p>This class is used to hold snapshots of proxies.
|
||||
*
|
||||
* @author Rod Johnson
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @see org.springframework.aop.framework.AopProxy
|
||||
*/
|
||||
public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
@@ -99,6 +102,12 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
*/
|
||||
private List<Advisor> advisors = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* List of minimal {@link AdvisorKeyEntry} instances,
|
||||
* to be assigned to the {@link #advisors} field on reduction.
|
||||
* @since 6.0.10
|
||||
* @see #reduceToAdvisorKey
|
||||
*/
|
||||
private List<Advisor> advisorKey = this.advisors;
|
||||
|
||||
|
||||
@@ -111,7 +120,7 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a AdvisedSupport instance with the given parameters.
|
||||
* Create an {@code AdvisedSupport} instance with the given parameters.
|
||||
* @param interfaces the proxied interfaces
|
||||
*/
|
||||
public AdvisedSupport(Class<?>... interfaces) {
|
||||
@@ -131,7 +140,7 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
|
||||
/**
|
||||
* Set the given object as target.
|
||||
* Will create a SingletonTargetSource for the object.
|
||||
* <p>Will create a SingletonTargetSource for the object.
|
||||
* @see #setTargetSource
|
||||
* @see org.springframework.aop.target.SingletonTargetSource
|
||||
*/
|
||||
@@ -506,9 +515,9 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the AOP configuration from the given AdvisedSupport object,
|
||||
* but allow substitution of a fresh TargetSource and a given interceptor chain.
|
||||
* @param other the AdvisedSupport object to take proxy configuration from
|
||||
* Copy the AOP configuration from the given {@link AdvisedSupport} object,
|
||||
* but allow substitution of a fresh {@link TargetSource} and a given interceptor chain.
|
||||
* @param other the {@code AdvisedSupport} object to take proxy configuration from
|
||||
* @param targetSource the new TargetSource
|
||||
* @param advisors the Advisors for the chain
|
||||
*/
|
||||
@@ -528,8 +537,8 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a configuration-only copy of this AdvisedSupport,
|
||||
* replacing the TargetSource.
|
||||
* Build a configuration-only copy of this {@link AdvisedSupport},
|
||||
* replacing the {@link TargetSource}.
|
||||
*/
|
||||
AdvisedSupport getConfigurationOnlyCopy() {
|
||||
AdvisedSupport copy = new AdvisedSupport(this.advisorChainFactory, this.methodCache);
|
||||
@@ -554,18 +563,6 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Serialization support
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
|
||||
// Rely on default serialization; just initialize state after deserialization.
|
||||
ois.defaultReadObject();
|
||||
|
||||
// Initialize transient fields.
|
||||
this.methodCache = new ConcurrentHashMap<>(32);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toProxyConfigString() {
|
||||
return toString();
|
||||
@@ -587,6 +584,19 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Serialization support
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
|
||||
// Rely on default serialization; just initialize state after deserialization.
|
||||
ois.defaultReadObject();
|
||||
|
||||
// Initialize transient fields.
|
||||
this.methodCache = new ConcurrentHashMap<>(32);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Simple wrapper class around a Method. Used as the key when
|
||||
* caching methods, for efficient equals and hashCode comparisons.
|
||||
@@ -604,8 +614,7 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
return (this == other || (other instanceof MethodCacheKey methodCacheKey &&
|
||||
this.method == methodCacheKey.method));
|
||||
return (this == other || (other instanceof MethodCacheKey that && this.method == that.method));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -630,29 +639,33 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
|
||||
|
||||
/**
|
||||
* Stub for an Advisor instance that is just needed for key purposes,
|
||||
* Stub for an {@link Advisor} instance that is just needed for key purposes,
|
||||
* allowing for efficient equals and hashCode comparisons against the
|
||||
* advice class and the pointcut.
|
||||
* @since 6.0.10
|
||||
* @see #getConfigurationOnlyCopy()
|
||||
* @see #getAdvisorKey()
|
||||
*/
|
||||
private static class AdvisorKeyEntry implements Advisor {
|
||||
private static final class AdvisorKeyEntry implements Advisor {
|
||||
|
||||
private final Class<?> adviceType;
|
||||
|
||||
@Nullable
|
||||
private String classFilterKey;
|
||||
private final String classFilterKey;
|
||||
|
||||
@Nullable
|
||||
private String methodMatcherKey;
|
||||
private final String methodMatcherKey;
|
||||
|
||||
public AdvisorKeyEntry(Advisor advisor) {
|
||||
this.adviceType = advisor.getAdvice().getClass();
|
||||
if (advisor instanceof PointcutAdvisor pointcutAdvisor) {
|
||||
Pointcut pointcut = pointcutAdvisor.getPointcut();
|
||||
this.classFilterKey = ObjectUtils.identityToString(pointcut.getClassFilter());
|
||||
this.methodMatcherKey = ObjectUtils.identityToString(pointcut.getMethodMatcher());
|
||||
this.classFilterKey = pointcut.getClassFilter().toString();
|
||||
this.methodMatcherKey = pointcut.getMethodMatcher().toString();
|
||||
}
|
||||
else {
|
||||
this.classFilterKey = null;
|
||||
this.methodMatcherKey = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -663,10 +676,10 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
return (this == other || (other instanceof AdvisorKeyEntry otherEntry &&
|
||||
this.adviceType == otherEntry.adviceType &&
|
||||
ObjectUtils.nullSafeEquals(this.classFilterKey, otherEntry.classFilterKey) &&
|
||||
ObjectUtils.nullSafeEquals(this.methodMatcherKey, otherEntry.methodMatcherKey)));
|
||||
return (this == other || (other instanceof AdvisorKeyEntry that &&
|
||||
this.adviceType == that.adviceType &&
|
||||
ObjectUtils.nullSafeEquals(this.classFilterKey, that.classFilterKey) &&
|
||||
ObjectUtils.nullSafeEquals(this.methodMatcherKey, that.methodMatcherKey)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -370,8 +370,8 @@ class CglibAopProxy implements AopProxy, Serializable {
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
return (this == other || (other instanceof CglibAopProxy cglibAopProxy &&
|
||||
AopProxyUtils.equalsInProxy(this.advised, cglibAopProxy.advised)));
|
||||
return (this == other || (other instanceof CglibAopProxy that &&
|
||||
AopProxyUtils.equalsInProxy(this.advised, that.advised)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -597,15 +597,10 @@ class CglibAopProxy implements AopProxy, Serializable {
|
||||
}
|
||||
if (other instanceof Factory factory) {
|
||||
Callback callback = factory.getCallback(INVOKE_EQUALS);
|
||||
if (!(callback instanceof EqualsInterceptor equalsInterceptor)) {
|
||||
return false;
|
||||
}
|
||||
AdvisedSupport otherAdvised = equalsInterceptor.advised;
|
||||
return AopProxyUtils.equalsInProxy(this.advised, otherAdvised);
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
return (callback instanceof EqualsInterceptor that &&
|
||||
AopProxyUtils.equalsInProxy(this.advised, that.advised));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -920,20 +915,14 @@ class CglibAopProxy implements AopProxy, Serializable {
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (!(other instanceof ProxyCallbackFilter otherCallbackFilter)) {
|
||||
return false;
|
||||
}
|
||||
AdvisedSupport otherAdvised = otherCallbackFilter.advised;
|
||||
return (this.advised.getAdvisorKey().equals(otherAdvised.getAdvisorKey()) &&
|
||||
AopProxyUtils.equalsProxiedInterfaces(this.advised, otherAdvised) &&
|
||||
ObjectUtils.nullSafeEquals(this.advised.getTargetClass(), otherAdvised.getTargetClass()) &&
|
||||
this.advised.getTargetSource().isStatic() == otherAdvised.getTargetSource().isStatic() &&
|
||||
this.advised.isFrozen() == otherAdvised.isFrozen() &&
|
||||
this.advised.isExposeProxy() == otherAdvised.isExposeProxy() &&
|
||||
this.advised.isOpaque() == otherAdvised.isOpaque());
|
||||
return (this == other || (other instanceof ProxyCallbackFilter that &&
|
||||
this.advised.getAdvisorKey().equals(that.advised.getAdvisorKey()) &&
|
||||
AopProxyUtils.equalsProxiedInterfaces(this.advised, that.advised) &&
|
||||
ObjectUtils.nullSafeEquals(this.advised.getTargetClass(), that.advised.getTargetClass()) &&
|
||||
this.advised.getTargetSource().isStatic() == that.advised.getTargetSource().isStatic() &&
|
||||
this.advised.isFrozen() == that.advised.isFrozen() &&
|
||||
this.advised.isExposeProxy() == that.advised.isExposeProxy() &&
|
||||
this.advised.isOpaque() == that.advised.isOpaque()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -265,18 +265,32 @@ public class ProxyFactoryBean extends ProxyCreatorSupport
|
||||
* Return the type of the proxy. Will check the singleton instance if
|
||||
* already created, else fall back to the proxy interface (in case of just
|
||||
* a single one), the target bean type, or the TargetSource's target class.
|
||||
* @see org.springframework.aop.TargetSource#getTargetClass
|
||||
* @see org.springframework.aop.framework.AopProxy#getProxyClass
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public Class<?> getObjectType() {
|
||||
synchronized (this) {
|
||||
if (this.singletonInstance != null) {
|
||||
return this.singletonInstance.getClass();
|
||||
}
|
||||
}
|
||||
// This might be incomplete since it potentially misses introduced interfaces
|
||||
// from Advisors that will be lazily retrieved via setInterceptorNames.
|
||||
return createAopProxy().getProxyClass(this.proxyClassLoader);
|
||||
try {
|
||||
// This might be incomplete since it potentially misses introduced interfaces
|
||||
// from Advisors that will be lazily retrieved via setInterceptorNames.
|
||||
return createAopProxy().getProxyClass(this.proxyClassLoader);
|
||||
}
|
||||
catch (AopConfigException ex) {
|
||||
if (getTargetClass() == null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Failed to determine early proxy class: " + ex.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-1
@@ -58,6 +58,7 @@ import org.springframework.util.function.SingletonSupplier;
|
||||
* @author Juergen Hoeller
|
||||
* @author Stephane Nicoll
|
||||
* @author He Bo
|
||||
* @author Sebastien Deleuze
|
||||
* @since 3.1.2
|
||||
*/
|
||||
public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
|
||||
@@ -290,7 +291,7 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
|
||||
else if (Future.class.isAssignableFrom(returnType)) {
|
||||
return executor.submit(task);
|
||||
}
|
||||
else if (void.class == returnType) {
|
||||
else if (void.class == returnType || "kotlin.Unit".equals(returnType.getName())) {
|
||||
executor.submit(task);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -109,8 +109,8 @@ public abstract class ClassFilters {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
return (this == obj || (obj instanceof UnionClassFilter that &&
|
||||
public boolean equals(@Nullable Object other) {
|
||||
return (this == other || (other instanceof UnionClassFilter that &&
|
||||
ObjectUtils.nullSafeEquals(this.filters, that.filters)));
|
||||
}
|
||||
|
||||
@@ -123,7 +123,6 @@ public abstract class ClassFilters {
|
||||
public String toString() {
|
||||
return getClass().getName() + ": " + Arrays.toString(this.filters);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -150,8 +149,8 @@ public abstract class ClassFilters {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
return (this == obj || (obj instanceof IntersectionClassFilter that &&
|
||||
public boolean equals(@Nullable Object other) {
|
||||
return (this == other || (other instanceof IntersectionClassFilter that &&
|
||||
ObjectUtils.nullSafeEquals(this.filters, that.filters)));
|
||||
}
|
||||
|
||||
@@ -164,7 +163,6 @@ public abstract class ClassFilters {
|
||||
public String toString() {
|
||||
return getClass().getName() + ": " + Arrays.toString(this.filters);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -100,8 +100,8 @@ public class NameMatchMethodPointcut extends StaticMethodMatcherPointcut impleme
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
return (this == obj || (obj instanceof NameMatchMethodPointcut that &&
|
||||
public boolean equals(@Nullable Object other) {
|
||||
return (this == other || (other instanceof NameMatchMethodPointcut that &&
|
||||
this.mappedNames.equals(that.mappedNames)));
|
||||
}
|
||||
|
||||
|
||||
@@ -46,8 +46,8 @@ public class RootClassFilter implements ClassFilter, Serializable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
return (this == obj || (obj instanceof RootClassFilter that &&
|
||||
public boolean equals(@Nullable Object other) {
|
||||
return (this == other || (other instanceof RootClassFilter that &&
|
||||
this.clazz.equals(that.clazz)));
|
||||
}
|
||||
|
||||
|
||||
@@ -133,13 +133,9 @@ public final class EmptyTargetSource implements TargetSource, Serializable {
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (!(other instanceof EmptyTargetSource otherTs)) {
|
||||
return false;
|
||||
}
|
||||
return (ObjectUtils.nullSafeEquals(this.targetClass, otherTs.targetClass) && this.isStatic == otherTs.isStatic);
|
||||
return (this == other || (other instanceof EmptyTargetSource that &&
|
||||
ObjectUtils.nullSafeEquals(this.targetClass, that.targetClass) &&
|
||||
this.isStatic == that.isStatic));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=\
|
||||
org.springframework.aop.scope.ScopedProxyBeanRegistrationAotProcessor
|
||||
|
||||
org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor= \
|
||||
org.springframework.aop.aspectj.annotation.AspectJBeanFactoryInitializationAotProcessor
|
||||
|
||||
+23
-25
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,7 +23,6 @@ import java.util.Map;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.aspectj.weaver.tools.PointcutExpression;
|
||||
import org.aspectj.weaver.tools.PointcutPrimitive;
|
||||
import org.aspectj.weaver.tools.UnsupportedPointcutPrimitiveException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -65,7 +64,7 @@ public class AspectJExpressionPointcutTests {
|
||||
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() throws NoSuchMethodException {
|
||||
public void setup() throws NoSuchMethodException {
|
||||
getAge = TestBean.class.getMethod("getAge");
|
||||
setAge = TestBean.class.getMethod("setAge", int.class);
|
||||
setSomeNumber = TestBean.class.getMethod("setSomeNumber", Number.class);
|
||||
@@ -246,14 +245,13 @@ public class AspectJExpressionPointcutTests {
|
||||
@Test
|
||||
public void testInvalidExpression() {
|
||||
String expression = "execution(void org.springframework.beans.testfixture.beans.TestBean.setSomeNumber(Number) && args(Double)";
|
||||
assertThatIllegalArgumentException().isThrownBy(
|
||||
getPointcut(expression)::getClassFilter); // call to getClassFilter forces resolution
|
||||
assertThatIllegalArgumentException().isThrownBy(getPointcut(expression)::getClassFilter); // call to getClassFilter forces resolution
|
||||
}
|
||||
|
||||
private TestBean getAdvisedProxy(String pointcutExpression, CallCountingInterceptor interceptor) {
|
||||
TestBean target = new TestBean();
|
||||
|
||||
Pointcut pointcut = getPointcut(pointcutExpression);
|
||||
AspectJExpressionPointcut pointcut = getPointcut(pointcutExpression);
|
||||
|
||||
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
|
||||
advisor.setAdvice(interceptor);
|
||||
@@ -277,40 +275,31 @@ public class AspectJExpressionPointcutTests {
|
||||
@Test
|
||||
public void testWithUnsupportedPointcutPrimitive() {
|
||||
String expression = "call(int org.springframework.beans.testfixture.beans.TestBean.getAge())";
|
||||
assertThatExceptionOfType(UnsupportedPointcutPrimitiveException.class).isThrownBy(() ->
|
||||
getPointcut(expression).getClassFilter()) // call to getClassFilter forces resolution...
|
||||
.satisfies(ex -> assertThat(ex.getUnsupportedPrimitive()).isEqualTo(PointcutPrimitive.CALL));
|
||||
assertThatExceptionOfType(UnsupportedPointcutPrimitiveException.class)
|
||||
.isThrownBy(() -> getPointcut(expression).getClassFilter()) // call to getClassFilter forces resolution...
|
||||
.satisfies(ex -> assertThat(ex.getUnsupportedPrimitive()).isEqualTo(PointcutPrimitive.CALL));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAndSubstitution() {
|
||||
Pointcut pc = getPointcut("execution(* *(..)) and args(String)");
|
||||
PointcutExpression expr = ((AspectJExpressionPointcut) pc).getPointcutExpression();
|
||||
assertThat(expr.getPointcutExpression()).isEqualTo("execution(* *(..)) && args(String)");
|
||||
AspectJExpressionPointcut pc = getPointcut("execution(* *(..)) and args(String)");
|
||||
String expr = pc.getPointcutExpression().getPointcutExpression();
|
||||
assertThat(expr).isEqualTo("execution(* *(..)) && args(String)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleAndSubstitutions() {
|
||||
Pointcut pc = getPointcut("execution(* *(..)) and args(String) and this(Object)");
|
||||
PointcutExpression expr = ((AspectJExpressionPointcut) pc).getPointcutExpression();
|
||||
assertThat(expr.getPointcutExpression()).isEqualTo("execution(* *(..)) && args(String) && this(Object)");
|
||||
AspectJExpressionPointcut pc = getPointcut("execution(* *(..)) and args(String) and this(Object)");
|
||||
String expr = pc.getPointcutExpression().getPointcutExpression();
|
||||
assertThat(expr).isEqualTo("execution(* *(..)) && args(String) && this(Object)");
|
||||
}
|
||||
|
||||
private Pointcut getPointcut(String expression) {
|
||||
private AspectJExpressionPointcut getPointcut(String expression) {
|
||||
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
|
||||
pointcut.setExpression(expression);
|
||||
return pointcut;
|
||||
}
|
||||
|
||||
|
||||
public static class OtherIOther implements IOther {
|
||||
|
||||
@Override
|
||||
public void absquatulate() {
|
||||
// Empty
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchGenericArgument() {
|
||||
String expression = "execution(* set*(java.util.List<org.springframework.beans.testfixture.beans.TestBean>) )";
|
||||
@@ -505,6 +494,15 @@ public class AspectJExpressionPointcutTests {
|
||||
}
|
||||
|
||||
|
||||
public static class OtherIOther implements IOther {
|
||||
|
||||
@Override
|
||||
public void absquatulate() {
|
||||
// Empty
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class HasGeneric {
|
||||
|
||||
public void setFriends(List<TestBean> friends) {
|
||||
|
||||
+20
-17
@@ -40,10 +40,6 @@ import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import test.aop.DefaultLockable;
|
||||
import test.aop.Lockable;
|
||||
import test.aop.PerTargetAspect;
|
||||
import test.aop.TwoAdviceAspect;
|
||||
|
||||
import org.springframework.aop.Advisor;
|
||||
import org.springframework.aop.framework.Advised;
|
||||
@@ -51,6 +47,10 @@ import org.springframework.aop.framework.AopConfigException;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.aop.interceptor.ExposeInvocationInterceptor;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.aop.testfixture.aspectj.PerTargetAspect;
|
||||
import org.springframework.aop.testfixture.aspectj.TwoAdviceAspect;
|
||||
import org.springframework.aop.testfixture.mixin.DefaultLockable;
|
||||
import org.springframework.aop.testfixture.mixin.Lockable;
|
||||
import org.springframework.beans.testfixture.beans.ITestBean;
|
||||
import org.springframework.beans.testfixture.beans.TestBean;
|
||||
import org.springframework.core.OrderComparator;
|
||||
@@ -127,7 +127,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void multiplePerTargetAspects() throws Exception {
|
||||
void multiplePerTargetAspects() {
|
||||
TestBean target = new TestBean();
|
||||
int realAge = 65;
|
||||
target.setAge(realAge);
|
||||
@@ -153,7 +153,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void multiplePerTargetAspectsWithOrderAnnotation() throws Exception {
|
||||
void multiplePerTargetAspectsWithOrderAnnotation() {
|
||||
TestBean target = new TestBean();
|
||||
int realAge = 65;
|
||||
target.setAge(realAge);
|
||||
@@ -283,7 +283,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
int realAge = 65;
|
||||
target.setAge(realAge);
|
||||
ITestBean itb = createProxy(target, ITestBean.class,
|
||||
getAdvisorFactory().getAdvisors(aspectInstanceFactory(aspectInstance, "someBean")));
|
||||
getAdvisorFactory().getAdvisors(aspectInstanceFactory(aspectInstance, "someBean")));
|
||||
assertThat(itb.getAge()).as("Around advice must apply").isEqualTo(-1);
|
||||
assertThat(target.getAge()).isEqualTo(realAge);
|
||||
}
|
||||
@@ -292,7 +292,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
void bindingWithSingleArg() {
|
||||
TestBean target = new TestBean();
|
||||
ITestBean itb = createProxy(target, ITestBean.class,
|
||||
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new BindingAspectWithSingleArg(), "someBean")));
|
||||
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new BindingAspectWithSingleArg(), "someBean")));
|
||||
itb.setAge(10);
|
||||
assertThat(itb.getAge()).as("Around advice must apply").isEqualTo(20);
|
||||
assertThat(target.getAge()).isEqualTo(20);
|
||||
@@ -459,10 +459,10 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
assertThat(advisors).as("Two advice methods found").hasSize(2);
|
||||
ITestBean itb = createProxy(target, ITestBean.class, advisors);
|
||||
itb.setName("");
|
||||
assertThat(itb.getAge()).isEqualTo(0);
|
||||
assertThat(itb.age()).isEqualTo(0);
|
||||
int newAge = 32;
|
||||
itb.setAge(newAge);
|
||||
assertThat(itb.getAge()).isEqualTo(1);
|
||||
assertThat(itb.age()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -483,7 +483,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonAbstractParentAspect() throws Exception {
|
||||
void nonAbstractParentAspect() {
|
||||
IncrementingAspect aspect = new IncrementingAspect();
|
||||
|
||||
// Precondition:
|
||||
@@ -637,17 +637,20 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
}
|
||||
|
||||
|
||||
static class CommonPointcuts {
|
||||
|
||||
@Pointcut("execution(* getAge())")
|
||||
void getAge() {
|
||||
}
|
||||
}
|
||||
|
||||
@Aspect
|
||||
static class NamedPointcutAspectWithFQN {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private ITestBean fieldThatShouldBeIgnoredBySpringAtAspectJProcessing = new TestBean();
|
||||
|
||||
@Pointcut("execution(* getAge())")
|
||||
void getAge() {
|
||||
}
|
||||
|
||||
@Around("org.springframework.aop.aspectj.annotation.AbstractAspectJAdvisorFactoryTests.NamedPointcutAspectWithFQN.getAge()")
|
||||
@Around("org.springframework.aop.aspectj.annotation.AbstractAspectJAdvisorFactoryTests.CommonPointcuts.getAge()()")
|
||||
int changeReturnValue(ProceedingJoinPoint pjp) {
|
||||
return -1;
|
||||
}
|
||||
@@ -686,7 +689,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
@Aspect
|
||||
static class Library {
|
||||
|
||||
@Pointcut("execution(!void get*())")
|
||||
@Pointcut("execution(int get*())") // before AspectJ 1.9.20, "!void" used to work instead of "int"
|
||||
void propertyAccess() {}
|
||||
|
||||
@Pointcut("execution(* *(..)) && args(i)")
|
||||
|
||||
+40
-17
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -42,26 +42,38 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
*/
|
||||
class ArgumentBindingTests {
|
||||
|
||||
@Test
|
||||
void bindingInPointcutUsedByAdvice() {
|
||||
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new TestBean());
|
||||
proxyFactory.addAspect(NamedPointcutWithArgs.class);
|
||||
|
||||
ITestBean proxiedTestBean = proxyFactory.getProxy();
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> proxiedTestBean.setName("enigma"))
|
||||
.withMessage("enigma");
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationArgumentNameBinding() {
|
||||
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new TransactionalBean());
|
||||
proxyFactory.addAspect(PointcutWithAnnotationArgument.class);
|
||||
|
||||
ITransactionalBean proxiedTestBean = proxyFactory.getProxy();
|
||||
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(proxiedTestBean::doInTransaction)
|
||||
.withMessage("Invoked with @Transactional");
|
||||
.isThrownBy(proxiedTestBean::doInTransaction)
|
||||
.withMessage("Invoked with @Transactional");
|
||||
}
|
||||
|
||||
@Test
|
||||
void bindingInPointcutUsedByAdvice() {
|
||||
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new TestBean());
|
||||
proxyFactory.addAspect(NamedPointcutWithArgs.class);
|
||||
ITestBean proxiedTestBean = proxyFactory.getProxy();
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> proxiedTestBean.setName("enigma"))
|
||||
.withMessage("enigma");
|
||||
}
|
||||
|
||||
@Test
|
||||
void bindingWithDynamicAdvice() {
|
||||
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new TestBean());
|
||||
proxyFactory.addAspect(DynamicPointcutWithArgs.class);
|
||||
ITestBean proxiedTestBean = proxyFactory.getProxy();
|
||||
|
||||
proxiedTestBean.applyName(1);
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> proxiedTestBean.applyName("enigma"))
|
||||
.withMessage("enigma");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -94,6 +106,7 @@ class ArgumentBindingTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Mimics Spring's @Transactional annotation without actually introducing the dependency.
|
||||
*/
|
||||
@@ -101,16 +114,17 @@ class ArgumentBindingTests {
|
||||
@interface Transactional {
|
||||
}
|
||||
|
||||
|
||||
@Aspect
|
||||
static class PointcutWithAnnotationArgument {
|
||||
|
||||
@Around(value = "execution(* org.springframework..*.*(..)) && @annotation(transactional)")
|
||||
@Around("execution(* org.springframework..*.*(..)) && @annotation(transactional)")
|
||||
public Object around(ProceedingJoinPoint pjp, Transactional transactional) throws Throwable {
|
||||
throw new IllegalStateException("Invoked with @Transactional");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Aspect
|
||||
static class NamedPointcutWithArgs {
|
||||
|
||||
@@ -121,7 +135,16 @@ class ArgumentBindingTests {
|
||||
public Object doAround(ProceedingJoinPoint pjp, String aString) throws Throwable {
|
||||
throw new IllegalArgumentException(aString);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Aspect("pertarget(execution(* *(..)))")
|
||||
static class DynamicPointcutWithArgs {
|
||||
|
||||
@Around("execution(* *(..)) && args(java.lang.String)")
|
||||
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
|
||||
throw new IllegalArgumentException(String.valueOf(pjp.getArgs()[0]));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.aop.aspectj.annotation;
|
||||
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.generate.GenerationContext;
|
||||
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
|
||||
import org.springframework.aot.test.generate.TestGenerationContext;
|
||||
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link AspectJBeanFactoryInitializationAotProcessor}.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
class AspectJBeanFactoryInitializationAotProcessorTests {
|
||||
|
||||
private final GenerationContext generationContext = new TestGenerationContext();
|
||||
|
||||
@Test
|
||||
void shouldSkipEmptyClass() {
|
||||
assertThat(createContribution(EmptyClass.class)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldProcessAspect() {
|
||||
process(TestAspect.class);
|
||||
assertThat(RuntimeHintsPredicates.reflection().onMethod(TestAspect.class, "alterReturnValue").invoke())
|
||||
.accepts(this.generationContext.getRuntimeHints());
|
||||
}
|
||||
|
||||
private void process(Class<?> beanClass) {
|
||||
BeanFactoryInitializationAotContribution contribution = createContribution(beanClass);
|
||||
if (contribution != null) {
|
||||
contribution.applyTo(this.generationContext, mock());
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static BeanFactoryInitializationAotContribution createContribution(Class<?> beanClass) {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
beanFactory.registerBeanDefinition(beanClass.getName(), new RootBeanDefinition(beanClass));
|
||||
return new AspectJBeanFactoryInitializationAotProcessor().processAheadOfTime(beanFactory);
|
||||
}
|
||||
|
||||
|
||||
static class EmptyClass { }
|
||||
|
||||
@Aspect
|
||||
static class TestAspect {
|
||||
|
||||
@Around("pointcut()")
|
||||
public Object alterReturnValue(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
joinPoint.proceed();
|
||||
return "A-from-aspect";
|
||||
}
|
||||
|
||||
@Pointcut("execution(* com.example.aspect.Test*.methodA(..))")
|
||||
private void pointcut() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,13 +17,13 @@
|
||||
package org.springframework.aop.aspectj.annotation;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import test.aop.PerTargetAspect;
|
||||
|
||||
import org.springframework.aop.Pointcut;
|
||||
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
|
||||
import org.springframework.aop.aspectj.AspectJExpressionPointcutTests;
|
||||
import org.springframework.aop.aspectj.annotation.AbstractAspectJAdvisorFactoryTests.ExceptionThrowingAspect;
|
||||
import org.springframework.aop.framework.AopConfigException;
|
||||
import org.springframework.aop.testfixture.aspectj.PerTargetAspect;
|
||||
import org.springframework.beans.testfixture.beans.TestBean;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,11 +18,11 @@ package org.springframework.aop.aspectj.annotation;
|
||||
|
||||
import org.aspectj.lang.reflect.PerClauseKind;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import test.aop.PerTargetAspect;
|
||||
|
||||
import org.springframework.aop.Pointcut;
|
||||
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
|
||||
import org.springframework.aop.aspectj.annotation.AbstractAspectJAdvisorFactoryTests.ExceptionThrowingAspect;
|
||||
import org.springframework.aop.testfixture.aspectj.PerTargetAspect;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user