mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
1119 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0d055fca80 | |||
| d41f546c43 | |||
| c8a4026512 | |||
| e685ff0416 | |||
| 4bfbf7d3c8 | |||
| 2111bf9b9d | |||
| c72dd1ff66 | |||
| 279e6eb423 | |||
| eb65939341 | |||
| 3f79b267b1 | |||
| 43bd78913c | |||
| 5458e0dccc | |||
| 5641d87ce8 | |||
| 78a73e5f57 | |||
| 24dd484471 | |||
| b43972bb54 | |||
| 17109b2402 | |||
| dbfece6c31 | |||
| 86a101ac2b | |||
| c7269feeaa | |||
| 1f544f113a | |||
| 3a38bb48b5 | |||
| 02d3269dbb | |||
| d81ddcef34 | |||
| 701c39a325 | |||
| d77595bf2c | |||
| 1e75041b00 | |||
| 0b99872704 | |||
| b8b3e6d20c | |||
| 08bc7ed8f0 | |||
| ae3bc378d6 | |||
| 2ab1c5b387 | |||
| a2f52db452 | |||
| 8c2a39b5af | |||
| 02d003127f | |||
| 57f675c537 | |||
| d89e305c87 | |||
| 443e3d5fa6 | |||
| cf75a09011 | |||
| 45c20e34e4 | |||
| 2ce75dc415 | |||
| 8b3ddeed05 | |||
| 9c74c25961 | |||
| 3228502108 | |||
| 94fe1f4c63 | |||
| 20d27e4fb6 | |||
| 389238f622 | |||
| fd5b0e144d | |||
| 2c895974b2 | |||
| bfeca4a0bf | |||
| 4979d8fded | |||
| f9b6aed6b6 | |||
| 156546ad05 | |||
| 1a05ba3215 | |||
| 3bda2b7124 | |||
| 0b902f32f6 | |||
| fb6c325cc0 | |||
| c52bfc0586 | |||
| a33b14338f | |||
| 2ede74fa9c | |||
| 6f2a13fafd | |||
| 86ca6fee16 | |||
| 6baa60d454 | |||
| 92410395e3 | |||
| d03af15516 | |||
| 9ede1d07a0 | |||
| c65b0a199e | |||
| d781f299c0 | |||
| 6fc4898a1b | |||
| 1e73439955 | |||
| 566621f7e3 | |||
| 0c15be004e | |||
| 1c6ef3fe38 | |||
| f516431260 | |||
| d254bff197 | |||
| 6fc5a78252 | |||
| b4c61f20e7 | |||
| 0c477f14cc | |||
| c38f053905 | |||
| 88c2a25f12 | |||
| dd76ed7a0a | |||
| d58e48d9f5 | |||
| 8973d1ad8a | |||
| 837e8960c2 | |||
| 8a6c52b018 | |||
| 7f561fb53d | |||
| c4896aca9b | |||
| 3b093754c8 | |||
| 8e16e5ea35 | |||
| dedb58f7ed | |||
| f4b5738869 | |||
| 21a007bb15 | |||
| d3d414c3c7 | |||
| 3c34e69cc2 | |||
| 2aae0a4e0c | |||
| 156b3696a7 | |||
| 526fc391ee | |||
| 96fd3c10fb | |||
| fe5560400c | |||
| 6090eb0b42 | |||
| c36174b263 | |||
| 3804b1c602 | |||
| 6e5af9dccb | |||
| 40b33bca59 | |||
| 3253d2de89 | |||
| eaf54b54c3 | |||
| c596ff5c38 | |||
| cc90a956f7 | |||
| 6dbd684279 | |||
| faf3c7831f | |||
| 59b78cc513 | |||
| 14911fb32f | |||
| 4a81814dbb | |||
| 1451f30781 | |||
| 4b54ca46d3 | |||
| 5115684baf | |||
| 6630b16771 | |||
| d1d5b54f12 | |||
| 646fd3edcc | |||
| 9908967954 | |||
| 169392e132 | |||
| c050642290 | |||
| 7636eecb48 | |||
| 5fd3456f1e | |||
| d890827bae | |||
| 3758e5155c | |||
| 07a1aea9c7 | |||
| 376f13f8ef | |||
| 04cce0bafd | |||
| b80872b762 | |||
| 8feb8198fe | |||
| da7b68a643 | |||
| c97def0b98 | |||
| e83793ba7f | |||
| 4e863c5a75 | |||
| 18966d048c | |||
| a6ff95a69c | |||
| f7c3e6480a | |||
| 7e6612a920 | |||
| 9333ed22f6 | |||
| d868f58e6e | |||
| 4b6fabbd2f | |||
| cba2b6eaf4 | |||
| 84b3335e71 | |||
| c3e18bc173 | |||
| c942c04aa0 | |||
| ad80b94e14 | |||
| 34747baed0 | |||
| 8513ec7440 | |||
| 7c5b2db5bf | |||
| 2e07a72119 | |||
| 9ba5622efd | |||
| 3ff81a47c9 | |||
| dcec61ab7a | |||
| 4922e0e439 | |||
| 7adacd5ce5 | |||
| 08d89f7aac | |||
| d250a5155a | |||
| 52176edcbf | |||
| ae279eaced | |||
| 18e72d5c01 | |||
| 148f5c459e | |||
| 667eb42a63 | |||
| bd23798323 | |||
| 961084dfe0 | |||
| 3e5aa8d734 | |||
| 1bfcaecc9b | |||
| eed14214b5 | |||
| 4b7d1e3a2c | |||
| 08a99e275e | |||
| 44d14811d3 | |||
| 900ee11f3b | |||
| 89b85c81a7 | |||
| 51aaaae94e | |||
| aa10d23de4 | |||
| 10610a6f54 | |||
| 5e26786bab | |||
| 3b1af692cc | |||
| b9ae996dfc | |||
| 2d50b758c4 | |||
| 450cc212a2 | |||
| a9d100eeee | |||
| dde8f4489f | |||
| f9f7a7cd78 | |||
| 6de95a2b37 | |||
| f00756bc7c | |||
| b00d120514 | |||
| 2dc4eea62f | |||
| db8fa4d505 | |||
| 525621c4d8 | |||
| 01e90bbd0e | |||
| af1c06917d | |||
| bcff7d74cd | |||
| 170d6bfdad | |||
| 2f7046f572 | |||
| 3b8dd0a5ac | |||
| 9da318af96 | |||
| b45bfcafc2 | |||
| 81181c346a | |||
| 1378cce9fb | |||
| 12f765c133 | |||
| 06c6af9b0d | |||
| 181c814e69 | |||
| 0eda44186a | |||
| 48eb477755 | |||
| c43d2e2edc | |||
| 0d4010841e | |||
| cc9278666d | |||
| c87925cee7 | |||
| 5b6c127283 | |||
| 4cd9e2e9b0 | |||
| 376223c87d | |||
| abbea39855 | |||
| ce80637891 | |||
| 78d0dbb519 | |||
| 85704c890e | |||
| 7681200ee7 | |||
| 5e4ed68fdd | |||
| 6fd40a8f60 | |||
| 333249e7b0 | |||
| 49c4b205e4 | |||
| cf1ba98d01 | |||
| cb4222d2c2 | |||
| 3437e61f98 | |||
| 2573ba4a50 | |||
| bbde68c49e | |||
| 67e3d86bd8 | |||
| 37eaded63d | |||
| ccaccda6ca | |||
| 5ebbb3ff3e | |||
| 3c02ab83ed | |||
| 021161ea38 | |||
| 019c34f480 | |||
| b9ba0fc572 | |||
| 4a319c3c33 | |||
| fdf1418dfb | |||
| 5bcf5c6f7c | |||
| 52c77d89e9 | |||
| 4786e2bf53 | |||
| b53034fe62 | |||
| 87d4afda81 | |||
| 3a9e0ea8a7 | |||
| ba46d2bf21 | |||
| 27f9473422 | |||
| 4ce1ac0dcb | |||
| f99faac073 | |||
| d65d285378 | |||
| efb6abc43f | |||
| 3d57425dcb | |||
| d4caaebab0 | |||
| 8cc6dd629a | |||
| 391d7f2c6a | |||
| 10cb2322e9 | |||
| 9571aa1c68 | |||
| 97810c84a2 | |||
| d0076f5c14 | |||
| c110644107 | |||
| 05956d4028 | |||
| b2ca36f098 | |||
| 15253f3448 | |||
| 4e00c988c1 | |||
| c88c9620b3 | |||
| 2ac55659c8 | |||
| c64a322e19 | |||
| 25ea1f4c0f | |||
| 2f33e77ab4 | |||
| 33862d98ea | |||
| bbcc788f60 | |||
| 038dda97f8 | |||
| c504ac5a47 | |||
| 1ac0549881 | |||
| 616f728afa | |||
| 73c06347be | |||
| 4becce1c2b | |||
| 161a717639 | |||
| 28e63e9279 | |||
| 11de70ed08 | |||
| 9283fd2162 | |||
| 544f594592 | |||
| c21a8aa8b0 | |||
| 30d6ec3398 | |||
| ab83972c3e | |||
| 9e3f3bee71 | |||
| 2ba9939bd8 | |||
| 3a278cc66d | |||
| 6183f06846 | |||
| a34f9fa66c | |||
| f5db8bd1e8 | |||
| 317c6fbec2 | |||
| c354b1014d | |||
| a28ec3a0a8 | |||
| 6ad647d7ce | |||
| 8a283e39ff | |||
| 0d8a8432d1 | |||
| 3a8b5111d9 | |||
| b375a061df | |||
| 0e9cd2a35f | |||
| d6230824cb | |||
| edba535c8b | |||
| 79cf532c2f | |||
| f6b8ee76cd | |||
| 635cd29599 | |||
| 6f733512b5 | |||
| d6e05ddf2d | |||
| a92bd42236 | |||
| cebda46469 | |||
| f01fb19318 | |||
| 5e31856aaa | |||
| 1058fed339 | |||
| bcd09d7ad8 | |||
| abc56cde3d | |||
| 889fca98ac | |||
| 351a200747 | |||
| 01ceee8de5 | |||
| 0611192dac | |||
| be8bfad3af | |||
| 63fe45d92a | |||
| 5ce8ffd197 | |||
| 56c7b4065d | |||
| 680769d770 | |||
| 70cf754a2f | |||
| e6d360c1c6 | |||
| 8629182822 | |||
| 37ac3d8764 | |||
| d61b9c6294 | |||
| d8854a2f3f | |||
| 806c83591c | |||
| fd17df91fd | |||
| 384246c360 | |||
| e30391661d | |||
| 519927421e | |||
| 52c19272c6 | |||
| 0d5a7db238 | |||
| 064cd3b7af | |||
| 75f5dac16b | |||
| 6957ac5324 | |||
| d24c131130 | |||
| 0ab94478fe | |||
| 4b80b0f490 | |||
| 793581ebde | |||
| cc7f3101b7 | |||
| bddd3feb83 | |||
| 49c463b1d2 | |||
| 094eb3e236 | |||
| 3fed2ec3a1 | |||
| 357d5b4e6e | |||
| c873a597c7 | |||
| 4152034799 | |||
| 3c9cfa8a0f | |||
| b016f385e1 | |||
| f40d1f2329 | |||
| 4dc93bc485 | |||
| f73f1e0687 | |||
| 1b09ef5051 | |||
| c1932dd307 | |||
| 490ff0af5e | |||
| 16b9640af2 | |||
| 68f2b0ca59 | |||
| 2e3fbac9a0 | |||
| 1edc0d8002 | |||
| 18c11e84f3 | |||
| a6dc020dc4 | |||
| 07422d709e | |||
| a2da97bc4c | |||
| 2e809903ec | |||
| 6075629015 | |||
| 768fc7e341 | |||
| c20a2e4763 | |||
| e048b093b5 | |||
| 57ed5bf34b | |||
| 8b77ed164d | |||
| 3a8c40fd2f | |||
| 8ecedb81b3 | |||
| 3a481a7d7f | |||
| f19433f2d8 | |||
| 0b02a5e073 | |||
| e2b24f3c12 | |||
| c375fb1f70 | |||
| 74972fb751 | |||
| 15b6626a4c | |||
| ad3e5425d4 | |||
| c61d011076 | |||
| 8448f597b1 | |||
| c418118683 | |||
| fb3f30832c | |||
| 950c43b4e7 | |||
| a275d942d2 | |||
| 39e74d89e1 | |||
| 20afa3265a | |||
| dc4fcef884 | |||
| df50c8db3e | |||
| d04d7b2e57 | |||
| a3e37597aa | |||
| 679b668bbb | |||
| 676daa990b | |||
| 7c7fa69558 | |||
| 9ad92b16b0 | |||
| 3b899fe7e2 | |||
| 3209cf5c7a | |||
| 22376c2efa | |||
| 47667ab990 | |||
| 068dc7db28 | |||
| 3be4c0a893 | |||
| c91041b675 | |||
| a17cf742b2 | |||
| a102cd5f32 | |||
| d03b6aa1d6 | |||
| b32b4f3a59 | |||
| 502997d8e9 | |||
| fb4ad2f3ba | |||
| b3de1b8e95 | |||
| fb17e283d1 | |||
| 0b7a24fc14 | |||
| 75b540f25c | |||
| 8bf79cc9c4 | |||
| 7ff80bc09d | |||
| 8d6b0eb191 | |||
| 35c7e3960e | |||
| 8e8c3f5a7c | |||
| 268f3c853e | |||
| 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 | |||
| bf82ed7186 | |||
| 1b27a7baab | |||
| d40c10e00f | |||
| 5d022d2a24 | |||
| 433b72d493 | |||
| 7bc731dfa6 | |||
| 5cb6454824 | |||
| 9a68b3e910 | |||
| 38f9cbe3de | |||
| 76f94e998e | |||
| 71bb45c87b | |||
| 0521cdda88 | |||
| 6b1fbc9fe1 | |||
| 5243c2262a | |||
| a0c80ffc06 | |||
| a8614531ab | |||
| f0fe58f0ec | |||
| 8fb412ea74 | |||
| bf99361abb | |||
| 464b676ec5 | |||
| ac11b03cd3 | |||
| df2617d575 | |||
| a03a14797f | |||
| 20dd66cd5a | |||
| d720d6be6b | |||
| cb0c5f5a7b | |||
| 8691173fd8 | |||
| ddc3cf301a | |||
| b12115b61f | |||
| 029a69f5c1 | |||
| 80d1f12324 | |||
| e906eac049 | |||
| 32aa9e4b63 | |||
| 7162faaa4d | |||
| 1901b708b2 | |||
| d86750372a | |||
| 3a0e5c8894 | |||
| cf008eb9b1 | |||
| 0bf85af8e9 | |||
| 78bb66b0f2 | |||
| 246135364c | |||
| 468ef7a618 | |||
| 68b5eedde1 | |||
| 3d2befc84a | |||
| 8e9528de13 | |||
| 0c8d3e70cf | |||
| 47e631d5ff | |||
| e86003b692 | |||
| f6218cadd7 | |||
| 368a917466 | |||
| a6c5692586 | |||
| d0a2820af4 | |||
| 299b86bae3 | |||
| 1218e65ca1 | |||
| 496155525c | |||
| 7c37f4bc51 | |||
| 6793edc349 | |||
| 1777e7f3b7 | |||
| 6fa09e1783 | |||
| ae23f5a594 | |||
| deaa493644 | |||
| 2a126faae7 | |||
| 35304435d0 | |||
| 420255373d | |||
| 834d22f866 | |||
| 2a77665be7 | |||
| 14857ae0da | |||
| 7156ea016e | |||
| 0820210c7c | |||
| 9eb1fbd5c3 | |||
| ea9c217827 | |||
| 1fc020cf92 | |||
| 592ab0f350 | |||
| 0da2241367 | |||
| 37e8ef1542 | |||
| e995033811 | |||
| 4c0329014f | |||
| 72301dc861 | |||
| 84e863f803 | |||
| 7a79da589a | |||
| a481c7649f | |||
| ba4d9a5230 | |||
| e83594a2a3 | |||
| a8ea472121 | |||
| 1393fac402 | |||
| 46f1849c2f | |||
| 9900575f9c | |||
| 2ed10f13e9 | |||
| a23c9cd53d | |||
| 9ac2443b78 | |||
| f075120675 | |||
| b8f091e2f6 | |||
| 41f8b6926f | |||
| b4ed3fbcd0 | |||
| 83ec9f4fe7 | |||
| 3f65b8506b | |||
| 50074f0e96 | |||
| 31a51cca4f | |||
| 654dee8cd6 | |||
| 7028de9dbd | |||
| e4751513a4 | |||
| bd7e2d2875 | |||
| 6061fdf231 | |||
| 9b662e8244 | |||
| f51a640309 | |||
| 48906afaf4 | |||
| ef4d93507d | |||
| d1d79babe7 | |||
| 2eb8efe83b | |||
| dd57ec9fa0 | |||
| 0033eb4ed6 | |||
| 2ca8dd2faa | |||
| c2e3fed8dc | |||
| 91eb2be44c | |||
| d4ac90dae0 | |||
| 11a416156b | |||
| 3bf78d6f8c | |||
| 563b2a8505 | |||
| d37d6688d8 | |||
| 9ccbeec947 | |||
| 801f01e23f | |||
| ea398d7b7e | |||
| 40bf923d7d | |||
| e69a1d22f9 | |||
| 3f40452511 | |||
| 2ccf4cab8b | |||
| feac983869 | |||
| 66b27e6dc8 | |||
| 6072a537d9 | |||
| aecebf7981 | |||
| 4cbf47834d | |||
| 854715d9a7 | |||
| 9127777c32 | |||
| 6c816c55d1 | |||
| 209e02cf29 | |||
| 19686adc01 | |||
| 35667e81ea | |||
| d881cd3346 | |||
| ca51b1422a | |||
| da00bbebbf | |||
| 171535f680 | |||
| f06cf21341 | |||
| 254fb39567 | |||
| 29248dff15 | |||
| 40596433f1 | |||
| 65d450ab6d | |||
| 3be1216897 | |||
| b8a713fde3 | |||
| 271f2dc665 | |||
| 06b6c4bcf9 | |||
| cd4ab23fa9 | |||
| da323d3335 | |||
| 5598dd2c34 | |||
| 32f061a3e0 | |||
| f12d81f791 | |||
| 9b5cbc1334 | |||
| 49547fc710 | |||
| 40e378a5a6 | |||
| ea8fdd17c8 | |||
| a6566bfd5f | |||
| 8eb0a0b94e | |||
| 09cf489c6b | |||
| abefc0aba0 | |||
| 9a5290ea27 | |||
| 03b304f05c | |||
| b3176208e2 | |||
| 54e25e2fa6 | |||
| 1dfe737d0e | |||
| dc2f513619 | |||
| 83447663b4 | |||
| 0eb33d09ac | |||
| f67f98a1a7 | |||
| b581a79eed | |||
| 6c42f374c8 | |||
| 6535614d78 | |||
| 4879b56bb9 | |||
| 2fd83aa764 | |||
| fa82683ce2 | |||
| 93218a06ba | |||
| 714c3c59eb | |||
| 1a201cd180 | |||
| adcdefce43 | |||
| 049a024dea | |||
| 20bbebb299 | |||
| 24457660e3 | |||
| f1fb8cf03e | |||
| 18c6aceee7 | |||
| 24ddceea47 | |||
| 26f006509f | |||
| 82ec28b369 | |||
| db19f6395d | |||
| b677ff20fe | |||
| b98c1ec36a | |||
| 37ff9792be | |||
| 089503aab7 | |||
| 74155e3d88 | |||
| 3344ab909b | |||
| 564f33d5ef | |||
| 828b39587f | |||
| 83acd5b050 | |||
| 0605172d4d | |||
| 67798a7b52 | |||
| 089d938e15 | |||
| c00508d6cf | |||
| 83b0f4f394 | |||
| 2b981651e1 | |||
| 294cdba80c | |||
| 072a86149d | |||
| 09cb844421 | |||
| 842569c9e5 | |||
| 3568f6c61a | |||
| 8bb4c167e4 | |||
| 5bf213948c | |||
| dff7aa4d4b | |||
| c634acd9ff | |||
| 92c2fb45c7 | |||
| ed5c19f53e | |||
| cd9c0e03e7 | |||
| 03420f811b | |||
| 2dd34cf967 | |||
| a2072de391 | |||
| 526d9eae7f | |||
| 4565bcd757 | |||
| f22f439a68 | |||
| 7681d12eb0 | |||
| 5672284f53 | |||
| b077ae9259 | |||
| 367f381fea | |||
| 362e189aa5 | |||
| 96ae03b48f | |||
| b9221656cc | |||
| c30f6aa427 | |||
| c276e5b679 | |||
| f00a8cb3a3 | |||
| 220995b830 | |||
| e344f3f869 | |||
| 517a073f33 | |||
| dcba9475ba | |||
| 2e43412a82 | |||
| 48861b67dd | |||
| 61eaa9333b | |||
| 9c7b5cb3f5 | |||
| 53828cbfad | |||
| aa2028127f | |||
| a82659c837 | |||
| e3c602c43a | |||
| 02e9209e5c | |||
| 8a29bfba3f | |||
| 0c817f0441 | |||
| 326e27eab2 | |||
| 3415b04c73 | |||
| 5547361d18 | |||
| 805d643347 | |||
| a73ad52a8a | |||
| b20a71a0d3 | |||
| 13c32d80ba | |||
| 3171a8b0e2 | |||
| 3de4e931c7 | |||
| 93345de687 | |||
| bbf3c6ecac | |||
| ca4de8f191 | |||
| 858ea1a8c5 | |||
| 927d27b121 | |||
| 1df5e9f30c | |||
| dd50da314d | |||
| 1f068fcdb8 | |||
| 85d81024a4 | |||
| e7c3e1c516 | |||
| ed74b04520 | |||
| a268203832 | |||
| 6cfb11a360 | |||
| 9536dd14ef | |||
| c565df96f5 | |||
| 4b4a8af893 | |||
| 3b263c5c3d | |||
| 1e3161b161 | |||
| 6b50b7b72a | |||
| 96895c7759 | |||
| 96c494c6ad | |||
| 48548b29d6 | |||
| f18a85e193 | |||
| 55b3b28aec | |||
| 6a26db80b6 | |||
| ccf68878c7 | |||
| f7d195782f | |||
| bbab4faf7a | |||
| f7367ceae5 | |||
| 3fb98b6a97 | |||
| dc4f46df9f | |||
| 61c9cbc3f5 | |||
| 446b90172b | |||
| 80af7bedef | |||
| 6501176081 | |||
| f8c8873c99 | |||
| cc8c852c2b | |||
| ac35ffef9e | |||
| fe7f8e2de5 | |||
| 6b89cf94a3 | |||
| bd054a4918 | |||
| cb04c3b335 | |||
| 5c5d8e61ae | |||
| 0fef380202 | |||
| cc8361cd93 | |||
| 8b53fecc06 | |||
| 425d5a94cb | |||
| 38abee00e2 | |||
| 082784e023 | |||
| 155a37d3a6 | |||
| 0a5aff1b60 | |||
| 3d61d9e0d8 | |||
| f8a3253d12 | |||
| b4ba80b09e | |||
| 1a26e17f41 | |||
| f6045e8e11 | |||
| f0a3f776c2 | |||
| 74dc07e736 | |||
| c5eb4ed978 | |||
| bff81d3056 | |||
| c8bb7ded5d | |||
| 56f3bd86cf | |||
| 52ae97cffc | |||
| c7ca5c81c3 | |||
| ccb8db41f0 | |||
| 439bcd6715 | |||
| 4e33d0c293 | |||
| 46fe7f5bea | |||
| 9b4e0e9837 | |||
| 071d6a2a5a | |||
| 54904de77d | |||
| d7d0292654 | |||
| 2817dce78f | |||
| 698acd32a7 | |||
| f36327b380 | |||
| dbd32f8f11 | |||
| ea9c0ae6c0 | |||
| ef2b47e202 | |||
| c16f582ed8 | |||
| 6931106c5e | |||
| 045df81f14 | |||
| e210f08dce | |||
| 60e8dbf11d | |||
| 01414d0b3d | |||
| 2317bef021 | |||
| ad5bf2fac8 | |||
| ce5189a0a0 | |||
| a28ef22113 | |||
| 7a4ed38cd4 | |||
| fe55b49c7d | |||
| e32a92daa7 | |||
| d7970e4ab8 | |||
| 2f78b42133 | |||
| f4ef057e9e | |||
| c1fe57135e | |||
| 448786bfd0 | |||
| 0093d6e34d | |||
| 324c6057fd | |||
| 759ab23c45 | |||
| 1b62b6dd89 | |||
| 2c8d1b7bff | |||
| bd63c35b0b | |||
| c052a02592 | |||
| b3597f3d99 | |||
| 06bb6dbcff | |||
| 73e9de7b5f | |||
| e535ef37c7 | |||
| dd926af5a8 | |||
| a726b5109a | |||
| 7c817dd34b | |||
| c9165470f2 | |||
| 61816d0076 | |||
| 5ad853ef5b | |||
| aa2a067489 | |||
| dfbed616ba | |||
| 94214562d0 | |||
| be94e1a2fb | |||
| 714d380ec0 | |||
| f2ae106c32 | |||
| df7223f39c | |||
| 7f6f47b97b | |||
| b9e972c248 | |||
| 66a1be2d86 | |||
| 717915e6ac | |||
| c8c8f5722b | |||
| cd610b3ed1 | |||
| 7b20aefecf | |||
| 93deb0a584 | |||
| 05eab703cc | |||
| c0cd55a8fa | |||
| 35052f2113 | |||
| 53f891226e | |||
| cdc4497664 | |||
| 4fb4c95220 | |||
| 6cc084dbde | |||
| 974e10379a | |||
| 22ef48bec2 | |||
| b8b88e638b | |||
| 837327f530 | |||
| 4be813bbee | |||
| 65617f1e75 | |||
| e224c71119 | |||
| aaebf5749c | |||
| 5bc80fc094 | |||
| 75466fee8d | |||
| 24fa8793b1 | |||
| 4f10f559f7 | |||
| bedf5f26fe | |||
| 2f35e7756b | |||
| cc50af08e4 | |||
| d08e4081c2 | |||
| 2689bab652 | |||
| 9751987dc1 | |||
| d2906253f1 | |||
| e82dd14f0f | |||
| 4b8adf2dcc | |||
| c68552556f | |||
| b738a20233 | |||
| cca8fbd3de | |||
| 1dba8f6add | |||
| 25a1e8d414 | |||
| 8c6287ef7b | |||
| 322cbca0dc | |||
| 2685a35c3a | |||
| 21397a67c6 | |||
| e8ab53e76d | |||
| 8606d9b581 | |||
| 5b471a5349 | |||
| 08240bbcac | |||
| d719f3fc06 | |||
| 3d63cbf076 | |||
| 67f88482e6 | |||
| 938dff7bbe | |||
| b3f5d20ad8 | |||
| 162ccdd155 | |||
| f1594312cd | |||
| 7629ea5672 | |||
| 454a85978f | |||
| 49d688f99c | |||
| 033bebf8cd | |||
| 1ce22bdcc1 | |||
| 4e678479b9 | |||
| 2cbc7eed73 | |||
| 7a05f81844 | |||
| f69db61147 | |||
| 655f5eb1e2 | |||
| e45edff4a4 | |||
| 71b2154ec8 | |||
| fc309ee314 | |||
| a716391aab | |||
| 09b2e5d0fb | |||
| c6b2d2e1d9 | |||
| 8a5f655a5c | |||
| 1cf6d1dd9d | |||
| 3f14ebc4cf | |||
| 4d8f6c1b41 | |||
| 7150c23e93 | |||
| 8b8d147480 | |||
| 009179fed7 | |||
| 8fe21b8ef9 | |||
| 7c9f5d05db | |||
| 6dbd446fc8 | |||
| 110b809b7d | |||
| a45d438f05 | |||
| cabc41bcd6 | |||
| 5e625c8d2e | |||
| 3f648f54c5 | |||
| 5977bb05ca | |||
| 9332b3f690 | |||
| 5f13b2b35b | |||
| d61074268a | |||
| 01abd521e2 | |||
| cfb3a45479 | |||
| 7d7d7bcced | |||
| f91fae5dfb | |||
| 7169577f9c | |||
| 6dfd330fa5 | |||
| 72152ff1f3 | |||
| 7f9349b7ae | |||
| 1d6246d700 | |||
| 4c8f1910c8 | |||
| 5441796675 | |||
| 876e417eb4 | |||
| e6c812e2ab | |||
| 3b364c2a3d | |||
| 0e5e2f5d2e | |||
| 0930407d0e | |||
| 5bca25fe44 | |||
| ab253470f0 | |||
| 02f152c6e1 | |||
| 4bb12c4ba4 | |||
| 8d6d99731f | |||
| b5902f4fbf | |||
| 791802fdf7 | |||
| a455317122 | |||
| f4f8066877 | |||
| a8b400803b | |||
| d95ecbe0fd | |||
| fa85657801 | |||
| cba77c6909 | |||
| 424daede2f | |||
| b4eb352e1d | |||
| e92c9c5619 | |||
| 5fc937d558 | |||
| f926deda89 | |||
| c3e28728ce | |||
| f956d399ae | |||
| 7efd347da6 | |||
| 5e07141846 | |||
| 7660dbfd77 | |||
| 15a7f43c7b | |||
| 40543e0a58 | |||
| 097758baf3 | |||
| 96a429a561 | |||
| e5ee369e70 | |||
| f5bc084ce2 | |||
| 1721e42988 | |||
| eabb846d07 | |||
| d86cfc949d | |||
| 1d708510df | |||
| 280b0de646 | |||
| 1275b10d9c | |||
| 2738b3e50f | |||
| 169795d673 | |||
| 1f3dd3e2ee | |||
| 085565a771 | |||
| c227fbfdf2 | |||
| f3f3dc693a | |||
| 5e1a4740d7 | |||
| b4866e51b2 | |||
| a9fa813a75 | |||
| 806519f78a | |||
| d99c2cf31e | |||
| ffa431da7b | |||
| c3c5eaf914 | |||
| a91effcd86 | |||
| 07a5d8c91c | |||
| 0bd0578ff9 | |||
| a9b94241af | |||
| 2f3a73a59e | |||
| 7066dc4ba7 | |||
| 1e74d793a2 | |||
| 3d96e3f8c8 | |||
| d8215a62f6 | |||
| 841124af75 | |||
| dbc42f56b3 | |||
| 96fbcb26c9 | |||
| 6a6dd599a1 | |||
| 5e52259fb3 | |||
| e51c71bcd6 | |||
| 4d5e235166 | |||
| 5236d17ac4 | |||
| d9f659171b | |||
| 993a69d3a9 | |||
| 944305b9f1 | |||
| 6eb63f778e | |||
| eb240016ed | |||
| b674906e3a | |||
| bc7ba8cf2b | |||
| b4649137fc | |||
| c8b920a05d | |||
| c564725f46 | |||
| e599fec685 | |||
| 657fe1fa43 | |||
| 697d5e6247 | |||
| d8d7e0a762 | |||
| 73d30dd875 | |||
| 033548a760 | |||
| e416dfdbc0 | |||
| 592a3074e1 | |||
| b39e93d0d1 | |||
| 906c54faff | |||
| d3c3088c6b | |||
| f2c0b30641 | |||
| 601de66c03 | |||
| 91ea2d4c27 | |||
| b7884ddd02 | |||
| 9fec516560 | |||
| f469eff97b | |||
| 69cde11a51 | |||
| 7d2047cdbd | |||
| c76116b970 | |||
| 8d604350e4 | |||
| 4db724984e | |||
| 87942ed71d | |||
| dd871e0d8c | |||
| c1014f5989 | |||
| 6a2262969e | |||
| d39e44c209 | |||
| 4d15b58ca4 | |||
| 9342317291 | |||
| dfd74495bd | |||
| 90ffa95b6a | |||
| 825f160369 | |||
| 834e694e94 | |||
| ce3e9b0c29 | |||
| 1c7ceaa2ca | |||
| 7e0620a143 | |||
| fb039a3e0c | |||
| 05e4234525 | |||
| e19472568f | |||
| 845488af8d | |||
| b924b7b4c6 | |||
| b368fc03ea | |||
| 982a094646 | |||
| dfe320f172 | |||
| 2537c7c735 | |||
| 54853ee239 | |||
| b408cee29f | |||
| e63d89973a | |||
| b311088f5a | |||
| d9c534b13d | |||
| 95e311fd02 | |||
| 5b2aa0be81 | |||
| 33b4995547 | |||
| c16dab236a | |||
| 202c46035a | |||
| ea83d66fb5 | |||
| 9f85e397d4 | |||
| 7df2e2a8d2 | |||
| d446de62a4 | |||
| dd9f03d9b9 | |||
| 3c57d5518a | |||
| 5f98afc180 | |||
| 7492c0ea03 | |||
| 65a430bb42 | |||
| bd62f47afa | |||
| 3e0aa4c603 | |||
| 8b5bfdfa44 | |||
| da88ac8cca | |||
| a66f3e02f4 | |||
| 7f44861f32 | |||
| 63e20404a2 | |||
| 5c574b9878 | |||
| 9562a1c146 | |||
| ed4404f350 | |||
| bd55b647c7 | |||
| 19c7527376 |
+6
-6
@@ -123,13 +123,13 @@ define the source file coding standards we use along with some IDEA editor setti
|
||||
|
||||
### Reference Docs
|
||||
|
||||
The reference documentation is in the [framework-docs/src/docs/asciidoc](framework-docs/src/docs/asciidoc) directory, in
|
||||
[Asciidoctor](https://asciidoctor.org/) format. For trivial changes, you may be able to browse,
|
||||
edit source files, and submit directly from GitHub.
|
||||
The reference documentation is authored in [Asciidoctor](https://asciidoctor.org/) format
|
||||
using [Antora](https://docs.antora.org/antora/latest/). The source files for the documentation
|
||||
reside in the [framework-docs/modules/ROOT](framework-docs/modules/ROOT) directory. For
|
||||
trivial changes, you may be able to browse, edit source files, and submit directly from GitHub.
|
||||
|
||||
When making changes locally, execute `./gradlew :framework-docs:asciidoctor` and then browse the result under
|
||||
`framework-docs/build/docs/ref-docs/html5/index.html`.
|
||||
When making changes locally, execute `./gradlew antora` and then browse the results under
|
||||
`framework-docs/build/site/index.html`.
|
||||
|
||||
Asciidoctor also supports live editing. For more details see
|
||||
[AsciiDoc Tooling](https://docs.asciidoctor.org/asciidoctor/latest/tooling/).
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
This is the home of the Spring Framework: the foundation for all [Spring projects](https://spring.io/projects). Collectively the Spring Framework and the family of Spring projects are often referred to simply as "Spring".
|
||||
|
||||
Spring provides everything required beyond the Java programming language for creating enterprise applications for a wide range of scenarios and architectures. Please read the [Overview](https://docs.spring.io/spring/docs/current/spring-framework-reference/overview.html#spring-introduction) section as reference for a more complete introduction.
|
||||
Spring provides everything required beyond the Java programming language for creating enterprise applications for a wide range of scenarios and architectures. Please read the [Overview](https://docs.spring.io/spring-framework/reference/overview.html) section of the reference documentation for a more complete introduction.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
@@ -14,7 +14,7 @@ For access to artifacts or a distribution zip, see the [Spring Framework Artifac
|
||||
|
||||
## Documentation
|
||||
|
||||
The Spring Framework maintains reference documentation ([published](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/) and [source](framework-docs/src/docs/asciidoc)), GitHub [wiki pages](https://github.com/spring-projects/spring-framework/wiki), and an
|
||||
The Spring Framework maintains reference documentation ([published](https://docs.spring.io/spring-framework/reference/) and [source](framework-docs/modules/ROOT)), GitHub [wiki pages](https://github.com/spring-projects/spring-framework/wiki), and an
|
||||
[API reference](https://docs.spring.io/spring-framework/docs/current/javadoc-api/). There are also [guides and tutorials](https://spring.io/guides) across Spring projects.
|
||||
|
||||
## Micro-Benchmarks
|
||||
@@ -31,7 +31,7 @@ Information regarding CI builds can be found in the [Spring Framework Concourse
|
||||
|
||||
## Stay in Touch
|
||||
|
||||
Follow [@SpringCentral](https://twitter.com/springcentral), [@SpringFramework](https://twitter.com/springframework), and its [team members](https://twitter.com/springframework/lists/team/members) on Twitter. In-depth articles can be found at [The Spring Blog](https://spring.io/blog/), and releases are announced via our [news feed](https://spring.io/blog/category/news).
|
||||
Follow [@SpringCentral](https://twitter.com/springcentral), [@SpringFramework](https://twitter.com/springframework), and its [team members](https://twitter.com/springframework/lists/team/members) on Twitter. In-depth articles can be found at [The Spring Blog](https://spring.io/blog/), and releases are announced via our [releases feed](https://spring.io/blog/category/releases).
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+12
-69
@@ -1,16 +1,14 @@
|
||||
plugins {
|
||||
id 'io.spring.nohttp' version '0.0.11'
|
||||
id 'io.freefair.aspectj' version '8.0.1' apply false
|
||||
// kotlinVersion is managed in gradle.properties
|
||||
id 'org.jetbrains.kotlin.plugin.serialization' version "${kotlinVersion}" apply false
|
||||
id 'org.jetbrains.dokka' version '1.8.10'
|
||||
id 'org.asciidoctor.jvm.convert' version '3.3.2' apply false
|
||||
id 'org.asciidoctor.jvm.pdf' version '3.3.2' apply false
|
||||
id 'org.jetbrains.dokka' version '1.8.20'
|
||||
id 'org.unbroken-dome.xjc' version '2.0.0' apply false
|
||||
id 'com.github.ben-manes.versions' version '0.46.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
|
||||
id 'me.champeau.mrjar' version '0.1.1'
|
||||
}
|
||||
|
||||
ext {
|
||||
@@ -28,7 +26,6 @@ configure(allprojects) { project ->
|
||||
includeGroup 'io.projectreactor.netty'
|
||||
}
|
||||
}
|
||||
maven { url "https://repo.spring.io/libs-spring-framework-build" }
|
||||
if (version.contains('-')) {
|
||||
maven { url "https://repo.spring.io/milestone" }
|
||||
}
|
||||
@@ -49,7 +46,6 @@ configure([rootProject] + javaProjects) { project ->
|
||||
|
||||
apply plugin: "java"
|
||||
apply plugin: "java-test-fixtures"
|
||||
apply plugin: "checkstyle"
|
||||
apply plugin: 'org.springframework.build.conventions'
|
||||
apply from: "${rootDir}/gradle/toolchains.gradle"
|
||||
apply from: "${rootDir}/gradle/ide.gradle"
|
||||
@@ -63,33 +59,6 @@ configure([rootProject] + javaProjects) { project ->
|
||||
matching { it.name.endsWith("Classpath") }.all { it.extendsFrom(dependencyManagement) }
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
include(["**/*Tests.class", "**/*Test.class"])
|
||||
systemProperty("java.awt.headless", "true")
|
||||
systemProperty("testGroups", project.properties.get("testGroups"))
|
||||
systemProperty("io.netty.leakDetection.level", "paranoid")
|
||||
systemProperty("io.netty5.leakDetectionLevel", "paranoid")
|
||||
systemProperty("io.netty5.leakDetection.targetRecords", "32")
|
||||
systemProperty("io.netty5.buffer.lifecycleTracingEnabled", "true")
|
||||
systemProperty("io.netty5.buffer.leakDetectionEnabled", "true")
|
||||
jvmArgs(["--add-opens=java.base/java.lang=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.util=ALL-UNNAMED"])
|
||||
}
|
||||
|
||||
checkstyle {
|
||||
toolVersion = "10.10.0"
|
||||
configDirectory.set(rootProject.file("src/checkstyle"))
|
||||
}
|
||||
|
||||
tasks.named("checkstyleMain").configure {
|
||||
maxHeapSize = "1g"
|
||||
}
|
||||
|
||||
tasks.named("checkstyleTest").configure {
|
||||
maxHeapSize = "1g"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
dependencyManagement(enforcedPlatform(dependencies.project(path: ":framework-platform")))
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api")
|
||||
@@ -109,17 +78,16 @@ 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")
|
||||
}
|
||||
|
||||
ext.javadocLinks = [
|
||||
"https://docs.oracle.com/en/java/javase/17/docs/api/",
|
||||
"https://jakarta.ee/specifications/platform/9/apidocs/",
|
||||
"https://docs.oracle.com/cd/E13222_01/wls/docs90/javadocs/", // CommonJ
|
||||
"https://www.ibm.com/docs/api/v1/content/SSEQTP_8.5.5/com.ibm.websphere.javadoc.doc/web/apidocs/",
|
||||
"https://docs.jboss.org/jbossas/javadoc/4.0.5/connector/",
|
||||
"https://docs.jboss.org/jbossas/javadoc/7.1.2.Final/",
|
||||
"https://www.eclipse.org/aspectj/doc/released/aspectj5rt-api/",
|
||||
"https://docs.oracle.com/cd/E13222_01/wls/docs90/javadocs/", // CommonJ and weblogic.* packages
|
||||
"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://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/",
|
||||
@@ -130,15 +98,13 @@ configure([rootProject] + javaProjects) { project ->
|
||||
// TODO Uncomment link to JUnit 5 docs once we execute Gradle with Java 18+.
|
||||
// See https://github.com/spring-projects/spring-framework/issues/27497
|
||||
//
|
||||
// "https://junit.org/junit5/docs/5.9.3/api/",
|
||||
// "https://junit.org/junit5/docs/5.10.0/api/",
|
||||
"https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/",
|
||||
"https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/",
|
||||
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
|
||||
// The external Javadoc link for JSR 305 must come last to ensure that types from
|
||||
// JSR 250 (such as @PostConstruct) are still supported. This is due to the fact
|
||||
// that JSR 250 and JSR 305 both define types in javax.annotation, which results
|
||||
// in a split package, and the javadoc tool does not support split packages
|
||||
// across multiple external Javadoc sites.
|
||||
// Previously there could be a split-package issue between JSR250 and JSR305 javax.annotation packages,
|
||||
// but since 6.0 JSR 250 annotations such as @Resource and @PostConstruct have been replaced by their
|
||||
// JakartaEE equivalents in the jakarta.annotation package.
|
||||
"https://www.javadoc.io/doc/com.google.code.findbugs/jsr305/3.0.2/"
|
||||
] as String[]
|
||||
}
|
||||
@@ -149,28 +115,5 @@ configure(moduleProjects) { project ->
|
||||
|
||||
configure(rootProject) {
|
||||
description = "Spring Framework"
|
||||
|
||||
apply plugin: "io.spring.nohttp"
|
||||
apply plugin: 'org.springframework.build.api-diff'
|
||||
|
||||
nohttp {
|
||||
source.exclude "**/test-output/**"
|
||||
source.exclude "**/.gradle/**"
|
||||
allowlistFile = project.file("src/nohttp/allowlist.lines")
|
||||
def rootPath = file(rootDir).toPath()
|
||||
def projectDirs = allprojects.collect { it.projectDir } + "${rootDir}/buildSrc"
|
||||
projectDirs.forEach { dir ->
|
||||
[ 'bin', 'build', 'out', '.settings' ]
|
||||
.collect { rootPath.relativize(new File(dir, it).toPath()) }
|
||||
.forEach { source.exclude "$it/**" }
|
||||
[ '.classpath', '.project' ]
|
||||
.collect { rootPath.relativize(new File(dir, it).toPath()) }
|
||||
.forEach { source.exclude "$it" }
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named("checkstyleNohttp").configure {
|
||||
maxHeapSize = "1g"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
plugins {
|
||||
id 'java-gradle-plugin'
|
||||
id 'checkstyle'
|
||||
id 'io.spring.javaformat' version "${javaFormatVersion}"
|
||||
}
|
||||
|
||||
repositories {
|
||||
@@ -17,10 +19,13 @@ ext {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
|
||||
implementation("org.jetbrains.kotlin:kotlin-compiler-embeddable:${kotlinVersion}")
|
||||
checkstyle "io.spring.javaformat:spring-javaformat-checkstyle:${javaFormatVersion}"
|
||||
implementation "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}"
|
||||
implementation "org.jetbrains.kotlin:kotlin-compiler-embeddable:${kotlinVersion}"
|
||||
implementation "me.champeau.gradle:japicmp-gradle-plugin:0.3.0"
|
||||
implementation "org.gradle:test-retry-gradle-plugin:1.4.1"
|
||||
implementation "io.spring.javaformat:spring-javaformat-gradle-plugin:${javaFormatVersion}"
|
||||
implementation "io.spring.nohttp:nohttp-gradle:0.0.11"
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
org.gradle.caching=true
|
||||
javaFormatVersion=0.0.38
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import io.spring.javaformat.gradle.SpringJavaFormatPlugin;
|
||||
import io.spring.nohttp.gradle.NoHttpExtension;
|
||||
import io.spring.nohttp.gradle.NoHttpPlugin;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.DependencySet;
|
||||
import org.gradle.api.plugins.JavaBasePlugin;
|
||||
import org.gradle.api.plugins.quality.Checkstyle;
|
||||
import org.gradle.api.plugins.quality.CheckstyleExtension;
|
||||
import org.gradle.api.plugins.quality.CheckstylePlugin;
|
||||
|
||||
/**
|
||||
* {@link Plugin} that applies conventions for checkstyle.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class CheckstyleConventions {
|
||||
|
||||
/**
|
||||
* Applies the Spring Java Format and Checkstyle plugins with the project conventions.
|
||||
* @param project the current project
|
||||
*/
|
||||
public void apply(Project project) {
|
||||
project.getPlugins().withType(JavaBasePlugin.class, (java) -> {
|
||||
if (project.getRootProject() == project) {
|
||||
configureNoHttpPlugin(project);
|
||||
}
|
||||
project.getPlugins().apply(CheckstylePlugin.class);
|
||||
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g"));
|
||||
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
|
||||
checkstyle.setToolVersion("10.12.1");
|
||||
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
|
||||
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
|
||||
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
|
||||
checkstyleDependencies
|
||||
.add(project.getDependencies().create("io.spring.javaformat:spring-javaformat-checkstyle:" + version));
|
||||
});
|
||||
}
|
||||
|
||||
private static void configureNoHttpPlugin(Project project) {
|
||||
project.getPlugins().apply(NoHttpPlugin.class);
|
||||
NoHttpExtension noHttp = project.getExtensions().getByType(NoHttpExtension.class);
|
||||
noHttp.setAllowlistFile(project.file("src/nohttp/allowlist.lines"));
|
||||
noHttp.getSource().exclude("**/test-output/**", "**/.settings/**",
|
||||
"**/.classpath", "**/.project", "**/.gradle/**");
|
||||
List<String> buildFolders = List.of("bin", "build", "out");
|
||||
project.allprojects(subproject -> {
|
||||
Path rootPath = project.getRootDir().toPath();
|
||||
Path projectPath = rootPath.relativize(subproject.getProjectDir().toPath());
|
||||
for (String buildFolder : buildFolders) {
|
||||
Path innerBuildDir = projectPath.resolve(buildFolder);
|
||||
noHttp.getSource().exclude(innerBuildDir + File.separator + "**");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
@@ -25,10 +25,8 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinBasePlugin;
|
||||
* Plugin to apply conventions to projects that are part of Spring Framework's build.
|
||||
* Conventions are applied in response to various plugins being applied.
|
||||
*
|
||||
* When the {@link JavaBasePlugin} is applied, the conventions in {@link TestConventions}
|
||||
* are applied.
|
||||
* When the {@link JavaBasePlugin} is applied, the conventions in {@link JavaConventions}
|
||||
* are applied.
|
||||
* <p>When the {@link JavaBasePlugin} is applied, the conventions in {@link CheckstyleConventions},
|
||||
* {@link TestConventions} and {@link JavaConventions} are applied.
|
||||
* When the {@link KotlinBasePlugin} is applied, the conventions in {@link KotlinConventions}
|
||||
* are applied.
|
||||
*
|
||||
@@ -38,8 +36,10 @@ public class ConventionsPlugin implements Plugin<Project> {
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
new CheckstyleConventions().apply(project);
|
||||
new JavaConventions().apply(project);
|
||||
new KotlinConventions().apply(project);
|
||||
new TestConventions().apply(project);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -24,7 +24,9 @@ import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.plugins.JavaBasePlugin;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.plugins.JavaPluginExtension;
|
||||
import org.gradle.api.tasks.compile.JavaCompile;
|
||||
import org.gradle.jvm.toolchain.JavaLanguageVersion;
|
||||
|
||||
/**
|
||||
* {@link Plugin} that applies conventions for compiling Java sources in Spring Framework.
|
||||
@@ -68,6 +70,8 @@ public class JavaConventions {
|
||||
* @param project the current project
|
||||
*/
|
||||
private void applyJavaCompileConventions(Project project) {
|
||||
project.getExtensions().getByType(JavaPluginExtension.class)
|
||||
.getToolchain().getLanguageVersion().set(JavaLanguageVersion.of(17));
|
||||
project.getTasks().withType(JavaCompile.class)
|
||||
.matching(compileTask -> compileTask.getName().equals(JavaPlugin.COMPILE_JAVA_TASK_NAME))
|
||||
.forEach(compileTask -> {
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.build;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.plugins.JavaBasePlugin;
|
||||
import org.gradle.api.tasks.testing.Test;
|
||||
@@ -41,11 +43,36 @@ class TestConventions {
|
||||
|
||||
private void configureTestConventions(Project project) {
|
||||
project.getTasks().withType(Test.class,
|
||||
test -> project.getPlugins().withType(TestRetryPlugin.class, testRetryPlugin -> {
|
||||
TestRetryTaskExtension testRetry = test.getExtensions().getByType(TestRetryTaskExtension.class);
|
||||
testRetry.getFailOnPassedAfterRetry().set(true);
|
||||
testRetry.getMaxRetries().set(isCi() ? 3 : 0);
|
||||
}));
|
||||
test -> {
|
||||
configureTests(project, test);
|
||||
configureTestRetryPlugin(project, test);
|
||||
});
|
||||
}
|
||||
|
||||
private void configureTests(Project project, Test test) {
|
||||
test.useJUnitPlatform();
|
||||
test.include("**/*Tests.class", "**/*Test.class");
|
||||
test.setSystemProperties(Map.of(
|
||||
"java.awt.headless", "true",
|
||||
"io.netty.leakDetection.level", "paranoid",
|
||||
"io.netty5.leakDetectionLevel", "paranoid",
|
||||
"io.netty5.leakDetection.targetRecords", "32",
|
||||
"io.netty5.buffer.lifecycleTracingEnabled", "true"
|
||||
));
|
||||
if (project.hasProperty("testGroups")) {
|
||||
test.systemProperty("testGroups", project.getProperties().get("testGroups"));
|
||||
}
|
||||
test.jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.util=ALL-UNNAMED",
|
||||
"-Djava.locale.providers=COMPAT");
|
||||
}
|
||||
|
||||
private void configureTestRetryPlugin(Project project, Test test) {
|
||||
project.getPlugins().withType(TestRetryPlugin.class, testRetryPlugin -> {
|
||||
TestRetryTaskExtension testRetry = test.getExtensions().getByType(TestRetryTaskExtension.class);
|
||||
testRetry.getFailOnPassedAfterRetry().set(true);
|
||||
testRetry.getMaxRetries().set(isCi() ? 3 : 0);
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isCi() {
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build.hint;
|
||||
|
||||
import org.gradle.api.file.ConfigurableFileCollection;
|
||||
import org.gradle.api.provider.SetProperty;
|
||||
import org.gradle.api.tasks.Classpath;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.process.CommandLineArgumentProvider;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Argument provider for registering the runtime hints agent with a Java process.
|
||||
*/
|
||||
public interface RuntimeHintsAgentArgumentProvider extends CommandLineArgumentProvider {
|
||||
|
||||
@Classpath
|
||||
ConfigurableFileCollection getAgentJar();
|
||||
|
||||
@Input
|
||||
SetProperty<String> getIncludedPackages();
|
||||
|
||||
@Input
|
||||
SetProperty<String> getExcludedPackages();
|
||||
|
||||
@Override
|
||||
default Iterable<String> asArguments() {
|
||||
StringBuilder packages = new StringBuilder();
|
||||
getIncludedPackages().get().forEach(packageName -> packages.append('+').append(packageName).append(','));
|
||||
getExcludedPackages().get().forEach(packageName -> packages.append('-').append(packageName).append(','));
|
||||
return Collections.singleton("-javaagent:" + getAgentJar().getSingleFile() + "=" + packages);
|
||||
}
|
||||
}
|
||||
+4
-27
@@ -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.
|
||||
@@ -16,38 +16,15 @@
|
||||
|
||||
package org.springframework.build.hint;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.gradle.api.model.ObjectFactory;
|
||||
import org.gradle.api.provider.SetProperty;
|
||||
|
||||
/**
|
||||
* Entry point to the DSL extension for the {@link RuntimeHintsAgentPlugin} Gradle plugin.
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class RuntimeHintsAgentExtension {
|
||||
public interface RuntimeHintsAgentExtension {
|
||||
|
||||
private final SetProperty<String> includedPackages;
|
||||
SetProperty<String> getIncludedPackages();
|
||||
|
||||
private final SetProperty<String> excludedPackages;
|
||||
|
||||
public RuntimeHintsAgentExtension(ObjectFactory objectFactory) {
|
||||
this.includedPackages = objectFactory.setProperty(String.class).convention(Collections.singleton("org.springframework"));
|
||||
this.excludedPackages = objectFactory.setProperty(String.class).convention(Collections.emptySet());
|
||||
}
|
||||
|
||||
public SetProperty<String> getIncludedPackages() {
|
||||
return this.includedPackages;
|
||||
}
|
||||
|
||||
public SetProperty<String> getExcludedPackages() {
|
||||
return this.excludedPackages;
|
||||
}
|
||||
|
||||
String asJavaAgentArgument() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
this.includedPackages.get().forEach(packageName -> builder.append('+').append(packageName).append(','));
|
||||
this.excludedPackages.get().forEach(packageName -> builder.append('-').append(packageName).append(','));
|
||||
return builder.toString();
|
||||
}
|
||||
SetProperty<String> getExcludedPackages();
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -16,41 +16,79 @@
|
||||
|
||||
package org.springframework.build.hint;
|
||||
|
||||
import org.gradle.api.JavaVersion;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.attributes.Bundling;
|
||||
import org.gradle.api.attributes.Category;
|
||||
import org.gradle.api.attributes.LibraryElements;
|
||||
import org.gradle.api.attributes.Usage;
|
||||
import org.gradle.api.attributes.java.TargetJvmVersion;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.tasks.bundling.Jar;
|
||||
import org.gradle.api.tasks.testing.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* {@link Plugin} that configures the {@code RuntimeHints} Java agent to test tasks.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class RuntimeHintsAgentPlugin implements Plugin<Project> {
|
||||
|
||||
public static final String RUNTIMEHINTS_TEST_TASK = "runtimeHintsTest";
|
||||
private static final String EXTENSION_NAME = "runtimeHintsAgent";
|
||||
private static final String CONFIGURATION_NAME = "testRuntimeHintsAgentJar";
|
||||
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
|
||||
project.getPlugins().withType(JavaPlugin.class, javaPlugin -> {
|
||||
RuntimeHintsAgentExtension agentExtension = project.getExtensions().create(EXTENSION_NAME,
|
||||
RuntimeHintsAgentExtension.class, project.getObjects());
|
||||
RuntimeHintsAgentExtension agentExtension = createRuntimeHintsAgentExtension(project);
|
||||
Test agentTest = project.getTasks().create(RUNTIMEHINTS_TEST_TASK, Test.class, test -> {
|
||||
test.useJUnitPlatform(options -> {
|
||||
options.includeTags("RuntimeHintsTests");
|
||||
});
|
||||
test.include("**/*Tests.class", "**/*Test.class");
|
||||
test.systemProperty("java.awt.headless", "true");
|
||||
});
|
||||
project.afterEvaluate(p -> {
|
||||
Jar jar = project.getRootProject().project("spring-core-test").getTasks().withType(Jar.class).named("jar").get();
|
||||
agentTest.jvmArgs("-javaagent:" + jar.getArchiveFile().get().getAsFile() + "=" + agentExtension.asJavaAgentArgument());
|
||||
test.systemProperty("org.graalvm.nativeimage.imagecode", "runtime");
|
||||
test.getJvmArgumentProviders().add(createRuntimeHintsAgentArgumentProvider(project, agentExtension));
|
||||
});
|
||||
project.getTasks().getByName("check", task -> task.dependsOn(agentTest));
|
||||
project.getDependencies().add(CONFIGURATION_NAME, project.project(":spring-core-test"));
|
||||
});
|
||||
}
|
||||
|
||||
private static RuntimeHintsAgentExtension createRuntimeHintsAgentExtension(Project project) {
|
||||
RuntimeHintsAgentExtension agentExtension = project.getExtensions().create(EXTENSION_NAME, RuntimeHintsAgentExtension.class);
|
||||
agentExtension.getIncludedPackages().convention(Collections.singleton("org.springframework"));
|
||||
agentExtension.getExcludedPackages().convention(Collections.emptySet());
|
||||
return agentExtension;
|
||||
}
|
||||
|
||||
private static RuntimeHintsAgentArgumentProvider createRuntimeHintsAgentArgumentProvider(
|
||||
Project project, RuntimeHintsAgentExtension agentExtension) {
|
||||
RuntimeHintsAgentArgumentProvider agentArgumentProvider = project.getObjects().newInstance(RuntimeHintsAgentArgumentProvider.class);
|
||||
agentArgumentProvider.getAgentJar().from(createRuntimeHintsAgentConfiguration(project));
|
||||
agentArgumentProvider.getIncludedPackages().set(agentExtension.getIncludedPackages());
|
||||
agentArgumentProvider.getExcludedPackages().set(agentExtension.getExcludedPackages());
|
||||
return agentArgumentProvider;
|
||||
}
|
||||
|
||||
private static Configuration createRuntimeHintsAgentConfiguration(Project project) {
|
||||
return project.getConfigurations().create(CONFIGURATION_NAME, configuration -> {
|
||||
configuration.setCanBeConsumed(false);
|
||||
configuration.setTransitive(false); // Only the built artifact is required
|
||||
configuration.attributes(attributes -> {
|
||||
attributes.attribute(Bundling.BUNDLING_ATTRIBUTE, project.getObjects().named(Bundling.class, Bundling.EXTERNAL));
|
||||
attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, Category.LIBRARY));
|
||||
attributes.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, project.getObjects().named(LibraryElements.class, LibraryElements.JAR));
|
||||
attributes.attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, Integer.valueOf(JavaVersion.current().getMajorVersion()));
|
||||
attributes.attribute(Usage.USAGE_ATTRIBUTE, project.getObjects().named(Usage.class, Usage.JAVA_RUNTIME));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,6 @@ RUN ./setup.sh
|
||||
|
||||
ENV JAVA_HOME /opt/openjdk/java17
|
||||
ENV JDK17 /opt/openjdk/java17
|
||||
ENV JDK20 /opt/openjdk/java20
|
||||
ENV JDK21 /opt/openjdk/java21
|
||||
|
||||
ENV PATH $JAVA_HOME/bin:$PATH
|
||||
|
||||
@@ -5,8 +5,8 @@ case "$1" in
|
||||
java17)
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/17.0.7+7/bellsoft-jdk17.0.7+7-linux-amd64.tar.gz"
|
||||
;;
|
||||
java20)
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/20.0.1+10/bellsoft-jdk20.0.1+10-linux-amd64.tar.gz"
|
||||
java21)
|
||||
echo "https://download.java.net/java/early_access/jdk21/31/GPL/openjdk-21-ea+31_linux-x64_bin.tar.gz"
|
||||
;;
|
||||
*)
|
||||
echo $"Unknown java version"
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ curl https://raw.githubusercontent.com/spring-io/concourse-java-scripts/v0.0.4/c
|
||||
|
||||
mkdir -p /opt/openjdk
|
||||
pushd /opt/openjdk > /dev/null
|
||||
for jdk in java17 java20
|
||||
for jdk in java17 java21
|
||||
do
|
||||
JDK_URL=$( /get-jdk-url.sh $jdk )
|
||||
mkdir $jdk
|
||||
|
||||
+2
-2
@@ -3,8 +3,8 @@ github-repo-name: "spring-projects/spring-framework"
|
||||
sonatype-staging-profile: "org.springframework"
|
||||
docker-hub-organization: "springci"
|
||||
artifactory-server: "https://repo.spring.io"
|
||||
branch: "6.0.x"
|
||||
milestone: "6.0.x"
|
||||
branch: "main"
|
||||
milestone: "6.1.x"
|
||||
build-name: "spring-framework"
|
||||
pipeline-name: "spring-framework"
|
||||
concourse-url: "https://ci.spring.io"
|
||||
|
||||
+10
-57
@@ -45,7 +45,7 @@ resource_types:
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: concourse/registry-image-resource
|
||||
tag: 1.5.0
|
||||
tag: 1.8.0
|
||||
- name: artifactory-resource
|
||||
type: registry-image
|
||||
source:
|
||||
@@ -57,19 +57,13 @@ resource_types:
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: concourse/github-release-resource
|
||||
tag: 1.5.5
|
||||
tag: 1.8.0
|
||||
- name: github-status-resource
|
||||
type: registry-image
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: dpb587/github-status-resource
|
||||
tag: master
|
||||
- name: pull-request
|
||||
type: registry-image
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: teliaoss/github-pr-resource
|
||||
tag: v0.23.0
|
||||
- name: slack-notification
|
||||
type: registry-image
|
||||
source:
|
||||
@@ -111,14 +105,6 @@ resources:
|
||||
username: ((artifactory-username))
|
||||
password: ((artifactory-password))
|
||||
build_name: ((build-name))
|
||||
- name: git-pull-request
|
||||
type: pull-request
|
||||
icon: source-pull
|
||||
source:
|
||||
access_token: ((github-ci-pull-request-token))
|
||||
repository: ((github-repo-name))
|
||||
base_branch: ((branch))
|
||||
ignore_paths: ["ci/*"]
|
||||
- name: repo-status-build
|
||||
type: github-status-resource
|
||||
icon: eye-check-outline
|
||||
@@ -127,14 +113,14 @@ resources:
|
||||
access_token: ((github-ci-status-token))
|
||||
branch: ((branch))
|
||||
context: build
|
||||
- name: repo-status-jdk20-build
|
||||
- name: repo-status-jdk21-build
|
||||
type: github-status-resource
|
||||
icon: eye-check-outline
|
||||
source:
|
||||
repository: ((github-repo-name))
|
||||
access_token: ((github-ci-status-token))
|
||||
branch: ((branch))
|
||||
context: jdk20-build
|
||||
context: jdk21-build
|
||||
- name: slack-alert
|
||||
type: slack-notification
|
||||
icon: slack
|
||||
@@ -231,7 +217,7 @@ jobs:
|
||||
"zip.type": "schema"
|
||||
get_params:
|
||||
threads: 8
|
||||
- name: jdk20-build
|
||||
- name: jdk21-build
|
||||
serial: true
|
||||
public: true
|
||||
plan:
|
||||
@@ -239,7 +225,7 @@ jobs:
|
||||
- get: git-repo
|
||||
- get: every-morning
|
||||
trigger: true
|
||||
- put: repo-status-jdk20-build
|
||||
- put: repo-status-jdk21-build
|
||||
params: { state: "pending", commit: "git-repo" }
|
||||
- do:
|
||||
- task: check-project
|
||||
@@ -248,48 +234,17 @@ jobs:
|
||||
privileged: true
|
||||
timeout: ((task-timeout))
|
||||
params:
|
||||
TEST_TOOLCHAIN: 20
|
||||
TEST_TOOLCHAIN: 21
|
||||
<<: *build-project-task-params
|
||||
on_failure:
|
||||
do:
|
||||
- put: repo-status-jdk20-build
|
||||
- put: repo-status-jdk21-build
|
||||
params: { state: "failure", commit: "git-repo" }
|
||||
- put: slack-alert
|
||||
params:
|
||||
<<: *slack-fail-params
|
||||
- put: repo-status-jdk20-build
|
||||
- put: repo-status-jdk21-build
|
||||
params: { state: "success", commit: "git-repo" }
|
||||
- name: build-pull-requests
|
||||
serial: true
|
||||
public: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
resource: git-pull-request
|
||||
trigger: true
|
||||
version: every
|
||||
- do:
|
||||
- put: git-pull-request
|
||||
params:
|
||||
path: git-repo
|
||||
status: pending
|
||||
- task: build-pr
|
||||
image: ci-image
|
||||
file: git-repo/ci/tasks/build-pr.yml
|
||||
privileged: true
|
||||
timeout: ((task-timeout))
|
||||
params:
|
||||
BRANCH: ((branch))
|
||||
on_success:
|
||||
put: git-pull-request
|
||||
params:
|
||||
path: git-repo
|
||||
status: success
|
||||
on_failure:
|
||||
put: git-pull-request
|
||||
params:
|
||||
path: git-repo
|
||||
status: failure
|
||||
- name: stage-milestone
|
||||
serial: true
|
||||
plan:
|
||||
@@ -441,10 +396,8 @@ jobs:
|
||||
|
||||
groups:
|
||||
- name: "builds"
|
||||
jobs: ["build", "jdk20-build"]
|
||||
jobs: ["build", "jdk21-build"]
|
||||
- name: "releases"
|
||||
jobs: ["stage-milestone", "stage-rc", "stage-release", "promote-milestone", "promote-rc", "promote-release", "create-github-release"]
|
||||
- name: "ci-images"
|
||||
jobs: ["build-ci-images"]
|
||||
- name: "pull-requests"
|
||||
jobs: [ "build-pull-requests" ]
|
||||
|
||||
@@ -4,5 +4,6 @@ set -e
|
||||
source $(dirname $0)/common.sh
|
||||
|
||||
pushd git-repo > /dev/null
|
||||
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false --no-daemon --max-workers=4 check
|
||||
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Porg.gradle.java.installations.fromEnv=JDK17,JDK21 \
|
||||
--no-daemon --max-workers=4 check
|
||||
popd > /dev/null
|
||||
|
||||
@@ -5,5 +5,6 @@ source $(dirname $0)/common.sh
|
||||
repository=$(pwd)/distribution-repository
|
||||
|
||||
pushd git-repo > /dev/null
|
||||
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false --no-daemon --max-workers=4 -PdeploymentRepository=${repository} build publishAllPublicationsToDeploymentRepository
|
||||
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Porg.gradle.java.installations.fromEnv=JDK17,JDK21 \
|
||||
--no-daemon --max-workers=4 -PdeploymentRepository=${repository} build publishAllPublicationsToDeploymentRepository
|
||||
popd > /dev/null
|
||||
|
||||
@@ -4,6 +4,6 @@ set -e
|
||||
source $(dirname $0)/common.sh
|
||||
|
||||
pushd git-repo > /dev/null
|
||||
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Porg.gradle.java.installations.fromEnv=JDK17,JDK20 \
|
||||
-PmainToolchain=${MAIN_TOOLCHAIN} -PtestToolchain=${TEST_TOOLCHAIN} --no-daemon --max-workers=4 check
|
||||
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Porg.gradle.java.installations.fromEnv=JDK17,JDK21 \
|
||||
-PmainToolchain=${MAIN_TOOLCHAIN} -PtestToolchain=${TEST_TOOLCHAIN} --no-daemon --max-workers=4 check antora
|
||||
popd > /dev/null
|
||||
|
||||
@@ -35,7 +35,8 @@ git add gradle.properties > /dev/null
|
||||
git commit -m"Release v$stageVersion" > /dev/null
|
||||
git tag -a "v$stageVersion" -m"Release v$stageVersion" > /dev/null
|
||||
|
||||
./gradlew --no-daemon --max-workers=4 -PdeploymentRepository=${repository} build publishAllPublicationsToDeploymentRepository
|
||||
./gradlew --no-daemon --max-workers=4 -PdeploymentRepository=${repository} -Porg.gradle.java.installations.fromEnv=JDK17,JDK21 \
|
||||
build publishAllPublicationsToDeploymentRepository
|
||||
|
||||
git reset --hard HEAD^ > /dev/null
|
||||
if [[ $nextVersion != $snapshotVersion ]]; then
|
||||
|
||||
@@ -11,7 +11,7 @@ apply from: "${rootDir}/gradle/publications.gradle"
|
||||
|
||||
antora {
|
||||
version = '3.2.0-alpha.2'
|
||||
playbook = layout.buildDirectory.file('cached-antora-playbook.yml').get().getAsFile()
|
||||
playbook = 'cached-antora-playbook.yml'
|
||||
playbookProvider {
|
||||
repository = 'spring-projects/spring-framework'
|
||||
branch = 'docs-build'
|
||||
@@ -45,10 +45,6 @@ tasks.create("generateAntoraResources") {
|
||||
dependsOn 'generateAntoraYml'
|
||||
}
|
||||
|
||||
tasks.named("check") {
|
||||
dependsOn 'antora'
|
||||
}
|
||||
|
||||
jar {
|
||||
enabled = false
|
||||
}
|
||||
@@ -65,7 +61,9 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
api(project(":spring-context"))
|
||||
api(project(":spring-jms"))
|
||||
api(project(":spring-web"))
|
||||
api("jakarta.jms:jakarta.jms-api")
|
||||
api("jakarta.servlet:jakarta.servlet-api")
|
||||
|
||||
implementation(project(":spring-core-test"))
|
||||
@@ -102,7 +100,7 @@ task api(type: Javadoc) {
|
||||
overview = "framework-docs/src/docs/api/overview.html"
|
||||
splitIndex = true
|
||||
links(project.ext.javadocLinks)
|
||||
addBooleanOption('Xdoclint:syntax', true) // only check syntax with doclint
|
||||
addBooleanOption('Xdoclint:syntax,reference', true) // only check syntax and reference with doclint
|
||||
addBooleanOption('Werror', true) // fail build on Javadoc warnings
|
||||
}
|
||||
source moduleProjects.collect { project ->
|
||||
@@ -227,4 +225,4 @@ publishing {
|
||||
artifact distZip
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,6 +130,7 @@
|
||||
**** xref:testing/testcontext-framework/ctx-management/web.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/web-mocks.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/caching.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/failure-threshold.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/hierarchies.adoc[]
|
||||
*** xref:testing/testcontext-framework/fixture-di.adoc[]
|
||||
*** xref:testing/testcontext-framework/web-scoped-beans.adoc[]
|
||||
@@ -266,6 +267,7 @@
|
||||
***** xref:web/webmvc/mvc-controller/ann-methods/jackson.adoc[]
|
||||
**** xref:web/webmvc/mvc-controller/ann-modelattrib-methods.adoc[]
|
||||
**** xref:web/webmvc/mvc-controller/ann-initbinder.adoc[]
|
||||
**** xref:web/webmvc/mvc-controller/ann-validation.adoc[]
|
||||
**** xref:web/webmvc/mvc-controller/ann-exceptionhandler.adoc[]
|
||||
**** xref:web/webmvc/mvc-controller/ann-advice.adoc[]
|
||||
*** xref:web/webmvc-functional.adoc[]
|
||||
@@ -360,6 +362,7 @@
|
||||
***** xref:web/webflux/controller/ann-methods/jackson.adoc[]
|
||||
**** xref:web/webflux/controller/ann-modelattrib-methods.adoc[]
|
||||
**** xref:web/webflux/controller/ann-initbinder.adoc[]
|
||||
**** xref:web/webflux/controller/ann-validation.adoc[]
|
||||
**** xref:web/webflux/controller/ann-exceptions.adoc[]
|
||||
**** xref:web/webflux/controller/ann-advice.adoc[]
|
||||
*** xref:web/webflux-functional.adoc[]
|
||||
@@ -414,6 +417,7 @@
|
||||
*** xref:integration/cache/plug.adoc[]
|
||||
*** xref:integration/cache/specific-config.adoc[]
|
||||
** xref:integration/observability.adoc[]
|
||||
** xref:integration/checkpoint-restore.adoc[]
|
||||
** xref:integration/appendix.adoc[]
|
||||
* xref:languages.adoc[]
|
||||
** xref:languages/kotlin.adoc[]
|
||||
|
||||
@@ -25,7 +25,7 @@ The following table lists all currently supported Spring properties.
|
||||
| `spring.beaninfo.ignore`
|
||||
| Instructs Spring to use the `Introspector.IGNORE_ALL_BEANINFO` mode when calling the
|
||||
JavaBeans `Introspector`. See
|
||||
{api-spring-framework}++/beans/CachedIntrospectionResults.html#IGNORE_BEANINFO_PROPERTY_NAME++[`CachedIntrospectionResults`]
|
||||
{api-spring-framework}++/beans/StandardBeanInfoFactory.html#IGNORE_BEANINFO_PROPERTY_NAME++[`CachedIntrospectionResults`]
|
||||
for details.
|
||||
|
||||
| `spring.expression.compiler.mode`
|
||||
@@ -39,11 +39,6 @@ resolvable otherwise. See
|
||||
{api-spring-framework}++/core/env/AbstractEnvironment.html#IGNORE_GETENV_PROPERTY_NAME++[`AbstractEnvironment`]
|
||||
for details.
|
||||
|
||||
| `spring.index.ignore`
|
||||
| Instructs Spring to ignore the components index located in
|
||||
`META-INF/spring.components`. See xref:core/beans/classpath-scanning.adoc#beans-scanning-index[Generating an Index of Candidate Components]
|
||||
.
|
||||
|
||||
| `spring.jdbc.getParameterType.ignore`
|
||||
| Instructs Spring to ignore `java.sql.ParameterMetaData.getParameterType` completely.
|
||||
See the note in xref:data-access/jdbc/advanced.adoc#jdbc-batch-list[Batch Operations with a List of Objects].
|
||||
@@ -60,19 +55,27 @@ for details.
|
||||
{api-spring-framework}++/objenesis/SpringObjenesis.html#IGNORE_OBJENESIS_PROPERTY_NAME++[`SpringObjenesis`]
|
||||
for details.
|
||||
|
||||
| `spring.test.aot.processing.failOnError`
|
||||
| A boolean flag that controls whether errors encountered during AOT processing in the
|
||||
_Spring TestContext Framework_ should result in an exception that fails the overall process.
|
||||
See xref:testing/testcontext-framework/aot.adoc[Ahead of Time Support for Tests].
|
||||
|
||||
| `spring.test.constructor.autowire.mode`
|
||||
| The default _test constructor autowire mode_ to use if `@TestConstructor` is not present
|
||||
on a test class. See xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-testconstructor[Changing the default test constructor autowire mode]
|
||||
.
|
||||
on a test class. See xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-testconstructor[Changing the default test constructor autowire mode].
|
||||
|
||||
| `spring.test.context.cache.maxSize`
|
||||
| The maximum size of the context cache in the _Spring TestContext Framework_. See
|
||||
xref:testing/testcontext-framework/ctx-management/caching.adoc[Context Caching].
|
||||
|
||||
| `spring.test.context.failure.threshold`
|
||||
| The failure threshold for errors encountered while attempting to load an `ApplicationContext`
|
||||
in the _Spring TestContext Framework_. See
|
||||
xref:testing/testcontext-framework/ctx-management/failure-threshold.adoc[Context Failure Threshold].
|
||||
|
||||
| `spring.test.enclosing.configuration`
|
||||
| The default _enclosing configuration inheritance mode_ to use if
|
||||
`@NestedTestConfiguration` is not present on a test class. See
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration[Changing the default enclosing configuration inheritance mode]
|
||||
.
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration[Changing the default enclosing configuration inheritance mode].
|
||||
|
||||
|===
|
||||
|
||||
@@ -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
|
||||
@@ -728,11 +728,6 @@ of determining parameter names, an exception will be thrown.
|
||||
`StandardReflectionParameterNameDiscoverer` :: Uses the standard `java.lang.reflect.Parameter`
|
||||
API to determine parameter names. Requires that code be compiled with the `-parameters`
|
||||
flag for `javac`. Recommended approach on Java 8+.
|
||||
`LocalVariableTableParameterNameDiscoverer` :: Analyzes the local variable table available
|
||||
in the byte code of the advice class to determine parameter names from debug information.
|
||||
Requires that code be compiled with debug symbols (`-g:vars` at a minimum). Deprecated
|
||||
as of Spring Framework 6.0 for removal in Spring Framework 6.1 in favor of compiling
|
||||
code with `-parameters`. Not supported in a GraalVM native image.
|
||||
`AspectJAdviceParameterNameDiscoverer` :: Deduces parameter names from the pointcut
|
||||
expression, `returning`, and `throwing` clauses. See the
|
||||
{api-spring-framework}/aop/aspectj/AspectJAdviceParameterNameDiscoverer.html[javadoc]
|
||||
@@ -893,7 +888,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? {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -256,6 +256,13 @@ Java::
|
||||
|
||||
If you are registering bean definitions programmatically, consider using `RootBeanBefinition` as it allows to specify a `ResolvableType` that handles generics.
|
||||
|
||||
[[aot.bestpractices.constructors]]
|
||||
=== Avoid Multiple Constructors
|
||||
The container is able to choose the most appropriate constructor to use based on several candidates.
|
||||
However, this is not a best practice and flagging the preferred constructor with `@Autowired` if necessary is preferred.
|
||||
|
||||
In case you are working on a code base that you can't modify, you can set the {api-spring-framework}/beans/factory/support/AbstractBeanDefinition.html#PREFERRED_CONSTRUCTORS_ATTRIBUTE[`preferredConstructors` attribute] on the related bean definition to indicate which constructor should be used.
|
||||
|
||||
[[aot.bestpractices.factory-bean]]
|
||||
=== FactoryBean
|
||||
|
||||
@@ -318,6 +325,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -989,68 +989,4 @@ metadata is provided per-instance rather than per-class.
|
||||
|
||||
|
||||
|
||||
[[beans-scanning-index]]
|
||||
== Generating an Index of Candidate Components
|
||||
|
||||
While classpath scanning is very fast, it is possible to improve the startup performance
|
||||
of large applications by creating a static list of candidates at compilation time. In this
|
||||
mode, all modules that are targets of component scanning must use this mechanism.
|
||||
|
||||
NOTE: Your existing `@ComponentScan` or `<context:component-scan/>` directives must remain
|
||||
unchanged to request the context to scan candidates in certain packages. When the
|
||||
`ApplicationContext` detects such an index, it automatically uses it rather than scanning
|
||||
the classpath.
|
||||
|
||||
To generate the index, add an additional dependency to each module that contains
|
||||
components that are targets for component scan directives. The following example shows
|
||||
how to do so with Maven:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes,attributes"]
|
||||
----
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context-indexer</artifactId>
|
||||
<version>{spring-version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
----
|
||||
|
||||
With Gradle 4.5 and earlier, the dependency should be declared in the `compileOnly`
|
||||
configuration, as shown in the following example:
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,quotes,attributes"]
|
||||
----
|
||||
dependencies {
|
||||
compileOnly "org.springframework:spring-context-indexer:{spring-version}"
|
||||
}
|
||||
----
|
||||
|
||||
With Gradle 4.6 and later, the dependency should be declared in the `annotationProcessor`
|
||||
configuration, as shown in the following example:
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,quotes,attributes"]
|
||||
----
|
||||
dependencies {
|
||||
annotationProcessor "org.springframework:spring-context-indexer:{spring-version}"
|
||||
}
|
||||
----
|
||||
|
||||
The `spring-context-indexer` artifact generates a `META-INF/spring.components` file that
|
||||
is included in the jar file.
|
||||
|
||||
NOTE: When working with this mode in your IDE, the `spring-context-indexer` must be
|
||||
registered as an annotation processor to make sure the index is up-to-date when
|
||||
candidate components are updated.
|
||||
|
||||
TIP: The index is enabled automatically when a `META-INF/spring.components` file is found
|
||||
on the classpath. If an index is partially available for some libraries (or use cases)
|
||||
but could not be built for the whole application, you can fall back to a regular classpath
|
||||
arrangement (as though no index were present at all) by setting `spring.index.ignore` to
|
||||
`true`, either as a JVM system property or via the
|
||||
xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -910,7 +910,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 +924,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
|
||||
|
||||
@@ -771,11 +771,9 @@ resolved to the corresponding value. If not, then `default/path` is used
|
||||
as a default. If no default is specified and a property cannot be resolved, an
|
||||
`IllegalArgumentException` is thrown.
|
||||
|
||||
NOTE: The `@PropertySource` annotation is repeatable, according to Java 8 conventions.
|
||||
However, all such `@PropertySource` annotations need to be declared at the same
|
||||
level, either directly on the configuration class or as meta-annotations within the
|
||||
same custom annotation. Mixing direct annotations and meta-annotations is not
|
||||
recommended, since direct annotations effectively override meta-annotations.
|
||||
NOTE: `@PropertySource` can be used as a repeatable annotation. `@PropertySource`
|
||||
may also be used as a meta-annotation to create custom composed annotations with
|
||||
attribute overrides.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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]
|
||||
@@ -223,31 +248,41 @@ 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
|
||||
`(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.
|
||||
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 for `@Bean` methods in Java configuration classes.
|
||||
|
||||
[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 +442,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`"
|
||||
|
||||
@@ -38,7 +38,6 @@ The expression language supports the following functionality:
|
||||
* Class expressions
|
||||
* Accessing properties, arrays, lists, and maps
|
||||
* Method invocation
|
||||
* Relational operators
|
||||
* Assignment
|
||||
* Calling constructors
|
||||
* Bean references
|
||||
@@ -47,7 +46,9 @@ The expression language supports the following functionality:
|
||||
* Inline maps
|
||||
* Ternary operator
|
||||
* Variables
|
||||
* User-defined functions
|
||||
* User-defined functions added to the context
|
||||
* reflective invocation of `Method`
|
||||
* various cases of `MethodHandle`
|
||||
* Collection projection
|
||||
* Collection selection
|
||||
* Templated expressions
|
||||
|
||||
@@ -15,7 +15,7 @@ topics:
|
||||
* xref:core/expressions/language-ref/types.adoc[Types]
|
||||
* xref:core/expressions/language-ref/constructors.adoc[Constructors]
|
||||
* xref:core/expressions/language-ref/variables.adoc[Variables]
|
||||
* xref:core/expressions/language-ref/functions.adoc[Functions]
|
||||
* xref:core/expressions/language-ref/functions.adoc[User-Defined Functions]
|
||||
* xref:core/expressions/language-ref/bean-references.adoc[Bean References]
|
||||
* xref:core/expressions/language-ref/operator-ternary.adoc[Ternary Operator (If-Then-Else)]
|
||||
* xref:core/expressions/language-ref/operator-elvis.adoc[The Elvis Operator]
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
|
||||
You can extend SpEL by registering user-defined functions that can be called within the
|
||||
expression string. The function is registered through the `EvaluationContext`. The
|
||||
following example shows how to register a user-defined function:
|
||||
following example shows how to register a user-defined function to be invoked via reflection
|
||||
(i.e. a `Method`):
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -94,5 +95,97 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
The use of `MethodHandle` is also supported. This enables potentially more efficient use
|
||||
cases if the `MethodHandle` target and parameters have been fully bound prior to
|
||||
registration, but partially bound handles are also supported.
|
||||
|
||||
Consider the `String#formatted(String, Object...)` instance method, which produces a
|
||||
message according to a template and a variable number of arguments.
|
||||
|
||||
You can register and use the `formatted` method as a `MethodHandle`, as the following
|
||||
example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
|
||||
|
||||
MethodHandle mh = MethodHandles.lookup().findVirtual(String.class, "formatted",
|
||||
MethodType.methodType(String.class, Object[].class));
|
||||
context.setVariable("message", mh);
|
||||
|
||||
String message = parser.parseExpression("#message('Simple message: <%s>', 'Hello World', 'ignored')")
|
||||
.getValue(context, String.class);
|
||||
//returns "Simple message: <Hello World>"
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
|
||||
|
||||
val mh = MethodHandles.lookup().findVirtual(String::class.java, "formatted",
|
||||
MethodType.methodType(String::class.java, Array<Any>::class.java))
|
||||
context.setVariable("message", mh)
|
||||
|
||||
val message = parser.parseExpression("#message('Simple message: <%s>', 'Hello World', 'ignored')")
|
||||
.getValue(context, String::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
As hinted above, binding a `MethodHandle` and registering the bound `MethodHandle` is also
|
||||
supported. This is likely to be more performant if both the target and all the arguments
|
||||
are bound. In that case no arguments are necessary in the SpEL expression, as the
|
||||
following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
|
||||
|
||||
String template = "This is a %s message with %s words: <%s>";
|
||||
Object varargs = new Object[] { "prerecorded", 3, "Oh Hello World!", "ignored" };
|
||||
MethodHandle mh = MethodHandles.lookup().findVirtual(String.class, "formatted",
|
||||
MethodType.methodType(String.class, Object[].class))
|
||||
.bindTo(template)
|
||||
.bindTo(varargs); //here we have to provide arguments in a single array binding
|
||||
context.setVariable("message", mh);
|
||||
|
||||
String message = parser.parseExpression("#message()")
|
||||
.getValue(context, String.class);
|
||||
//returns "This is a prerecorded message with 3 words: <Oh Hello World!>"
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
|
||||
|
||||
val template = "This is a %s message with %s words: <%s>"
|
||||
val varargs = arrayOf("prerecorded", 3, "Oh Hello World!", "ignored")
|
||||
|
||||
val mh = MethodHandles.lookup().findVirtual(String::class.java, "formatted",
|
||||
MethodType.methodType(String::class.java, Array<Any>::class.java))
|
||||
.bindTo(template)
|
||||
.bindTo(varargs) //here we have to provide arguments in a single array binding
|
||||
context.setVariable("message", mh)
|
||||
|
||||
val message = parser.parseExpression("#message()")
|
||||
.getValue(context, String::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -38,6 +38,10 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: The SpEL Elvis operator also checks for _empty_ Strings in addition to `null` objects.
|
||||
The original snippet is thus only close to emulating the semantics of the operator (it would need an
|
||||
additional `!name.isEmpty()` check).
|
||||
|
||||
The following listing shows a more complex example:
|
||||
|
||||
[tabs]
|
||||
@@ -53,7 +57,7 @@ Java::
|
||||
String name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String.class);
|
||||
System.out.println(name); // Nikola Tesla
|
||||
|
||||
tesla.setName(null);
|
||||
tesla.setName("");
|
||||
name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String.class);
|
||||
System.out.println(name); // Elvis Presley
|
||||
----
|
||||
@@ -69,7 +73,7 @@ Kotlin::
|
||||
var name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String::class.java)
|
||||
println(name) // Nikola Tesla
|
||||
|
||||
tesla.setName(null)
|
||||
tesla.setName("")
|
||||
name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String::class.java)
|
||||
println(name) // Elvis Presley
|
||||
----
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
= Null-safety
|
||||
|
||||
Although Java does not let you express null-safety with its type system, the Spring Framework
|
||||
now provides the following annotations in the `org.springframework.lang` package to let you
|
||||
provides the following annotations in the `org.springframework.lang` package to let you
|
||||
declare nullability of APIs and fields:
|
||||
|
||||
* {api-spring-framework}/lang/Nullable.html[`@Nullable`]: Annotation to indicate that a
|
||||
specific parameter, return value, or field can be `null`.
|
||||
* {api-spring-framework}/lang/NonNull.html[`@NonNull`]: Annotation to indicate that a specific
|
||||
parameter, return value, or field cannot be `null` (not needed on parameters / return values
|
||||
parameter, return value, or field cannot be `null` (not needed on parameters, return values,
|
||||
and fields where `@NonNullApi` and `@NonNullFields` apply, respectively).
|
||||
* {api-spring-framework}/lang/NonNullApi.html[`@NonNullApi`]: Annotation at the package level
|
||||
that declares non-null as the default semantics for parameters and return values.
|
||||
@@ -17,11 +17,10 @@ level that declares non-null as the default semantics for fields.
|
||||
|
||||
The Spring Framework itself leverages these annotations, but they can also be used in any
|
||||
Spring-based Java project to declare null-safe APIs and optionally null-safe fields.
|
||||
Generic type arguments, varargs and array elements nullability are not supported yet but
|
||||
should be in an upcoming release, see https://jira.spring.io/browse/SPR-15942[SPR-15942]
|
||||
for up-to-date information. Nullability declarations are expected to be fine-tuned between
|
||||
Spring Framework releases, including minor ones. Nullability of types used inside method
|
||||
bodies is outside of the scope of this feature.
|
||||
Nullability declarations for generic type arguments, varargs, and array elements are not supported yet.
|
||||
Nullability declarations are expected to be fine-tuned between Spring Framework releases,
|
||||
including minor ones. Nullability of types used inside method bodies is outside the
|
||||
scope of this feature.
|
||||
|
||||
NOTE: Other common libraries such as Reactor and Spring Data provide null-safe APIs that
|
||||
use a similar nullability arrangement, delivering a consistent overall experience for
|
||||
@@ -37,8 +36,8 @@ In addition to providing an explicit declaration for Spring Framework API nullab
|
||||
these annotations can be used by an IDE (such as IDEA or Eclipse) to provide useful
|
||||
warnings related to null-safety in order to avoid `NullPointerException` at runtime.
|
||||
|
||||
They are also used to make Spring API null-safe in Kotlin projects, since Kotlin natively
|
||||
supports https://kotlinlang.org/docs/reference/null-safety.html[null-safety]. More details
|
||||
They are also used to make Spring APIs null-safe in Kotlin projects, since Kotlin natively
|
||||
supports https://kotlinlang.org/docs/null-safety.html[null-safety]. More details
|
||||
are available in the xref:languages/kotlin/null-safety.adoc[Kotlin support documentation].
|
||||
|
||||
|
||||
@@ -48,11 +47,11 @@ are available in the xref:languages/kotlin/null-safety.adoc[Kotlin support docum
|
||||
== JSR-305 meta-annotations
|
||||
|
||||
Spring annotations are meta-annotated with https://jcp.org/en/jsr/detail?id=305[JSR 305]
|
||||
annotations (a dormant but wide-spread JSR). JSR-305 meta-annotations let tooling vendors
|
||||
annotations (a dormant but widespread JSR). JSR-305 meta-annotations let tooling vendors
|
||||
like IDEA or Kotlin provide null-safety support in a generic way, without having to
|
||||
hard-code support for Spring annotations.
|
||||
|
||||
It is not necessary nor recommended to add a JSR-305 dependency to the project classpath to
|
||||
take advantage of Spring null-safe API. Only projects such as Spring-based libraries that use
|
||||
It is neither necessary nor recommended to add a JSR-305 dependency to the project classpath to
|
||||
take advantage of Spring's null-safe APIs. Only projects such as Spring-based libraries that use
|
||||
null-safety annotations in their codebase should add `com.google.code.findbugs:jsr305:3.0.2`
|
||||
with `compileOnly` Gradle configuration or Maven `provided` scope to avoid compile warnings.
|
||||
with `compileOnly` Gradle configuration or Maven `provided` scope to avoid compiler warnings.
|
||||
|
||||
@@ -1,5 +1,50 @@
|
||||
[[beans-binding]]
|
||||
= Data Binding
|
||||
|
||||
Data binding is useful for binding user input to a target object where user input is a map
|
||||
with property paths as keys, following xref:beans-beans-conventions[JavaBeans conventions].
|
||||
`DataBinder` is the main class that supports this, and it provides two ways to bind user
|
||||
input:
|
||||
|
||||
- xref:beans-constructor-binding[Constructor binding] - bind user input to a public data
|
||||
constructor, looking up constructor argument values in the user input.
|
||||
- xref:beans-beans[Property binding] - bind user input to setters, matching keys from the
|
||||
the user input to properties of the target object structure.
|
||||
|
||||
You can apply both constructor and property binding or only one.
|
||||
|
||||
|
||||
[[beans-constructor-binding]]
|
||||
== Constructor Binding
|
||||
|
||||
To use constructor binding:
|
||||
|
||||
1. Create a `DataBinder` with `null` as the target object.
|
||||
2. Set `targetType` to the target class.
|
||||
3. Call `construct`.
|
||||
|
||||
The target class should have a single public constructor or a single non-public constructor
|
||||
with arguments. If there are multiple constructors, then a default constructor if present
|
||||
is used.
|
||||
|
||||
By default, constructor parameter names are used to look up argument values, but you can
|
||||
configure a `NameResolver`. Spring MVC and WebFlux both rely to allow customizing the name
|
||||
of the value to bind through an `@BindParam` annotation on constructor parameters.
|
||||
|
||||
xref:beans-beans-conventions[Type conversion] is applied as needed to convert user input.
|
||||
If the constructor parameter is an object, it is constructed recursively in the same
|
||||
manner, but through a nested property path. That means constructor binding creates both
|
||||
the target object and any objects it contains.
|
||||
|
||||
Binding and conversion errors are reflected in the `BindingResult` of the `DataBinder`.
|
||||
If the target is created successfully, then `target` is set to the created instance
|
||||
after the call to `construct`.
|
||||
|
||||
|
||||
|
||||
|
||||
[[beans-beans]]
|
||||
= Bean Manipulation and the `BeanWrapper`
|
||||
== Property Binding with `BeanWrapper`
|
||||
|
||||
The `org.springframework.beans` package adheres to the JavaBeans standard.
|
||||
A JavaBean is a class with a default no-argument constructor and that follows
|
||||
@@ -26,7 +71,7 @@ perform actions on that bean, such as setting and retrieving properties.
|
||||
|
||||
|
||||
[[beans-beans-conventions]]
|
||||
== Setting and Getting Basic and Nested Properties
|
||||
=== Setting and Getting Basic and Nested Properties
|
||||
|
||||
Setting and getting properties is done through the `setPropertyValue` and
|
||||
`getPropertyValue` overloaded method variants of `BeanWrapper`. See their Javadoc for
|
||||
@@ -192,7 +237,7 @@ Kotlin::
|
||||
|
||||
|
||||
[[beans-beans-conversion]]
|
||||
== Built-in `PropertyEditor` Implementations
|
||||
== ``PropertyEditor``'s
|
||||
|
||||
Spring uses the concept of a `PropertyEditor` to effect the conversion between an
|
||||
`Object` and a `String`. It can be handy
|
||||
@@ -378,7 +423,7 @@ Kotlin::
|
||||
|
||||
|
||||
[[beans-beans-conversion-customeditor-registration]]
|
||||
=== Registering Additional Custom `PropertyEditor` Implementations
|
||||
=== Custom ``PropertyEditor``'s
|
||||
|
||||
When setting bean properties as string values, a Spring IoC container ultimately uses
|
||||
standard JavaBeans `PropertyEditor` implementations to convert these strings to the complex type of the
|
||||
@@ -521,7 +566,7 @@ Finally, the following example shows how to use `CustomEditorConfigurer` to regi
|
||||
----
|
||||
|
||||
[[beans-beans-conversion-customeditor-registration-per]]
|
||||
==== Using `PropertyEditorRegistrar`
|
||||
=== `PropertyEditorRegistrar`
|
||||
|
||||
Another mechanism for registering property editors with the Spring container is to
|
||||
create and use a `PropertyEditorRegistrar`. This interface is particularly useful when
|
||||
|
||||
@@ -123,15 +123,12 @@ Validator, is expected to be present in the classpath and is automatically detec
|
||||
|
||||
|
||||
[[validation-beanvalidation-spring-inject]]
|
||||
=== Injecting a Validator
|
||||
=== Inject Jakarta Validator
|
||||
|
||||
`LocalValidatorFactoryBean` implements both `jakarta.validation.ValidatorFactory` and
|
||||
`jakarta.validation.Validator`, as well as Spring's `org.springframework.validation.Validator`.
|
||||
You can inject a reference to either of these interfaces into beans that need to invoke
|
||||
validation logic.
|
||||
|
||||
You can inject a reference to `jakarta.validation.Validator` if you prefer to work with the Bean
|
||||
Validation API directly, as the following example shows:
|
||||
`jakarta.validation.Validator`, so you can inject a reference to the latter to
|
||||
apply validation logic if you prefer to work with the Bean Validation API directly,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -160,8 +157,15 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
You can inject a reference to `org.springframework.validation.Validator` if your bean
|
||||
requires the Spring Validation API, as the following example shows:
|
||||
|
||||
[[validation-beanvalidation-spring-inject-adapter]]
|
||||
=== Inject Spring Validator
|
||||
|
||||
In addition to implementing `jakarta.validation.Validator`, `LocalValidatorFactoryBean`
|
||||
also adapts to `org.springframework.validation.Validator`, so you can inject a reference
|
||||
to the latter if your bean requires the Spring Validation API.
|
||||
|
||||
For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -190,9 +194,15 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
When used as `org.springframework.validation.Validator`, `LocalValidatorFactoryBean`
|
||||
invokes the underlying `jakarta.validation.Validator`, and then adapts
|
||||
``ContraintViolation``s to ``FieldError``s, and registers them with the `Errors` object
|
||||
passed into the `validate` method.
|
||||
|
||||
|
||||
|
||||
[[validation-beanvalidation-spring-constraints]]
|
||||
=== Configuring Custom Constraints
|
||||
=== Configure Custom Constraints
|
||||
|
||||
Each bean validation constraint consists of two parts:
|
||||
|
||||
@@ -274,9 +284,8 @@ As the preceding example shows, a `ConstraintValidator` implementation can have
|
||||
[[validation-beanvalidation-spring-method]]
|
||||
=== Spring-driven Method Validation
|
||||
|
||||
You can integrate the method validation feature supported by Bean Validation 1.1 (and, as
|
||||
a custom extension, also by Hibernate Validator 4.3) into a Spring context through a
|
||||
`MethodValidationPostProcessor` bean definition:
|
||||
You can integrate the method validation feature of Bean Validation into a
|
||||
Spring context through a `MethodValidationPostProcessor` bean definition:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -305,11 +314,11 @@ XML::
|
||||
----
|
||||
======
|
||||
|
||||
To be eligible for Spring-driven method validation, all target classes need to be annotated
|
||||
To be eligible for Spring-driven method validation, target classes need to be annotated
|
||||
with Spring's `@Validated` annotation, which can optionally also declare the validation
|
||||
groups to use. See
|
||||
{api-spring-framework}/validation/beanvalidation/MethodValidationPostProcessor.html[`MethodValidationPostProcessor`]
|
||||
for setup details with the Hibernate Validator and Bean Validation 1.1 providers.
|
||||
for setup details with the Hibernate Validator and Bean Validation providers.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
@@ -320,8 +329,141 @@ xref:core/aop/proxying.adoc#aop-understanding-aop-proxies[Understanding AOP Prox
|
||||
to always use methods and accessors on proxied classes; direct field access will not work.
|
||||
====
|
||||
|
||||
NOTE: Spring MVC and WebFlux have built-in support for method validation, and therefore
|
||||
for web controller methods there is no need for a class level `@Validated` and an AOP proxy.
|
||||
See the Spring MVC xref:web/webmvc/mvc-controller/ann-validation.adoc[Validation] section,
|
||||
the WebFlux xref:web/webflux/controller/ann-validation.adoc[Validation] section,
|
||||
and the xref:web/webmvc/mvc-controller/ann-validation.adoc[Error Responses] section.
|
||||
|
||||
|
||||
[[validation-beanvalidation-spring-method-exceptions]]
|
||||
==== Method Validation Exceptions
|
||||
|
||||
By default, `jakarta.validation.ConstraintViolationException` is raised with the set of
|
||||
``ConstraintViolation``s returned by `jakarata.validation.Validator`. As an alternative,
|
||||
you can have `MethodValidationException` raised instead with ``ConstraintViolation``s
|
||||
adapted to `MessageSourceResolvable` errors. To enable set the following flag:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
|
||||
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
|
||||
@Bean
|
||||
public MethodValidationPostProcessor validationPostProcessor() {
|
||||
MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
|
||||
processor.setAdaptConstraintViolations(true);
|
||||
return processor;
|
||||
}
|
||||
}
|
||||
|
||||
----
|
||||
|
||||
XML::
|
||||
+
|
||||
[source,xml,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor">
|
||||
<property name="adaptConstraintViolations" value="true"/>
|
||||
</bean>
|
||||
----
|
||||
======
|
||||
|
||||
`MethodValidationException` contains a list of ``ParameterValidationResult``s which
|
||||
group errors by method parameter, and each exposes a `MethodParameter`, the argument
|
||||
value, and a list of `MessageSourceResolvable` errors adapted from
|
||||
``ConstraintViolation``s. For `@Valid` method parameters with cascaded violations on
|
||||
fields and properties, the `ParameterValidationResult` is `ParameterErrors` which
|
||||
implements `org.springframework.validation.Errors` and exposes validation errors as
|
||||
``FieldError``s.
|
||||
|
||||
|
||||
[[validation-beanvalidation-spring-method-i18n]]
|
||||
==== Customizing Validation Errors
|
||||
|
||||
The adapted `MessageSourceResolvable` errors can be turned into error messages to
|
||||
display to users through the configured `MessageSource` with locale and language specific
|
||||
resource bundles. This section provides an example for illustration.
|
||||
|
||||
Given the following class declarations:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
record Person(@Size(min = 1, max = 10) String name) {
|
||||
}
|
||||
|
||||
@Validated
|
||||
public class MyService {
|
||||
|
||||
void addStudent(@Valid Person person, @Max(2) int degrees) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@JvmRecord
|
||||
internal data class Person(@Size(min = 1, max = 10) val name: String)
|
||||
|
||||
@Validated
|
||||
class MyService {
|
||||
|
||||
fun addStudent(person: @Valid Person?, degrees: @Max(2) Int) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
A `ConstraintViolation` on `Person.name()` is adapted to a `FieldErrro` with the following:
|
||||
|
||||
- Error codes `"Size.student.name"`, `"Size.name"`, `"Size.java.lang.String"`, and `"Size"`
|
||||
- Message arguments `"name"`, `10`, and `1` (the field name and the constraint attributes)
|
||||
- Default message "size must be between 1 and 10"
|
||||
|
||||
To customize the default message, you can add properties to
|
||||
xref:core/beans/context-introduction.adoc#context-functionality-messagesource[MessageSource]
|
||||
resource bundles using any of the above errors codes and message arguments. Note also that the
|
||||
message argument `"name"` is itself a `MessagreSourceResolvable` with error codes
|
||||
`"student.name"` and `"name"` and can customized too. For example:
|
||||
|
||||
Properties::
|
||||
+
|
||||
[source,properties,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
Size.student.name=Please, provide a {0} that is between {2} and {1} characters long
|
||||
student.name=username
|
||||
----
|
||||
|
||||
A `ConstraintViolation` on the `degrees` method parameter is adapted to a
|
||||
`MessageSourceResolvable` with the following:
|
||||
|
||||
- Error codes `"Max.myService#addStudent.degrees"`, `"Max.degrees"`, `"Max.int"`, `"Max"`
|
||||
- Message arguments "degrees2 and 2 (the field name and the constraint attribute)
|
||||
- Default message "must be less than or equal to 2"
|
||||
|
||||
To customize the above default message, you can add a property such as:
|
||||
|
||||
Properties::
|
||||
+
|
||||
[source,properties,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
Max.degrees=You cannot provide more than {1} {0}
|
||||
----
|
||||
|
||||
|
||||
[[validation-beanvalidation-spring-other]]
|
||||
=== Additional Configuration Options
|
||||
|
||||
@@ -195,6 +195,12 @@ of Spring Web MVC, you can use the `<spring:bind/>` tag to inspect the error mes
|
||||
you can also inspect the `Errors` object yourself. More information about the
|
||||
methods it offers can be found in the {api-spring-framework}/validation/Errors.html[javadoc].
|
||||
|
||||
Validators may also get locally invoked for the immediate validation of a given object,
|
||||
not involving a binding process. As of 6.1, this has been simplified through a new
|
||||
`Validator.validateObject(Object)` method which is available by default now, returning
|
||||
a simple ´Errors` representation which can be inspected: typically calling `hasErrors()`
|
||||
or the new `failOnError` method for turning the error summary message into an exception
|
||||
(e.g. `validator.validateObject(myObject).failOnError(IllegalArgumentException::new)`).
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ including error handling. It includes the following topics:
|
||||
|
||||
* xref:data-access/jdbc/core.adoc#jdbc-JdbcTemplate[Using `JdbcTemplate`]
|
||||
* xref:data-access/jdbc/core.adoc#jdbc-NamedParameterJdbcTemplate[Using `NamedParameterJdbcTemplate`]
|
||||
* xref:data-access/jdbc/core.adoc#jdbc-JdbcClient[Unified JDBC Query/Update Operations: `JdbcClient`]
|
||||
* xref:data-access/jdbc/core.adoc#jdbc-SQLExceptionTranslator[Using `SQLExceptionTranslator`]
|
||||
* xref:data-access/jdbc/core.adoc#jdbc-statements-executing[Running Statements]
|
||||
* xref:data-access/jdbc/core.adoc#jdbc-statements-querying[Running Queries]
|
||||
@@ -501,8 +502,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 +532,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 +545,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 +577,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 +591,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 +639,6 @@ Java::
|
||||
}
|
||||
|
||||
// setters omitted...
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
@@ -673,12 +667,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 +685,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 +698,122 @@ 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-JdbcClient]]
|
||||
== Unified JDBC Query/Update Operations: `JdbcClient`
|
||||
|
||||
As of 6.1, the named parameter statements of `NamedParameterJdbcTemplate` and the positional
|
||||
parameter statements of a regular `JdbcTemplate` are available through a unified client API
|
||||
with a fluent interaction model.
|
||||
|
||||
E.g. with positional parameters:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
private JdbcClient jdbcClient = JdbcClient.create(dataSource);
|
||||
|
||||
public int countOfActorsByFirstName(String firstName) {
|
||||
return this.jdbcClient.sql("select count(*) from t_actor where first_name = ?")
|
||||
.param(firstName);
|
||||
.query(Integer.class).single();
|
||||
}
|
||||
----
|
||||
|
||||
E.g. with named parameters:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
private JdbcClient jdbcClient = JdbcClient.create(dataSource);
|
||||
|
||||
public int countOfActorsByFirstName(String firstName) {
|
||||
return this.jdbcClient.sql("select count(*) from t_actor where first_name = :firstName")
|
||||
.param("firstName", firstName);
|
||||
.query(Integer.class).single();
|
||||
}
|
||||
----
|
||||
|
||||
`RowMapper` capabilities are available as well, with flexible result resolution:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
List<Actor> actors = this.jdbcClient.sql("select first_name, last_name from t_actor")
|
||||
.query((rs, rowNum) -> new Actor(rs.getString("first_name"), rs.getString("last_name")))
|
||||
.list();
|
||||
----
|
||||
|
||||
Instead of a custom `RowMapper`, you may also specify a class to map to.
|
||||
E.g. assuming that `Actor` has `firstName` and `lastName` properties
|
||||
as a record class, a custom constructor, bean properties, or plain fields:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
List<Actor> actors = this.jdbcClient.sql("select first_name, last_name from t_actor")
|
||||
.query(Actor.class)
|
||||
.list();
|
||||
----
|
||||
|
||||
With a required single object result:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
Actor actor = this.jdbcClient.sql("select first_name, last_name from t_actor where id = ?",
|
||||
.param(1212L);
|
||||
.query(Actor.class)
|
||||
.single();
|
||||
----
|
||||
|
||||
With a `java.util.Optional` result:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
Optional<Actor> actor = this.jdbcClient.sql("select first_name, last_name from t_actor where id = ?",
|
||||
.param(1212L);
|
||||
.query(Actor.class)
|
||||
.optional();
|
||||
----
|
||||
|
||||
And for an update statement:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
this.jdbcClient.sql("insert into t_actor (first_name, last_name) values (?, ?)")
|
||||
.param("Leonor").param("Watling");
|
||||
.update();
|
||||
----
|
||||
|
||||
Or an update statement with named parameters:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
this.jdbcClient.sql("insert into t_actor (first_name, last_name) values (:firstName, :lastName)")
|
||||
.param("firstName", "Leonor").param("lastName", "Watling");
|
||||
.update();
|
||||
----
|
||||
|
||||
Instead of individual named parameters, you may also specify a parameter source object,
|
||||
e.g. a record class or a class with bean properties or a plain field holder which
|
||||
provides `firstName` and `lastName` properties, such as the `Actor` class from above:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
this.jdbcClient.sql("insert into t_actor (first_name, last_name) values (:firstName, :lastName)")
|
||||
.paramSource(new Actor("Leonor", "Watling");
|
||||
.update();
|
||||
----
|
||||
|
||||
The automatic `Actor` class mapping for parameters as well as the query results above is
|
||||
provided through implicit `SimplePropertySqlParameterSource` and `SimplePropertyRowMapper`
|
||||
strategies which are also available for direct use. They can serve as a common replacement
|
||||
for `BeanPropertySqlParameterSource` and `BeanPropertyRowMapper`/`DataClassRowMapper`,
|
||||
also with `JdbcTemplate` and `NamedParameterJdbcTemplate` themselves.
|
||||
|
||||
NOTE: `JdbcClient` is a flexible but simplified facade for JDBC query/update statements.
|
||||
Advanced capabilities such as batch inserts and stored procedure calls typically require
|
||||
extra customization: consider Spring's `SimpleJdbcInsert` and `SimpleJdbcCall` classes or
|
||||
plain direct `JdbcTemplate` usage for any such capabilities not available on `JdbcClient`.
|
||||
|
||||
|
||||
[[jdbc-SQLExceptionTranslator]]
|
||||
@@ -718,12 +823,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 +859,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 +899,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 +915,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 +923,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
|
||||
|
||||
@@ -8,10 +8,16 @@ implementations and transaction demarcation. Most of these patterns can be direc
|
||||
translated to all other supported ORM tools. The later sections in this chapter then
|
||||
cover the other ORM technologies and show brief examples.
|
||||
|
||||
NOTE: As of Spring Framework 5.3, Spring requires Hibernate ORM 5.2+ for Spring's
|
||||
[NOTE]
|
||||
====
|
||||
As of Spring Framework 6.0, Spring requires Hibernate ORM 5.5+ for Spring's
|
||||
`HibernateJpaVendorAdapter` as well as for a native Hibernate `SessionFactory` setup.
|
||||
It is strongly recommended to go with Hibernate ORM 5.4 for a newly started application.
|
||||
For use with `HibernateJpaVendorAdapter`, Hibernate Search needs to be upgraded to 5.11.6.
|
||||
We recommend Hibernate ORM 5.6 as the last feature branch in that Hibernate generation.
|
||||
|
||||
Hibernate ORM 6.x is only supported as a JPA provider (`HibernateJpaVendorAdapter`).
|
||||
Plain `SessionFactory` setup with the `orm.hibernate5` package is not supported anymore.
|
||||
We recommend Hibernate ORM 6.1/6.2 with JPA-style setup for new development projects.
|
||||
====
|
||||
|
||||
|
||||
[[orm-session-factory-setup]]
|
||||
|
||||
@@ -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,6 +254,24 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
Alternatively, there is a shortcut for mapping to a single value:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
Flux<String> names = client.sql("SELECT name FROM person")
|
||||
.mapValue(String.class)
|
||||
.all();
|
||||
----
|
||||
|
||||
Or you may map to a result object with bean properties or record components:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
// assuming a name property on Person
|
||||
Flux<Person> persons = client.sql("SELECT name FROM person")
|
||||
.mapProperties(Person.class)
|
||||
.all();
|
||||
----
|
||||
|
||||
[[r2dbc-DatabaseClient-mapping-null]]
|
||||
.What about `null`?
|
||||
@@ -315,10 +337,31 @@ 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);
|
||||
----
|
||||
|
||||
Alternatively, you may pass in a map of names and values:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
Map<String, Object> params = new LinkedHashMap<>();
|
||||
params.put("id", "joe");
|
||||
params.put("name", "Joe");
|
||||
params.put("age", 34);
|
||||
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
|
||||
.bindValues(params);
|
||||
----
|
||||
|
||||
Or you may pass in a parameter object with bean properties or record components:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
// assuming id, name, age properties on Person
|
||||
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
|
||||
.bindProperties(new Person("joe", "Joe", 34);
|
||||
----
|
||||
|
||||
.R2DBC Native Bind Markers
|
||||
@@ -327,7 +370,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 +406,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 +418,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 +433,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 +445,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))
|
||||
----
|
||||
======
|
||||
|
||||
@@ -417,9 +460,9 @@ Do not pass `Collection<String>` or the like as an array parameter.
|
||||
[[r2dbc-DatabaseClient-filter]]
|
||||
==== Statement Filters
|
||||
|
||||
Sometimes it you need to fine-tune options on the actual `Statement`
|
||||
before it gets run. Register a `Statement` filter
|
||||
(`StatementFilterFunction`) through `DatabaseClient` to intercept and
|
||||
Sometimes you need to fine-tune options on the actual `Statement`
|
||||
before it gets run. To do so, register a `Statement` filter
|
||||
(`StatementFilterFunction`) with the `DatabaseClient` to intercept and
|
||||
modify statements in their execution, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
@@ -429,9 +472,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,13 +482,14 @@ 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", …)
|
||||
----
|
||||
======
|
||||
|
||||
`DatabaseClient` exposes also simplified `filter(…)` overload accepting `Function<Statement, Statement>`:
|
||||
`DatabaseClient` also exposes a simplified `filter(…)` overload that accepts
|
||||
a `Function<Statement, Statement>`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -454,10 +498,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::
|
||||
@@ -465,10 +509,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) }
|
||||
----
|
||||
======
|
||||
|
||||
@@ -592,7 +636,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();
|
||||
|
||||
@@ -604,7 +648,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()
|
||||
|
||||
@@ -671,7 +715,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).
|
||||
@@ -717,19 +760,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.
|
||||
|
||||
|
||||
|
||||
|
||||
+22
-33
@@ -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,47 +193,39 @@ 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
|
||||
@@ -375,7 +366,6 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[transaction-declarative-attransactional-settings]]
|
||||
== `@Transactional` Settings
|
||||
|
||||
@@ -454,10 +444,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 +511,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`
|
||||
|
||||
|
||||
@@ -57,10 +57,14 @@ attribute of the annotation to `true`.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
`@TransactionalEventListener` only works with thread-bound transactions managed by
|
||||
`PlatformTransactionManager`. A reactive transaction managed by `ReactiveTransactionManager`
|
||||
uses the Reactor context instead of thread-local attributes, so from the perspective of
|
||||
an event listener, there is no compatible active transaction that it can participate in.
|
||||
As of 6.1, `@TransactionalEventListener` can work with thread-bound transactions managed by
|
||||
`PlatformTransactionManager` as well as reactive transactions managed by `ReactiveTransactionManager`.
|
||||
For the former, listeners are guaranteed to see the current thread-bound transaction.
|
||||
Since the latter uses the Reactor context instead of thread-local variables, the transaction
|
||||
context needs to be included in the published event instance as the event source.
|
||||
See the
|
||||
{api-spring-framework}/transaction/reactive/TransactionalEventPublisher.html[`TransactionalEventPublisher`]
|
||||
javadoc for details.
|
||||
====
|
||||
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
|
||||
For more information about the Spring Framework's transaction support, see:
|
||||
|
||||
* https://www.infoworld.com/article/2077963/distributed-transactions-in-spring--with-and-without-xa.html[Distributed
|
||||
transactions in Spring, with and without XA] is a JavaWorld presentation in which
|
||||
Spring's David Syer guides you through seven patterns for distributed
|
||||
transactions in Spring applications, three of them with XA and four without.
|
||||
* link:++https://www.infoworld.com/article/2077963/distributed-transactions-in-spring--with-and-without-xa.html++[
|
||||
Distributed transactions in Spring, with and without XA] is a JavaWorld presentation in
|
||||
which Spring's David Syer guides you through seven patterns for distributed transactions
|
||||
in Spring applications, three of them with XA and four without.
|
||||
* https://www.infoq.com/minibooks/JTDS[_Java Transaction Design Strategies_] is a book
|
||||
available from https://www.infoq.com/[InfoQ] that provides a well-paced introduction
|
||||
to transactions in Java. It also includes side-by-side examples of how to configure
|
||||
|
||||
@@ -16,7 +16,7 @@ STOMP Messaging.
|
||||
xref:web-reactive.adoc[Web Reactive] :: Spring WebFlux, WebClient,
|
||||
WebSocket, RSocket.
|
||||
xref:integration.adoc[Integration] :: REST Clients, JMS, JCA, JMX,
|
||||
Email, Tasks, Scheduling, Caching, Observability.
|
||||
Email, Tasks, Scheduling, Caching, Observability, JVM Checkpoint Restore.
|
||||
xref:languages.adoc[Languages] :: Kotlin, Groovy, Dynamic Languages.
|
||||
xref:testing/appendix.adoc[Appendix] :: Spring properties.
|
||||
https://github.com/spring-projects/spring-framework/wiki[Wiki] :: What's New,
|
||||
|
||||
@@ -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
|
||||
@@ -211,6 +208,65 @@ NOTE: This is an optional feature, and your favorite cache library may not suppo
|
||||
All `CacheManager` implementations provided by the core framework support it. See the
|
||||
documentation of your cache provider for more details.
|
||||
|
||||
[[cache-annotations-cacheable-reactive]]
|
||||
=== Caching with CompletableFuture and Reactive Return Types
|
||||
|
||||
As of 6.1, cache annotations take `CompletableFuture` and reactive return types
|
||||
into account, automatically adapting the cache interaction accordingly.
|
||||
|
||||
For a method returning a `CompletableFuture`, the object produced by that future
|
||||
will be cached whenever it is complete, and the cache lookup for a cache hit will
|
||||
be retrieved via a `CompletableFuture`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Cacheable("books")
|
||||
public CompletableFuture<Book> findBook(ISBN isbn) {...}
|
||||
----
|
||||
|
||||
For a method returning a Reactor `Mono`, the object emitted by that Reactive Streams
|
||||
publisher will be cached whenever it is available, and the cache lookup for a cache
|
||||
hit will be retrieved as a `Mono` (backed by a `CompletableFuture`):
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Cacheable("books")
|
||||
public Mono<Book> findBook(ISBN isbn) {...}
|
||||
----
|
||||
|
||||
For a method returning a Reactor `Flux`, the objects emitted by that Reactive Streams
|
||||
publisher will be collected into a `List` and cached whenever that list is complete,
|
||||
and the cache lookup for a cache hit will be retrieved as a `Flux` (backed by a
|
||||
`CompletableFuture` for the cached `List` value):
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Cacheable("books")
|
||||
public Flux<Book> findBooks(String author) {...}
|
||||
----
|
||||
|
||||
Such `CompletableFuture` and reactive adaptation also works for synchronized caching,
|
||||
computing the value only once in case of a concurrent cache miss:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Cacheable(cacheNames="foos", sync=true) <1>
|
||||
public CompletableFuture<Foo> executeExpensiveOperation(String id) {...}
|
||||
----
|
||||
<1> Using the `sync` attribute.
|
||||
|
||||
NOTE: In order for such an arrangement to work at runtime, the configured cache
|
||||
needs to be capable of `CompletableFuture`-based retrieval. The Spring-provided
|
||||
`ConcurrentMapCacheManager` automatically adapts to that retrieval style, and
|
||||
`CaffeineCacheManager` natively supports it when its asynchronous cache mode is
|
||||
enabled: set `setAsyncCacheMode(true)` on your `CaffeineCacheManager` instance.
|
||||
|
||||
Last but not least, be aware that annotation-driven caching is not appropriate
|
||||
for sophisticated reactive interactions involving composition and back pressure.
|
||||
If you choose to declare `@Cacheable` on specific reactive methods, consider the
|
||||
impact of the rather coarse-granular cache interaction which simply stores the
|
||||
emitted object for a `Mono` or even a pre-collected list of objects for a `Flux`.
|
||||
|
||||
[[cache-annotations-cacheable-condition]]
|
||||
=== Conditional Caching
|
||||
|
||||
@@ -229,7 +285,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 +297,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
|
||||
@@ -342,9 +396,12 @@ other), such declarations should be avoided. Note also that such conditions shou
|
||||
on the result object (that is, the `#result` variable), as these are validated up-front to
|
||||
confirm the exclusion.
|
||||
|
||||
As of 6.1, `@CachePut` takes `CompletableFuture` and reactive return types into account,
|
||||
performing the put operation whenever the produced object is available.
|
||||
|
||||
|
||||
[[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
|
||||
@@ -384,6 +441,9 @@ trigger, the return values are ignored (as they do not interact with the cache).
|
||||
not the case with `@Cacheable` which adds data to the cache or updates data in the cache
|
||||
and, thus, requires a result.
|
||||
|
||||
As of 6.1, `@CacheEvict` takes `CompletableFuture` and reactive return types into account,
|
||||
performing an after-invocation evict operation whenever processing has completed.
|
||||
|
||||
|
||||
[[cache-annotations-caching]]
|
||||
== The `@Caching` Annotation
|
||||
@@ -402,7 +462,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
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
[[checkpoint-restore]]
|
||||
= JVM Checkpoint Restore
|
||||
|
||||
The Spring Framework integrates with checkpoint/restore as implemented by https://github.com/CRaC/docs[Project CRaC] in order to allow implementing systems capable to reduce the startup and warmup times of Spring-based Java applications with the JVM.
|
||||
|
||||
Using this feature requires:
|
||||
|
||||
* A checkpoint/restore enabled JVM (Linux only for now).
|
||||
* The presence in the classpath of the https://github.com/CRaC/org.crac[`org.crac:crac`] library.
|
||||
* Specifying the required `java` command line parameters like `-XX:CRaCCheckpointTo=PATH` or `-XX:CRaCRestoreFrom=PATH`.
|
||||
|
||||
WARNING: The files generated in the path specified by `-XX:CRaCCheckpointTo=PATH` when a checkpoint is requested contain a representation of the memory of the running JVM, which may contain secrets and other sensitive data. Using this feature should be done with the assumption that any value "seen" by the JVM, such as configuration properties coming from the environment, will be stored in those CRaC files. As a consequence, the security implications of where and how those files are generated, stored and accessed should be carefully assessed.
|
||||
|
||||
Conceptually, checkpoint and restore match with xref:core/beans/factory-nature.adoc#beans-factory-lifecycle-processor[Spring `Lifecycle` contract] for individual beans.
|
||||
|
||||
== On demand checkpoint/restore of a running application
|
||||
|
||||
A checkpoint can be created on demand, for example using a command like `jcmd application.jar JDK.checkpoint`. Before the creation of the checkpoint, Spring Framework
|
||||
stops all the running beans, giving them a chance to close resources if needed by implementing `Lifecycle.stop`. After restore, the same beans are restarted, with `Lifecycle.start` allowing to reopen resources when relevant. For libraries not depending on Spring, checkpoint/restore custom integration can be provided by implementing `org.crac.Resource` and registering the related instance.
|
||||
|
||||
WARNING: Leveraging checkpoint/restore of a running application typically requires additional lifecycle management to gracefully stop and start using resources like files or sockets and stop active threads.
|
||||
|
||||
NOTE: If the checkpoint is created on a warmed-up JVM, the restored JVM will be equally warmed-up, allowing potentially peak performance immediately. This method typically requires access to remote services, and thus requires some level of platform integration.
|
||||
|
||||
== Automatic checkpoint/restore at startup
|
||||
|
||||
When the `-Dspring.context.checkpoint=onRefresh` Java system property is set, a checkpoint is created automatically during the startup at `LifecycleProcessor.onRefresh` level. At this phase, all non-lazy initialized singletons are instantiated, `InitializingBean.afterPropertiesSet` callbacks have been invoked, but not `Lifecycle.start` ones and `ContextRefreshedEvent` has not yet been published.
|
||||
|
||||
WARNING: As mentioned above, and especially in use cases where the CRaC files are shipped as part of a deployable artifact (a container image for example), operate with the assumption that any sensitive data "seen" by the JVM ends up in the CRaC files, and assess carefully the related security implications.
|
||||
|
||||
NOTE: Here checkpoint/restore is a way to "fast-forward" the startup of the application to a phase where the application context is about to start, but does not allow to have a fully warmed-up JVM.
|
||||
@@ -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.
|
||||
@@ -21,11 +21,20 @@ As outlined xref:integration/observability.adoc[at the beginning of this section
|
||||
|===
|
||||
|Observation name |Description
|
||||
|
||||
|xref:integration/observability.adoc#http-client[`"http.client.requests"`]
|
||||
|xref:integration/observability.adoc#observability.http-client[`"http.client.requests"`]
|
||||
|Time spent for HTTP client exchanges
|
||||
|
||||
|xref:integration/observability.adoc#http-server[`"http.server.requests"`]
|
||||
|xref:integration/observability.adoc#observability.http-server[`"http.server.requests"`]
|
||||
|Processing time for HTTP server exchanges at the Framework level
|
||||
|
||||
|xref:integration/observability.adoc#observability.jms.publish[`"jms.message.publish"`]
|
||||
|Time spent sending a JMS message to a destination by a message producer.
|
||||
|
||||
|xref:integration/observability.adoc#observability.jms.process[`"jms.message.process"`]
|
||||
|Processing time for a JMS message that was previously received by a message consumer.
|
||||
|
||||
|xref:integration/observability.adoc#observability.tasks-scheduled[`"tasks.scheduled.execution"`]
|
||||
|Processing time for an execution of a `@Scheduled` task
|
||||
|===
|
||||
|
||||
NOTE: Observations are using Micrometer's official naming convention, but Metrics names will be automatically converted
|
||||
@@ -36,16 +45,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,39 +72,34 @@ 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[]
|
||||
|
||||
You can configure `ObservationFilter` instances on the `ObservationRegistry`.
|
||||
|
||||
[[observability.tasks-scheduled]]
|
||||
== @Scheduled tasks instrumentation
|
||||
|
||||
[[observability.http-server]]
|
||||
== HTTP Server instrumentation
|
||||
An Observation is created for xref:integration/scheduling.adoc#scheduling-enable-annotation-support[each execution of an `@Scheduled` task].
|
||||
Applications need to configure the `ObservationRegistry` on the `ScheduledTaskRegistrar` to enable the recording of observations.
|
||||
This can be done by declaring a `SchedulingConfigurer` bean that sets the observation registry:
|
||||
|
||||
HTTP server exchanges observations are created with the name `"http.server.requests"` for Servlet and Reactive applications.
|
||||
include-code::./ObservationSchedulingConfigurer[]
|
||||
|
||||
[[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`.
|
||||
|
||||
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[]
|
||||
It is using the `org.springframework.scheduling.support.DefaultScheduledTaskObservationConvention` by default, backed by the `ScheduledTaskObservationContext`.
|
||||
You can configure a custom implementation on the `ObservationRegistry` directly.
|
||||
During the execution of the scheduled method, the current observation is restored in the `ThreadLocal` context or the Reactor context (if the scheduled method returns a `Mono` or `Flux` type).
|
||||
|
||||
By default, the following `KeyValues` are created:
|
||||
|
||||
@@ -103,7 +107,107 @@ By default, the following `KeyValues` are created:
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`exception` _(required)_|Name of the exception thrown during the exchange, or `KeyValue#NONE_VALUE`} if no exception happened.
|
||||
|`code.function` _(required)_|Name of Java `Method` that is scheduled for execution.
|
||||
|`code.namespace` _(required)_|Canonical name of the class of the bean instance that holds the scheduled method.
|
||||
|`exception` _(required)_|Name of the exception thrown during the execution, or `"none"` if no exception happened.
|
||||
|`outcome` _(required)_|Outcome of the method execution. Can be `"SUCCESS"`, `"ERROR"` or `"UNKNOWN"` (if for example the operation was cancelled during execution).
|
||||
|===
|
||||
|
||||
|
||||
[[observability.jms]]
|
||||
== JMS messaging instrumentation
|
||||
|
||||
Spring Framework uses the Jakarta JMS instrumentation provided by Micrometer if the `io.micrometer:micrometer-core` dependency is on the classpath.
|
||||
The `io.micrometer.core.instrument.binder.jms.JmsInstrumentation` instruments `jakarta.jms.Session` and records the relevant observations.
|
||||
|
||||
This instrumentation will create 2 types of observations:
|
||||
|
||||
* `"jms.message.publish"` when a JMS message is sent to the broker, typically with `JmsTemplate`.
|
||||
* `"jms.message.process"` when a JMS message is processed by the application, typically with a `MessageListener` or a `@JmsListener` annotated method.
|
||||
|
||||
NOTE: currently there is no instrumentation for `"jms.message.receive"` observations as there is little value in measuring the time spent waiting for the reception of a message.
|
||||
Such an integration would typically instrument `MessageConsumer#receive` method calls. But once those return, the processing time is not measured and the trace scope cannot be propagated to the application.
|
||||
|
||||
By default, both observations share the same set of possible `KeyValues`:
|
||||
|
||||
.Low cardinality Keys
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`exception` |Class name of the exception thrown during the messaging operation (or "none").
|
||||
|`messaging.destination.temporary` _(required)_|Whether the destination is a `TemporaryQueue` or `TemporaryTopic` (values: `"true"` or `"false"`).
|
||||
|`messaging.operation` _(required)_|Name of JMS operation being performed (values: `"publish"` or `"process"`).
|
||||
|===
|
||||
|
||||
.High cardinality Keys
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`messaging.message.conversation_id` |The correlation ID of the JMS message.
|
||||
|`messaging.destination.name` |The name of destination the current message was sent to.
|
||||
|`messaging.message.id` |Value used by the messaging system as an identifier for the message.
|
||||
|===
|
||||
|
||||
[[observability.jms.publish]]
|
||||
=== JMS message Publication instrumentation
|
||||
|
||||
`"jms.message.publish"` observations are recorded when a JMS message is sent to the broker.
|
||||
They measure the time spent sending the message and propagate the tracing information with outgoing JMS message headers.
|
||||
|
||||
You will need to configure the `ObservationRegistry` on the `JmsTemplate` to enable observations:
|
||||
|
||||
include-code::./JmsTemplatePublish[]
|
||||
|
||||
It uses the `io.micrometer.core.instrument.binder.jms.DefaultJmsPublishObservationConvention` by default, backed by the `io.micrometer.core.instrument.binder.jms.JmsPublishObservationContext`.
|
||||
|
||||
[[observability.jms.process]]
|
||||
=== JMS message Processing instrumentation
|
||||
|
||||
`"jms.message.process"` observations are recorded when a JMS message is processed by the application.
|
||||
They measure the time spent processing the message and propagate the tracing context with incoming JMS message headers.
|
||||
|
||||
Most applications will use the xref:integration/jms/annotated.adoc#jms-annotated[`@JmsListener` annotated methods] mechanism to process incoming messages.
|
||||
You will need to ensure that the `ObservationRegistry` is configured on the dedicated `JmsListenerContainerFactory`:
|
||||
|
||||
include-code::./JmsConfiguration[]
|
||||
|
||||
A xref:integration/jms/annotated.adoc#jms-annotated-support[default container factory is required to enable the annotation support],
|
||||
but note that `@JmsListener` annotations can refer to specific container factory beans for specific purposes.
|
||||
In all cases, Observations are only recorded if the observation registry is configured on the container factory.
|
||||
|
||||
Similar observations are recorded with `JmsTemplate` when messages are processed by a `MessageListener`.
|
||||
Such listeners are set on a `MessageConsumer` within a session callback (see `JmsTemplate.execute(SessionCallback<T>)`).
|
||||
|
||||
This observation uses the `io.micrometer.core.instrument.binder.jms.DefaultJmsProcessObservationConvention` by default, backed by the `io.micrometer.core.instrument.binder.jms.JmsProcessObservationContext`.
|
||||
|
||||
[[observability.http-server]]
|
||||
== HTTP Server instrumentation
|
||||
|
||||
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 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.
|
||||
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
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|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.
|
||||
|`outcome` _(required)_|Outcome of the HTTP server exchange.
|
||||
|`status` _(required)_|HTTP response raw status code, or `"UNKNOWN"` if no response was created.
|
||||
@@ -121,11 +225,15 @@ By default, the following `KeyValues` are created:
|
||||
[[observability.http-server.reactive]]
|
||||
=== Reactive applications
|
||||
|
||||
Applications need to configure the `org.springframework.web.filter.reactive.ServerHttpObservationFilter` reactive `WebFilter` in their application.
|
||||
Applications need to configure the `WebHttpHandlerBuilder` with a `MeterRegistry` to enable server instrumentation.
|
||||
This can be done on the `WebHttpHandlerBuilder`, as follows:
|
||||
|
||||
include-code::./HttpHandlerConfiguration[]
|
||||
|
||||
It is using 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`.
|
||||
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.
|
||||
This will only record an observation as an error if the `Exception` has not been handled by an application Controller.
|
||||
Typically, all exceptions handled by Spring WebFlux's `@ExceptionHandler` and <<web.adoc#webflux-ann-rest-exceptions,`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[]
|
||||
@@ -153,9 +261,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,7 +272,7 @@ 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"]
|
||||
@@ -193,7 +301,7 @@ 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"]
|
||||
|
||||
@@ -3,11 +3,19 @@
|
||||
|
||||
The Spring Framework provides the following choices for making calls to REST endpoints:
|
||||
|
||||
* xref:integration/rest-clients.adoc#rest-webclient[`WebClient`] - non-blocking, reactive client w fluent API.
|
||||
* xref:integration/rest-clients.adoc#rest-restclient[`RestClient`] - synchronous client with a fluent API.
|
||||
* xref:integration/rest-clients.adoc#rest-webclient[`WebClient`] - non-blocking, reactive client with fluent API.
|
||||
* xref:integration/rest-clients.adoc#rest-resttemplate[`RestTemplate`] - synchronous client with template method API.
|
||||
* xref:integration/rest-clients.adoc#rest-http-interface[HTTP Interface] - annotated interface with generated, dynamic proxy implementation.
|
||||
|
||||
|
||||
[[rest-restclient]]
|
||||
== `RestClient`
|
||||
|
||||
Reference documentation is forthcoming.
|
||||
For now, please refer to the https://docs.spring.io/spring-framework/docs/6.1.0-M2/javadoc-api/org/springframework/web/client/RestClient.html[API documentation].
|
||||
|
||||
|
||||
[[rest-webclient]]
|
||||
== `WebClient`
|
||||
|
||||
@@ -36,9 +44,8 @@ The `RestTemplate` provides a higher level API over HTTP client libraries. It ma
|
||||
easy to invoke REST endpoints in a single line. It exposes the following groups of
|
||||
overloaded methods:
|
||||
|
||||
NOTE: `RestTemplate` is in maintenance mode, with only requests for minor
|
||||
changes and bugs to be accepted. Please, consider using the
|
||||
xref:web/webflux-webclient.adoc[WebClient] instead.
|
||||
NOTE: The xref:integration/rest-clients.adoc#rest-restclient[`RestClient`] offers a more modern API for synchronous HTTP access.
|
||||
For asynchronous and streaming scenarios, consider the reactive xref:web/webflux-webclient.adoc[WebClient].
|
||||
|
||||
[[rest-overview-of-resttemplate-methods-tbl]]
|
||||
.RestTemplate methods
|
||||
@@ -356,12 +363,13 @@ If necessary the `Content-Type` may also be set explicitly.
|
||||
[[rest-http-interface]]
|
||||
== HTTP Interface
|
||||
|
||||
The Spring Framework lets you define an HTTP service as a Java interface with annotated
|
||||
methods for HTTP exchanges. You can then generate a proxy that implements this interface
|
||||
and performs the exchanges. This helps to simplify HTTP remote access which often
|
||||
involves a facade that wraps the details of using the underlying HTTP client.
|
||||
The Spring Framework lets you define an HTTP service as a Java interface with
|
||||
`@HttpExchange` methods. You can pass such an interface to `HttpServiceProxyFactory`
|
||||
to create a proxy which performs requests through an HTTP client such as `RestClient`
|
||||
or `WebClient`. You can also implement the interface from an `@Controller` for server
|
||||
request handling.
|
||||
|
||||
One, declare an interface with `@HttpExchange` methods:
|
||||
Start by creating the interface with `@HttpExchange` methods:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -375,12 +383,38 @@ One, declare an interface with `@HttpExchange` methods:
|
||||
}
|
||||
----
|
||||
|
||||
Two, create a proxy that will perform the declared HTTP exchanges:
|
||||
Now you can create a proxy that performs requests when methods are called.
|
||||
|
||||
For `RestClient`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
RestClient restClient = RestClient.builder().baseUrl("https://api.github.com/").build();
|
||||
RestClientAdapter adapter = RestClientAdapter.create(restClient);
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
|
||||
|
||||
RepositoryService service = factory.createClient(RepositoryService.class);
|
||||
----
|
||||
|
||||
For `WebClient`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
WebClient client = WebClient.builder().baseUrl("https://api.github.com/").build();
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).build();
|
||||
WebClientAdapter adapter = WebClientAdapter.forClient(webClient)
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
|
||||
|
||||
RepositoryService service = factory.createClient(RepositoryService.class);
|
||||
----
|
||||
|
||||
For `RestTemplate`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory("https://api.github.com/"));
|
||||
RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate);
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
|
||||
|
||||
RepositoryService service = factory.createClient(RepositoryService.class);
|
||||
----
|
||||
@@ -448,6 +482,10 @@ method parameters:
|
||||
Object (entity to be encoded, e.g. as JSON), `HttpEntity` (part content and headers),
|
||||
a Spring `Part`, or Reactive Streams `Publisher` of any of the above.
|
||||
|
||||
| `MultipartFile`
|
||||
| Add a request part from a `MultipartFile`, typically used in a Spring MVC controller
|
||||
where it represents an uploaded file.
|
||||
|
||||
| `@CookieValue`
|
||||
| Add a cookie or multiple cookies. The argument may be a `Map<String, ?>` or
|
||||
`MultiValueMap<String, ?>` with multiple cookies, a `Collection<?>` of values, or an
|
||||
@@ -459,43 +497,74 @@ method parameters:
|
||||
[[rest-http-interface-return-values]]
|
||||
=== Return Values
|
||||
|
||||
Annotated, HTTP exchange methods support the following return values:
|
||||
The supported return values depend on the underlying client.
|
||||
|
||||
Clients adapted to `HttpExchangeAdapter` such as `RestClient` and `RestTemplate`
|
||||
support synchronous return values:
|
||||
|
||||
[cols="1,2", options="header"]
|
||||
|===
|
||||
| Method return value | Description
|
||||
|
||||
| `void`, `Mono<Void>`
|
||||
| Perform the given request, and release the response content, if any.
|
||||
| `void`
|
||||
| Perform the given request.
|
||||
|
||||
| `HttpHeaders`, `Mono<HttpHeaders>`
|
||||
| Perform the given request, release the response content, if any, and return the
|
||||
response headers.
|
||||
| `HttpHeaders`
|
||||
| Perform the given request and return the response headers.
|
||||
|
||||
| `<T>`, `Mono<T>`
|
||||
| `<T>`
|
||||
| Perform the given request and decode the response content to the declared return type.
|
||||
|
||||
| `<T>`, `Flux<T>`
|
||||
| Perform the given request and decode the response content to a stream of the declared
|
||||
element type.
|
||||
| `ResponseEntity<Void>`
|
||||
| Perform the given request and return a `ResponseEntity` with the status and headers.
|
||||
|
||||
| `ResponseEntity<Void>`, `Mono<ResponseEntity<Void>>`
|
||||
| Perform the given request, and release the response content, if any, and return a
|
||||
`ResponseEntity` with the status and headers.
|
||||
|
||||
| `ResponseEntity<T>`, `Mono<ResponseEntity<T>>`
|
||||
| `ResponseEntity<T>`
|
||||
| Perform the given request, decode the response content to the declared return type, and
|
||||
return a `ResponseEntity` with the status, headers, and the decoded body.
|
||||
|
||||
|===
|
||||
|
||||
Clients adapted to `ReactorHttpExchangeAdapter` such as `WebClient`, support all of above
|
||||
as well as reactive variants. The table below shows Reactor types, but you can also use
|
||||
other reactive types that are supported through the `ReactiveAdapterRegistry`:
|
||||
|
||||
[cols="1,2", options="header"]
|
||||
|===
|
||||
| Method return value | Description
|
||||
|
||||
| `Mono<Void>`
|
||||
| Perform the given request, and release the response content, if any.
|
||||
|
||||
| `Mono<HttpHeaders>`
|
||||
| Perform the given request, release the response content, if any, and return the
|
||||
response headers.
|
||||
|
||||
| `Mono<T>`
|
||||
| Perform the given request and decode the response content to the declared return type.
|
||||
|
||||
| `Flux<T>`
|
||||
| Perform the given request and decode the response content to a stream of the declared
|
||||
element type.
|
||||
|
||||
| `Mono<ResponseEntity<Void>>`
|
||||
| Perform the given request, and release the response content, if any, and return a
|
||||
`ResponseEntity` with the status and headers.
|
||||
|
||||
| `Mono<ResponseEntity<T>>`
|
||||
| Perform the given request, decode the response content to the declared return type, and
|
||||
return a `ResponseEntity` with the status, headers, and the decoded body.
|
||||
|
||||
| `Mono<ResponseEntity<Flux<T>>`
|
||||
| Perform the given request, decode the response content to a stream of the declared
|
||||
element type, and return a `ResponseEntity` with the status, headers, and the decoded
|
||||
response body stream.
|
||||
element type, and return a `ResponseEntity` with the status, headers, and the decoded
|
||||
response body stream.
|
||||
|
||||
|===
|
||||
|
||||
TIP: You can also use any other async or reactive types registered in the
|
||||
`ReactiveAdapterRegistry`.
|
||||
By default, the timeout for synchronous return values with `ReactorHttpExchangeAdapter`
|
||||
depends on how the underlying HTTP client is configured. You can set a `blockTimeout`
|
||||
value on the adapter level as well, but we recommend relying on timeout settings of the
|
||||
underlying HTTP client, which operates at a lower level and provides more control.
|
||||
|
||||
|
||||
[[rest-http-interface-exceptions]]
|
||||
|
||||
@@ -62,22 +62,27 @@ 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),
|
||||
replacing a CommonJ WorkManager for that purpose.
|
||||
|
||||
As of 6.1, `ThreadPoolTaskExecutor` provides a pause/resume capability and graceful
|
||||
shutdown through Spring's lifecycle management. There is also a new "virtualThreads"
|
||||
option on `SimpleAsyncTaskExecutor` which is aligned with JDK 21's Virtual Threads,
|
||||
as well as a graceful shutdown capability for `SimpleAsyncTaskExecutor` as well.
|
||||
|
||||
|
||||
[[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 +232,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 +243,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
|
||||
@@ -251,6 +254,11 @@ to provide common bean-style configuration along the lines of `ThreadPoolTaskExe
|
||||
These variants work perfectly fine for locally embedded thread pool setups in lenient
|
||||
application server environments, as well -- in particular on Tomcat and Jetty.
|
||||
|
||||
As of 6.1, `ThreadPoolTaskScheduler` provides a pause/resume capability and graceful
|
||||
shutdown through Spring's lifecycle management. There is also a new option called
|
||||
`SimpleAsyncTaskScheduler` which is aligned with JDK 21's Virtual Threads, using a
|
||||
single scheduler thread but firing up a new thread for every scheduled task execution.
|
||||
|
||||
|
||||
|
||||
[[scheduling-annotation-support]]
|
||||
@@ -380,6 +388,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.
|
||||
@@ -393,6 +407,120 @@ container and once through the `@Configurable` aspect), with the consequence of
|
||||
`@Scheduled` method being invoked twice.
|
||||
====
|
||||
|
||||
[[scheduling-annotation-support-scheduled-reactive]]
|
||||
=== The `@Scheduled` annotation on Reactive methods or Kotlin suspending functions
|
||||
|
||||
As of Spring Framework 6.1, `@Scheduled` methods are also supported on several types
|
||||
of reactive methods:
|
||||
|
||||
- methods with a `Publisher` return type (or any concrete implementation of `Publisher`)
|
||||
like in the following example:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Scheduled(fixedDelay = 500)
|
||||
public Publisher<Void> reactiveSomething() {
|
||||
// return an instance of Publisher
|
||||
}
|
||||
----
|
||||
|
||||
- methods with a return type that can be adapted to `Publisher` via the shared instance
|
||||
of the `ReactiveAdapterRegistry`, provided the type supports _deferred subscription_ like
|
||||
in the following example:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Scheduled(fixedDelay = 500)
|
||||
public Single<String> rxjavaNonPublisher() {
|
||||
return Single.just("example");
|
||||
}
|
||||
----
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The `CompletableFuture` class is an example of a type that can typically be adapted
|
||||
to `Publisher` but doesn't support deferred subscription. Its `ReactiveAdapter` in the
|
||||
registry denotes that by having the `getDescriptor().isDeferred()` method return `false`.
|
||||
====
|
||||
|
||||
- Kotlin suspending functions, like in the following example:
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Scheduled(fixedDelay = 500)
|
||||
suspend fun something() {
|
||||
// do something asynchronous
|
||||
}
|
||||
----
|
||||
|
||||
- methods that return a Kotlin `Flow` or `Deferred` instance, like in the following example:
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Scheduled(fixedDelay = 500)
|
||||
fun something(): Flow<Void> {
|
||||
flow {
|
||||
// do something asynchronous
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
All these types of methods must be declared without any arguments. In the case of Kotlin
|
||||
suspending functions, the `kotlinx.coroutines.reactor` bridge must also be present to allow
|
||||
the framework to invoke a suspending function as a `Publisher`.
|
||||
|
||||
The Spring Framework will obtain a `Publisher` for the annotated method once and will
|
||||
schedule a `Runnable` in which it subscribes to said `Publisher`. These inner regular
|
||||
subscriptions occur according to the corresponding `cron`/fixedDelay`/`fixedRate` configuration.
|
||||
|
||||
If the `Publisher` emits `onNext` signal(s), these are ignored and discarded (the same way
|
||||
return values from synchronous `@Scheduled` methods are ignored).
|
||||
|
||||
In the following example, the `Flux` emits `onNext("Hello"), onNext("World")` every 5
|
||||
seconds, but these values are unused:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Scheduled(initialDelay = 5000, fixedRate = 5000)
|
||||
public Flux<String> reactiveSomething() {
|
||||
return Flux.just("Hello", "World");
|
||||
}
|
||||
----
|
||||
|
||||
If the `Publisher` emits an `onError` signal, it is logged at `WARN` level and recovered.
|
||||
Because of the asynchronous and lazy nature of `Publisher` instances, exceptions are
|
||||
not thrown from the `Runnable` task: this means that the `ErrorHandler` contract is not
|
||||
involved for reactive methods.
|
||||
|
||||
As a result, further scheduled subscription occurs despite the error.
|
||||
|
||||
In the following example, the `Mono` subscription fails twice in the first five seconds.
|
||||
Then subscriptions start succeeding, printing a message to the standard output every five
|
||||
seconds:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Scheduled(initialDelay = 0, fixedRate = 5000)
|
||||
public Mono<Void> reactiveSomething() {
|
||||
AtomicInteger countdown = new AtomicInteger(2);
|
||||
|
||||
return Mono.defer(() -> {
|
||||
if (countDown.get() == 0 || countDown.decrementAndGet() == 0) {
|
||||
return Mono.fromRunnable(() -> System.out.println("Message"));
|
||||
}
|
||||
return Mono.error(new IllegalStateException("Cannot deliver message"));
|
||||
})
|
||||
}
|
||||
----
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
When destroying the annotated bean or closing the application context, Spring Framework cancels
|
||||
scheduled tasks, which includes the next scheduled subscription to the `Publisher` as well
|
||||
as any past subscription that is still currently active (e.g. for long-running publishers
|
||||
or even infinite publishers).
|
||||
====
|
||||
|
||||
|
||||
[[scheduling-annotation-support-async]]
|
||||
=== The `@Async` annotation
|
||||
|
||||
@@ -137,10 +137,12 @@ demonstrate its API and protocol features.
|
||||
|
||||
The `spring-messaging` module contains the following:
|
||||
|
||||
* xref:rsocket.adoc#rsocket-requester[RSocketRequester] -- fluent API to make requests through an `io.rsocket.RSocket`
|
||||
with data and metadata encoding/decoding.
|
||||
* xref:rsocket.adoc#rsocket-annot-responders[Annotated Responders] -- `@MessageMapping` annotated handler methods for
|
||||
responding.
|
||||
* xref:rsocket.adoc#rsocket-requester[RSocketRequester] -- fluent API to make requests
|
||||
through an `io.rsocket.RSocket` with data and metadata encoding/decoding.
|
||||
* xref:rsocket.adoc#rsocket-annot-responders[Annotated Responders] -- `@MessageMapping`
|
||||
and `@RSocketExchange` annotated handler methods for responding.
|
||||
* xref:rsocket.adoc#rsocket-interface[RSocket Interface] -- RSocket service declaration
|
||||
as Java interface with `@RSocketExchange` methods, for use as requester or responder.
|
||||
|
||||
The `spring-web` module contains `Encoder` and `Decoder` implementations such as Jackson
|
||||
CBOR/JSON, and Protobuf that RSocket applications will likely need. It also contains the
|
||||
@@ -863,6 +865,69 @@ interaction type(s):
|
||||
|
||||
|
||||
|
||||
[[rsocket-annot-rsocketexchange]]
|
||||
=== @RSocketExchange
|
||||
|
||||
As an alternative to `@MessageMapping`, you can also handle requests with
|
||||
`@RSocketExchange` methods. Such methods are declared on an
|
||||
xref:rsocket-interface[RSocket Interface] and can be used as a requester via
|
||||
`RSocketServiceProxyFactory` or implemented by a responder.
|
||||
|
||||
For example, to handle requests as a responder:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public interface RadarsService {
|
||||
|
||||
@RSocketExchange("locate.radars.within")
|
||||
Flux<AirportLocation> radars(MapRequest request);
|
||||
}
|
||||
|
||||
@Controller
|
||||
public class RadarsController implements RadarsService {
|
||||
|
||||
public Flux<AirportLocation> radars(MapRequest request) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
interface RadarsService {
|
||||
|
||||
@RSocketExchange("locate.radars.within")
|
||||
fun radars(request: MapRequest): Flow<AirportLocation>
|
||||
}
|
||||
|
||||
@Controller
|
||||
class RadarsController : RadarsService {
|
||||
|
||||
override fun radars(request: MapRequest): Flow<AirportLocation> {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
There some differences between `@RSocketExhange` and `@MessageMapping` since the
|
||||
former needs to remain suitable for requester and responder use. For example, while
|
||||
`@MessageMapping` can be declared to handle any number of routes and each route can
|
||||
be a pattern, `@RSocketExchange` must be declared with a single, concrete route. There are
|
||||
also small differences in the supported method parameters related to metadata, see
|
||||
xref:rsocket-annot-messagemapping[@MessageMapping] and
|
||||
xref:rsocket-interface[RSocket Interface] for a list of supported parameters.
|
||||
|
||||
`@RSocketExchange` can be used at the type level to specify a common prefix for all routes
|
||||
for a given RSocket service interface.
|
||||
|
||||
|
||||
[[rsocket-annot-connectmapping]]
|
||||
=== @ConnectMapping
|
||||
|
||||
@@ -997,12 +1062,13 @@ Kotlin::
|
||||
[[rsocket-interface]]
|
||||
== RSocket Interface
|
||||
|
||||
The Spring Framework lets you define an RSocket service as a Java interface with annotated
|
||||
methods for RSocket exchanges. You can then generate a proxy that implements this interface
|
||||
and performs the exchanges. This helps to simplify RSocket remote access by wrapping the
|
||||
use of the underlying xref:rsocket.adoc#rsocket-requester[RSocketRequester].
|
||||
The Spring Framework lets you define an RSocket service as a Java interface with
|
||||
`@RSocketExchange` methods. You can pass such an interface to `RSocketServiceProxyFactory`
|
||||
to create a proxy which performs requests through an
|
||||
xref:rsocket.adoc#rsocket-requester[RSocketRequester]. You can also implement the
|
||||
interface as a responder that handles requests.
|
||||
|
||||
One, declare an interface with `@RSocketExchange` methods:
|
||||
Start by creating the interface with `@RSocketExchange` methods:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -1016,7 +1082,7 @@ One, declare an interface with `@RSocketExchange` methods:
|
||||
}
|
||||
----
|
||||
|
||||
Two, create a proxy that will perform the declared RSocket exchanges:
|
||||
Now you can create a proxy that performs requests when methods are called:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -1026,6 +1092,10 @@ Two, create a proxy that will perform the declared RSocket exchanges:
|
||||
RepositoryService service = factory.createClient(RadarService.class);
|
||||
----
|
||||
|
||||
You can also implement the interface to handle requests as a responder.
|
||||
See xref:rsocket.adoc#rsocket-annot-rsocketexchange[Annotated Responders].
|
||||
|
||||
|
||||
|
||||
[[rsocket-interface-method-parameters]]
|
||||
=== Method Parameters
|
||||
@@ -1066,3 +1136,10 @@ method parameters:
|
||||
Annotated, RSocket exchange methods support return values that are concrete value(s), or
|
||||
any producer of value(s) that can be adapted to a Reactive Streams `Publisher` via
|
||||
`ReactiveAdapterRegistry`.
|
||||
|
||||
By default, the behavior of RSocket service methods with synchronous (blocking) method
|
||||
signature depends on response timeout settings of the underlying RSocket `ClientTransport`
|
||||
as well as RSocket keep-alive settings. `RSocketServiceProxyFactory.Builder` does expose a
|
||||
`blockTimeout` option that also lets you configure the maximum time to block for a response,
|
||||
but we recommend configuring timeout values at the RSocket level for more control.
|
||||
|
||||
|
||||
+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:
|
||||
|
||||
+5
-1
@@ -12,7 +12,11 @@ metadata.
|
||||
You can use `@DirtiesContext` as both a class-level and a method-level annotation within
|
||||
the same class or class hierarchy. In such scenarios, the `ApplicationContext` is marked
|
||||
as dirty before or after any such annotated method as well as before or after the current
|
||||
test class, depending on the configured `methodMode` and `classMode`.
|
||||
test class, depending on the configured `methodMode` and `classMode`. When
|
||||
`@DirtiesContext` is declared at both the class level and the method level, the
|
||||
configured modes from both annotations will be honored. For example, if the class mode is
|
||||
set to `BEFORE_EACH_TEST_METHOD` and the method mode is set to `AFTER_METHOD`, the
|
||||
context will be marked as dirty both before and after the given test method.
|
||||
|
||||
The following examples explain when the context would be dirtied for various
|
||||
configuration scenarios:
|
||||
|
||||
@@ -14,12 +14,27 @@ 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].
|
||||
|
||||
[WARNING]
|
||||
[TIP]
|
||||
====
|
||||
By default, if an error is encountered during build-time AOT processing, an exception
|
||||
will be thrown, and the overall process will fail immediately.
|
||||
|
||||
If you would prefer that build-time AOT processing continue after errors are encountered,
|
||||
you can disable the `failOnError` mode which results in errors being logged at `WARN`
|
||||
level or with greater detail at `DEBUG` level.
|
||||
|
||||
The `failOnError` mode can be disabled from the command line or a build script by setting
|
||||
a JVM system property named `spring.test.aot.processing.failOnError` to `false`. As an
|
||||
alternative, you can set the same property via the
|
||||
xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism.
|
||||
====
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The `@ContextHierarchy` annotation is currently not supported in AOT mode.
|
||||
====
|
||||
@@ -35,7 +50,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]
|
||||
|
||||
@@ -119,5 +119,6 @@ advanced use cases.
|
||||
* xref:testing/testcontext-framework/ctx-management/dynamic-property-sources.adoc[Context Configuration with Dynamic Property Sources]
|
||||
* xref:testing/testcontext-framework/ctx-management/web.adoc[Loading a `WebApplicationContext`]
|
||||
* xref:testing/testcontext-framework/ctx-management/caching.adoc[Context Caching]
|
||||
* xref:testing/testcontext-framework/ctx-management/failure-threshold.adoc[Context Failure Threshold]
|
||||
* xref:testing/testcontext-framework/ctx-management/hierarchies.adoc[Context Hierarchies]
|
||||
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ framework uses the following configuration parameters to build the context cache
|
||||
* `contextLoader` (from `@ContextConfiguration`)
|
||||
* `parent` (from `@ContextHierarchy`)
|
||||
* `activeProfiles` (from `@ActiveProfiles`)
|
||||
* `propertySourceLocations` (from `@TestPropertySource`)
|
||||
* `propertySourceDescriptors` (from `@TestPropertySource`)
|
||||
* `propertySourceProperties` (from `@TestPropertySource`)
|
||||
* `resourceBasePath` (from `@WebAppConfiguration`)
|
||||
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
[[testcontext-ctx-management-failure-threshold]]
|
||||
= Context Failure Threshold
|
||||
|
||||
As of Spring Framework 6.1, a context _failure threshold_ policy is in place which helps
|
||||
avoid repeated attempts to load a failing `ApplicationContext`. By default, the failure
|
||||
threshold is set to `1` which means that only one attempt will be made to load an
|
||||
`ApplicationContext` for a given context cache key (see
|
||||
xref:testing/testcontext-framework/ctx-management/caching.adoc[Context Caching]). Any
|
||||
subsequent attempt to load the `ApplicationContext` for the same context cache key will
|
||||
result in an immediate `IllegalStateException` with an error message which explains that
|
||||
the attempt was preemptively skipped. This behavior allows individual test classes and
|
||||
test suites to fail faster by avoiding repeated attempts to load an `ApplicationContext`
|
||||
that will never successfully load -- for example, due to a configuration error or a missing
|
||||
external resource that prevents the context from loading in the current environment.
|
||||
|
||||
You can configure the context failure threshold from the command line or a build script
|
||||
by setting a JVM system property named `spring.test.context.failure.threshold` with a
|
||||
positive integer value. As an alternative, you can set the same property via the
|
||||
xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism.
|
||||
|
||||
NOTE: If you wish to effectively disable the context failure threshold, you can set the
|
||||
property to a very large value. For example, from the command line you could set the
|
||||
system property via `-Dspring.test.context.failure.threshold=1000000`.
|
||||
+69
-12
@@ -16,7 +16,7 @@ SPI, but `@TestPropertySource` is not supported with implementations of the olde
|
||||
`ContextLoader` SPI.
|
||||
|
||||
Implementations of `SmartContextLoader` gain access to merged test property source values
|
||||
through the `getPropertySourceLocations()` and `getPropertySourceProperties()` methods in
|
||||
through the `getPropertySourceDescriptors()` and `getPropertySourceProperties()` methods in
|
||||
`MergedContextConfiguration`.
|
||||
====
|
||||
|
||||
@@ -26,17 +26,23 @@ through the `getPropertySourceLocations()` and `getPropertySourceProperties()` m
|
||||
You can configure test properties files by using the `locations` or `value` attribute of
|
||||
`@TestPropertySource`.
|
||||
|
||||
Both traditional and XML-based properties file formats are supported -- for example,
|
||||
`"classpath:/com/example/test.properties"` or `"file:///path/to/file.xml"`.
|
||||
By default, both traditional and XML-based `java.util.Properties` file formats are
|
||||
supported -- for example, `"classpath:/com/example/test.properties"` or
|
||||
`"file:///path/to/file.xml"`. As of Spring Framework 6.1, you can configure a custom
|
||||
`PropertySourceFactory` via the `factory` attribute in `@TestPropertySource` in order to
|
||||
support a different file format such as JSON, YAML, etc.
|
||||
|
||||
Each path is interpreted as a Spring `Resource`. A plain path (for example,
|
||||
`"test.properties"`) is treated as a classpath resource that is relative to the package
|
||||
in which the test class is defined. A path starting with a slash is treated as an
|
||||
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.
|
||||
loaded by using the specified resource protocol.
|
||||
|
||||
Property placeholders in paths (such as `${...}`) will be resolved against the `Environment`.
|
||||
|
||||
As of Spring Framework 6.1, resource location patterns are also supported — for
|
||||
example, `"classpath*:/config/*.properties"`.
|
||||
|
||||
The following example uses a test properties file:
|
||||
|
||||
@@ -80,6 +86,20 @@ a Java properties file:
|
||||
* `key:value`
|
||||
* `key value`
|
||||
|
||||
[TIP]
|
||||
====
|
||||
Although properties can be defined using any of the above syntax variants and any number
|
||||
of spaces between the key and the value, it is recommended that you use one syntax
|
||||
variant and consistent spacing within your test suite — for example, consider always
|
||||
using `key = value` instead of `key= value`, `key=value`, etc. Similarly, if you define
|
||||
inlined properties using text blocks you should consistently use text blocks for inlined
|
||||
properties throughout your test suite.
|
||||
|
||||
The reason is that the exact strings you provide will be used to determine the key for
|
||||
the context cache. Consequently, to benefit from the context cache you must ensure that
|
||||
you define inlined properties consistently.
|
||||
====
|
||||
|
||||
The following example sets two inlined properties:
|
||||
|
||||
[tabs]
|
||||
@@ -89,24 +109,61 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@ContextConfiguration
|
||||
@TestPropertySource(properties = {"timezone = GMT", "port: 4242"}) // <1>
|
||||
@TestPropertySource(properties = {"timezone = GMT", "port = 4242"}) // <1>
|
||||
class MyIntegrationTests {
|
||||
// class body...
|
||||
}
|
||||
----
|
||||
<1> Setting two properties by using two variations of the key-value syntax.
|
||||
<1> Setting two properties via an array of strings.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@ContextConfiguration
|
||||
@TestPropertySource(properties = ["timezone = GMT", "port: 4242"]) // <1>
|
||||
@TestPropertySource(properties = ["timezone = GMT", "port = 4242"]) // <1>
|
||||
class MyIntegrationTests {
|
||||
// class body...
|
||||
}
|
||||
----
|
||||
<1> Setting two properties by using two variations of the key-value syntax.
|
||||
<1> Setting two properties via an array of strings.
|
||||
======
|
||||
|
||||
As of Spring Framework 6.1, you can use _text blocks_ to define multiple inlined
|
||||
properties in a single `String`. The following example sets two inlined properties using
|
||||
a text block:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@ContextConfiguration
|
||||
@TestPropertySource(properties = """
|
||||
timezone = GMT
|
||||
port = 4242
|
||||
""") // <1>
|
||||
class MyIntegrationTests {
|
||||
// class body...
|
||||
}
|
||||
----
|
||||
<1> Setting two properties via a text block.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@ContextConfiguration
|
||||
@TestPropertySource(properties = ["""
|
||||
timezone = GMT
|
||||
port = 4242
|
||||
"""]) // <1>
|
||||
class MyIntegrationTests {
|
||||
// class body...
|
||||
}
|
||||
----
|
||||
<1> Setting two properties via a text block.
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
@@ -166,7 +223,7 @@ Java::
|
||||
@ContextConfiguration
|
||||
@TestPropertySource(
|
||||
locations = "/test.properties",
|
||||
properties = {"timezone = GMT", "port: 4242"}
|
||||
properties = {"timezone = GMT", "port = 4242"}
|
||||
)
|
||||
class MyIntegrationTests {
|
||||
// class body...
|
||||
@@ -179,7 +236,7 @@ Kotlin::
|
||||
----
|
||||
@ContextConfiguration
|
||||
@TestPropertySource("/test.properties",
|
||||
properties = ["timezone = GMT", "port: 4242"]
|
||||
properties = ["timezone = GMT", "port = 4242"]
|
||||
)
|
||||
class MyIntegrationTests {
|
||||
// class body...
|
||||
|
||||
@@ -12,6 +12,8 @@ by default, exactly in the following order:
|
||||
xref:testing/testcontext-framework/application-events.adoc[`ApplicationEvents`].
|
||||
* `DependencyInjectionTestExecutionListener`: Provides dependency injection for the test
|
||||
instance.
|
||||
* `MicrometerObservationRegistryTestExecutionListener`: Provides support for
|
||||
Micrometer's `ObservationRegistry`.
|
||||
* `DirtiesContextTestExecutionListener`: Handles the `@DirtiesContext` annotation for
|
||||
"`after`" modes.
|
||||
* `TransactionalTestExecutionListener`: Provides transactional test execution with
|
||||
|
||||
@@ -67,52 +67,44 @@ from an existing `ProblemDetail`. This could be done centrally, e.g. from an
|
||||
|
||||
|
||||
[[webflux-ann-rest-exceptions-i18n]]
|
||||
== Internationalization
|
||||
== Customization and i18n
|
||||
[.small]#xref:web/webmvc/mvc-ann-rest-exceptions.adoc#mvc-ann-rest-exceptions-i18n[See equivalent in the Servlet stack]#
|
||||
|
||||
It is a common requirement to internationalize error response details, and good practice
|
||||
to customize the problem details for Spring WebFlux exceptions. This is supported as follows:
|
||||
It is a common requirement to customize and internationalize error response details.
|
||||
It is also good practice to customize the problem details for Spring WebFlux exceptions
|
||||
to avoid revealing implementation details. This section describes the support for that.
|
||||
|
||||
- Each `ErrorResponse` exposes a message code and arguments to resolve the "detail" field
|
||||
through a xref:core/beans/context-introduction.adoc#context-functionality-messagesource[MessageSource].
|
||||
The actual message code value is parameterized with placeholders, e.g.
|
||||
`+"HTTP method {0} not supported"+` to be expanded from the arguments.
|
||||
- Each `ErrorResponse` also exposes a message code to resolve the "title" field.
|
||||
- `ResponseEntityExceptionHandler` uses the message code and arguments to resolve the
|
||||
"detail" and the "title" fields.
|
||||
An `ErrorResponse` exposes message codes for "type", "title", and "detail", as well as
|
||||
message code arguments for the "detail" field. `ResponseEntityExceptionHandler` resolves
|
||||
these through a xref:core/beans/context-introduction.adoc#context-functionality-messagesource[MessageSource]
|
||||
and updates the corresponding `ProblemDetail` fields accordingly.
|
||||
|
||||
By default, the message code for the "detail" field is "problemDetail." + the fully
|
||||
qualified exception class name. Some exceptions may expose additional message codes in
|
||||
which case a suffix is added to the default message code. The table below lists message
|
||||
arguments and codes for Spring WebFlux exceptions:
|
||||
The default strategy for message codes follows the pattern:
|
||||
|
||||
`problemDetail.[type|title|detail].[fully qualified exception class name]`
|
||||
|
||||
An `ErrorResponse` may expose more than one message code, typically adding a suffix
|
||||
to the default message code. The table below lists message codes, and arguments for
|
||||
Spring WebFlux exceptions:
|
||||
|
||||
[[webflux-ann-rest-exceptions-codes]]
|
||||
[cols="1,1,2", options="header"]
|
||||
|===
|
||||
| Exception | Message Code | Message Code Arguments
|
||||
|
||||
| `UnsupportedMediaTypeStatusException`
|
||||
| `HandlerMethodValidationException`
|
||||
| (default)
|
||||
| `+{0}+` the media type that is not supported, `+{1}+` list of supported media types
|
||||
| `+{0}+` list all validation errors.
|
||||
Message codes and arguments for each error are also resolved via `MessageSource`.
|
||||
|
||||
| `UnsupportedMediaTypeStatusException`
|
||||
| (default) + ".parseError"
|
||||
|
|
||||
| `MethodNotAllowedException`
|
||||
| (default)
|
||||
| `+{0}+` the current HTTP method, `+{1}+` the list of supported HTTP methods
|
||||
|
||||
| `MissingRequestValueException`
|
||||
| (default)
|
||||
| `+{0}+` a label for the value (e.g. "request header", "cookie value", ...), `+{1}+` the value name
|
||||
|
||||
| `UnsatisfiedRequestParameterException`
|
||||
| (default)
|
||||
| `+{0}+` the list of parameter conditions
|
||||
|
||||
| `WebExchangeBindException`
|
||||
| (default)
|
||||
| `+{0}+` the list of global errors, `+{1}+` the list of field errors.
|
||||
Message codes and arguments for each error within the `BindingResult` are also resolved
|
||||
via `MessageSource`.
|
||||
|
||||
| `NotAcceptableStatusException`
|
||||
| (default)
|
||||
| `+{0}+` list of supported media types
|
||||
@@ -125,14 +117,32 @@ via `MessageSource`.
|
||||
| (default)
|
||||
| `+{0}+` the failure reason provided to the class constructor
|
||||
|
||||
| `MethodNotAllowedException`
|
||||
| `UnsupportedMediaTypeStatusException`
|
||||
| (default)
|
||||
| `+{0}+` the current HTTP method, `+{1}+` the list of supported HTTP methods
|
||||
| `+{0}+` the media type that is not supported, `+{1}+` list of supported media types
|
||||
|
||||
| `UnsupportedMediaTypeStatusException`
|
||||
| (default) + ".parseError"
|
||||
|
|
||||
|
||||
| `UnsatisfiedRequestParameterException`
|
||||
| (default)
|
||||
| `+{0}+` the list of parameter conditions
|
||||
|
||||
| `WebExchangeBindException`
|
||||
| (default)
|
||||
| `+{0}+` the list of global errors, `+{1}+` the list of field errors.
|
||||
Message codes and arguments for each error are also resolved via `MessageSource`.
|
||||
|
||||
|===
|
||||
|
||||
By default, the message code for the "title" field is "problemDetail.title." + the fully
|
||||
qualified exception class name.
|
||||
NOTE: Unlike other exceptions, the message arguments for
|
||||
`WebExchangeBindException` and `HandlerMethodValidationException` are based on a list of
|
||||
`MessageSourceResolvable` errors that can also be customized through a
|
||||
xref:core/beans/context-introduction.adoc#context-functionality-messagesource[MessageSource]
|
||||
resource bundle. See
|
||||
xref:core/validation/beanvalidation.adoc#validation-beanvalidation-spring-method-i18n[Customizing Validation Errors]
|
||||
for more details.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -700,9 +700,8 @@ Java::
|
||||
|
||||
@Override
|
||||
public void configurePathMatch(PathMatchConfigurer configurer) {
|
||||
configurer
|
||||
.setUseCaseSensitiveMatch(true)
|
||||
.addPathPrefix("/api", HandlerTypePredicate.forAnnotation(RestController.class));
|
||||
configurer.addPathPrefix(
|
||||
"/api", HandlerTypePredicate.forAnnotation(RestController.class));
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -717,9 +716,8 @@ Kotlin::
|
||||
|
||||
@Override
|
||||
fun configurePathMatch(configurer: PathMatchConfigurer) {
|
||||
configurer
|
||||
.setUseCaseSensitiveMatch(true)
|
||||
.addPathPrefix("/api", HandlerTypePredicate.forAnnotation(RestController::class.java))
|
||||
configurer.addPathPrefix(
|
||||
"/api", HandlerTypePredicate.forAnnotation(RestController::class.java))
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -740,6 +738,59 @@ reliance on it.
|
||||
|
||||
|
||||
|
||||
|
||||
[[webflux-config-blocking-execution]]
|
||||
== Blocking Execution
|
||||
|
||||
The WebFlux Java config allows you to customize blocking execution in WebFlux.
|
||||
|
||||
You can have blocking controller methods called on a separate thread by providing
|
||||
an `Executor` such as the
|
||||
{api-spring-framework}/core/task/VirtualThreadTaskExecutor.html[`VirtualThreadTaskExecutor`]
|
||||
as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureBlockingExecution(BlockingExecutionConfigurer configurer) {
|
||||
Executor executor = ...
|
||||
configurer.setExecutor(executor);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
fun configureBlockingExecution(configurer: BlockingExecutionConfigurer) {
|
||||
val executor = ...
|
||||
configurer.setExecutor(executor)
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
By default, controller methods whose return type is not recognized by the configured
|
||||
`ReactiveAdapterRegistry` are considered blocking, but you can set a custom controller
|
||||
method predicate via `BlockingExecutionConfigurer`.
|
||||
|
||||
|
||||
|
||||
|
||||
[[webflux-config-websocket-service]]
|
||||
== WebSocketService
|
||||
|
||||
|
||||
@@ -3,23 +3,21 @@
|
||||
|
||||
[.small]#xref:web/webmvc/mvc-controller/ann-initbinder.adoc[See equivalent in the Servlet stack]#
|
||||
|
||||
`@Controller` or `@ControllerAdvice` classes can have `@InitBinder` methods, to
|
||||
initialize instances of `WebDataBinder`. Those, in turn, are used to:
|
||||
`@Controller` or `@ControllerAdvice` classes can have `@InitBinder` methods to
|
||||
initialize `WebDataBinder` instances that in turn can:
|
||||
|
||||
* Bind request parameters (that is, form data or query) to a model object.
|
||||
* Convert `String`-based request values (such as request parameters, path variables,
|
||||
headers, cookies, and others) to the target type of controller method arguments.
|
||||
* Format model object values as `String` values when rendering HTML forms.
|
||||
* Bind request parameters to a model object.
|
||||
* Convert request values from string to object property types.
|
||||
* Format model object properties as strings when rendering HTML forms.
|
||||
|
||||
`@InitBinder` methods can register controller-specific `java.beans.PropertyEditor` or
|
||||
Spring `Converter` and `Formatter` components. In addition, you can use the
|
||||
xref:web/webflux/config.adoc#webflux-config-conversion[WebFlux Java configuration] to register `Converter` and
|
||||
`Formatter` types in a globally shared `FormattingConversionService`.
|
||||
In an `@Controller`, `DataBinder` customizations apply locally within the controller,
|
||||
or even to a specific model attribute referenced by name through the annotation.
|
||||
In an `@ControllerAdvice` customizations can apply to all or a subset of controllers.
|
||||
|
||||
`@InitBinder` methods support many of the same arguments that `@RequestMapping` methods
|
||||
do, except for `@ModelAttribute` (command object) arguments. Typically, they are declared
|
||||
with a `WebDataBinder` argument, for registrations, and a `void` return value.
|
||||
The following example uses the `@InitBinder` annotation:
|
||||
You can register `PropertyEditor`, `Converter`, and `Formatter` components in the
|
||||
`DataBinder` for type conversion. Alternatively, you can use the
|
||||
xref:web/webflux/config.adoc#webflux-config-conversion[WebFlux config] to register
|
||||
`Converter` and `Formatter` components in a globally shared `FormattingConversionService`.
|
||||
|
||||
--
|
||||
[tabs]
|
||||
@@ -112,4 +110,5 @@ Kotlin::
|
||||
== Model Design
|
||||
[.small]#xref:web/webmvc/mvc-controller/ann-initbinder.adoc#mvc-ann-initbinder-model-design[See equivalent in the Servlet stack]#
|
||||
|
||||
include::partial$web/web-data-binding-model-design.adoc[]
|
||||
|
||||
|
||||
+105
-71
@@ -3,11 +3,8 @@
|
||||
|
||||
[.small]#xref:web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc[See equivalent in the Servlet stack]#
|
||||
|
||||
You can use the `@ModelAttribute` annotation on a method argument to access an attribute from the
|
||||
model or have it instantiated if not present. The model attribute is also overlaid with
|
||||
the values of query parameters and form fields whose names match to field names. This is
|
||||
referred to as data binding, and it saves you from having to deal with parsing and
|
||||
converting individual query parameters and form fields. The following example binds an instance of `Pet`:
|
||||
The `@ModelAttribute` method parameter annotation binds request parameters onto a model
|
||||
object. For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -18,7 +15,7 @@ Java::
|
||||
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
|
||||
public String processSubmit(@ModelAttribute Pet pet) { } // <1>
|
||||
----
|
||||
<1> Bind an instance of `Pet`.
|
||||
<1> Bind to an instance of `Pet`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
@@ -27,28 +24,66 @@ Kotlin::
|
||||
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
|
||||
fun processSubmit(@ModelAttribute pet: Pet): String { } // <1>
|
||||
----
|
||||
<1> Bind an instance of `Pet`.
|
||||
<1> Bind to an instance of `Pet`.
|
||||
======
|
||||
|
||||
The `Pet` instance in the preceding example is resolved as follows:
|
||||
The `Pet` instance may be:
|
||||
|
||||
* From the model if already added through xref:web/webflux/controller/ann-modelattrib-methods.adoc[`Model`].
|
||||
* From the HTTP session through xref:web/webflux/controller/ann-methods/sessionattributes.adoc[`@SessionAttributes`].
|
||||
* From the invocation of a default constructor.
|
||||
* From the invocation of a "`primary constructor`" with arguments that match query
|
||||
parameters or form fields. Argument names are determined through JavaBeans
|
||||
`@ConstructorProperties` or through runtime-retained parameter names in the bytecode.
|
||||
* Accessed from the model where it could have been added by a
|
||||
xref:web/webflux/controller/ann-modelattrib-methods.adoc[`Model`].
|
||||
* Accessed from the HTTP session if the model attribute was listed in
|
||||
the class-level xref:web/webflux/controller/ann-methods/sessionattributes.adoc[`@SessionAttributes`].
|
||||
* Instantiated through a default constructor.
|
||||
* Instantiated through a "`primary constructor`" with arguments that match to Servlet
|
||||
request parameters. Argument names are determined through runtime-retained parameter
|
||||
names in the bytecode.
|
||||
|
||||
After the model attribute instance is obtained, data binding is applied. The
|
||||
`WebExchangeDataBinder` class matches names of query parameters and form fields to field
|
||||
names on the target `Object`. Matching fields are populated after type conversion is applied
|
||||
where necessary. For more on data binding (and validation), see
|
||||
xref:web/webmvc/mvc-config/validation.adoc[Validation]. For more on customizing data binding, see
|
||||
xref:web/webflux/controller/ann-initbinder.adoc[`DataBinder`].
|
||||
By default, both constructor and property
|
||||
xref:core/validation/beans-beans.adoc#beans-binding[data binding] are applied. However,
|
||||
model object design requires careful consideration, and for security reasons it is
|
||||
recommended either to use an object tailored specifically for web binding, or to apply
|
||||
constructor binding only. If property binding must still be used, then _allowedFields_
|
||||
patterns should be set to limit which properties can be set. For further details on this
|
||||
and example configuration, see
|
||||
xref:web/webflux/controller/ann-initbinder.adoc#webflux-ann-initbinder-model-design[model design].
|
||||
|
||||
Data binding can result in errors. By default, a `WebExchangeBindException` is raised, but,
|
||||
to check for such errors in the controller method, you can add a `BindingResult` argument
|
||||
immediately next to the `@ModelAttribute`, as the following example shows:
|
||||
When using constructor binding, you can customize request parameter names through an
|
||||
`@BindParam` annotation. For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
class Account {
|
||||
|
||||
private final String firstName;
|
||||
|
||||
public Account(@BindParam("first-name") String firstName) {
|
||||
this.firstName = firstName;
|
||||
}
|
||||
}
|
||||
----
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class Account(@BindParam("first-name") val firstName: String)
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: The `@BindParam` may also be placed on the fields that correspond to constructor
|
||||
parameters. While `@BindParam` is supported out of the box, you can also use a
|
||||
different annotation by setting a `DataBinder.NameResolver` on `DataBinder`
|
||||
|
||||
WebFlux, unlike Spring MVC, supports reactive types in the model, e.g. `Mono<Account>`.
|
||||
You can declare a `@ModelAttribute` argument with or without a reactive type wrapper, and
|
||||
it will be resolved accordingly to the actual value.
|
||||
|
||||
If data binding results in errors, by default a `WebExchangeBindException` is raised,
|
||||
but you can also add a `BindingResult` argument immediately next to the `@ModelAttribute`
|
||||
in order to handle such errors in the controller method. For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -81,49 +116,9 @@ Kotlin::
|
||||
<1> Adding a `BindingResult`.
|
||||
======
|
||||
|
||||
You can automatically apply validation after data binding by adding the
|
||||
`jakarta.validation.Valid` annotation or Spring's `@Validated` annotation (see also
|
||||
xref:core/validation/beanvalidation.adoc[Bean Validation] and
|
||||
xref:web/webmvc/mvc-config/validation.adoc[Spring validation]). The following example uses the `@Valid` annotation:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
|
||||
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) { // <1>
|
||||
if (result.hasErrors()) {
|
||||
return "petForm";
|
||||
}
|
||||
// ...
|
||||
}
|
||||
----
|
||||
<1> Using `@Valid` on a model attribute argument.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
|
||||
fun processSubmit(@Valid @ModelAttribute("pet") pet: Pet, result: BindingResult): String { // <1>
|
||||
if (result.hasErrors()) {
|
||||
return "petForm"
|
||||
}
|
||||
// ...
|
||||
}
|
||||
----
|
||||
<1> Using `@Valid` on a model attribute argument.
|
||||
======
|
||||
|
||||
Spring WebFlux, unlike Spring MVC, supports reactive types in the model -- for example,
|
||||
`Mono<Account>` or `io.reactivex.Single<Account>`. You can declare a `@ModelAttribute` argument
|
||||
with or without a reactive type wrapper, and it will be resolved accordingly,
|
||||
to the actual value if necessary. However, note that, to use a `BindingResult`
|
||||
argument, you must declare the `@ModelAttribute` argument before it without a reactive
|
||||
type wrapper, as shown earlier. Alternatively, you can handle any errors through the
|
||||
reactive type, as the following example shows:
|
||||
To use a `BindingResult` argument, you must declare the `@ModelAttribute` argument before
|
||||
it without a reactive type wrapper. If you want to use the reactive, you can handle errors
|
||||
directly through it. For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -160,10 +155,49 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
Note that use of `@ModelAttribute` is optional -- for example, to set its attributes.
|
||||
By default, any argument that is not a simple value type (as determined by
|
||||
{api-spring-framework}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty])
|
||||
and is not resolved by any other argument resolver is treated as if it were annotated
|
||||
with `@ModelAttribute`.
|
||||
You can automatically apply validation after data binding by adding the
|
||||
`jakarta.validation.Valid` annotation or Spring's `@Validated` annotation (see
|
||||
xref:core/validation/beanvalidation.adoc[Bean Validation] and
|
||||
xref:web/webmvc/mvc-config/validation.adoc[Spring validation]). For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
|
||||
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) { // <1>
|
||||
if (result.hasErrors()) {
|
||||
return "petForm";
|
||||
}
|
||||
// ...
|
||||
}
|
||||
----
|
||||
<1> Using `@Valid` on a model attribute argument.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
|
||||
fun processSubmit(@Valid @ModelAttribute("pet") pet: Pet, result: BindingResult): String { // <1>
|
||||
if (result.hasErrors()) {
|
||||
return "petForm"
|
||||
}
|
||||
// ...
|
||||
}
|
||||
----
|
||||
<1> Using `@Valid` on a model attribute argument.
|
||||
======
|
||||
|
||||
If method validation applies because other parameters have `@Constraint` annotations,
|
||||
then `HandlerMethodValidationException` would be raised instead. See the section on
|
||||
controller method xref:web/webmvc/mvc-controller/ann-validation.adoc[Validation].
|
||||
|
||||
TIP: Using `@ModelAttribute` is optional. By default, any argument that is not a simple
|
||||
value type as determined by
|
||||
{api-spring-framework}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty]
|
||||
_AND_ that is not resolved by any other argument resolver is treated as an `@ModelAttribute`.
|
||||
|
||||
|
||||
|
||||
+4
-2
@@ -176,6 +176,10 @@ Kotlin::
|
||||
======
|
||||
--
|
||||
|
||||
If method validation applies because other parameters have `@Constraint` annotations,
|
||||
then `HandlerMethodValidationException` is raised instead. See the section on
|
||||
xref:web/webflux/controller/ann-validation.adoc[Validation].
|
||||
|
||||
To access all multipart data as a `MultiValueMap`, you can use `@RequestBody`,
|
||||
as the following example shows:
|
||||
|
||||
@@ -306,5 +310,3 @@ file upload.
|
||||
|
||||
Received part events can also be relayed to another service by using the `WebClient`.
|
||||
See xref:web/webflux-webclient/client-body.adoc#webflux-client-body-multipart[Multipart Data].
|
||||
|
||||
|
||||
|
||||
@@ -89,4 +89,33 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
You can also declare an `Errors` parameter for access to validation errors, but in
|
||||
that case the request body must not be a `Mono`, and will be resolved first:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@PostMapping("/accounts")
|
||||
public void handle(@Valid @RequestBody Account account, Errors errors) {
|
||||
// use one of the onError* operators...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@PostMapping("/accounts")
|
||||
fun handle(@Valid @RequestBody account: Mono<Account>) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
If method validation applies because other parameters have `@Constraint` annotations,
|
||||
then `HandlerMethodValidationException` is raised instead. For more details, see the
|
||||
section on xref:web/webflux/controller/ann-validation.adoc[Validation].
|
||||
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
[[webflux-ann-requestmapping]]
|
||||
= Request Mapping
|
||||
= Mapping Requests
|
||||
|
||||
[.small]#xref:web/webmvc/mvc-controller/ann-requestmapping.adoc[See equivalent in the Servlet stack]#
|
||||
|
||||
This section discusses request mapping for annotated controllers.
|
||||
|
||||
[[webflux-ann-requestmapping-annotation]]
|
||||
== `@RequestMapping`
|
||||
|
||||
[.small]#xref:web/webmvc/mvc-controller/ann-requestmapping.adoc#mvc-ann-requestmapping-annotation[See equivalent in the Servlet stack]#
|
||||
|
||||
The `@RequestMapping` annotation is used to map requests to controllers methods. It has
|
||||
various attributes to match by URL, HTTP method, request parameters, headers, and media
|
||||
types. You can use it at the class level to express shared mappings or at the method level
|
||||
@@ -500,3 +507,68 @@ Kotlin::
|
||||
|
||||
|
||||
|
||||
[[webflux-ann-httpexchange-annotation]]
|
||||
== `@HttpExchange`
|
||||
[.small]#xref:web/webmvc/mvc-controller/ann-requestmapping.adoc#mvc-ann-httpexchange-annotation[See equivalent in the Reactive stack]#
|
||||
|
||||
As an alternative to `@RequestMapping`, you can also handle requests with `@HttpExchange`
|
||||
methods. Such methods are declared on an
|
||||
xref:integration/rest-clients.adoc#rest-http-interface[HTTP Interface] and can be used as
|
||||
a client via `HttpServiceProxyFactory` or implemented by a server `@Controller`.
|
||||
|
||||
For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@RestController
|
||||
@HttpExchange("/persons")
|
||||
class PersonController {
|
||||
|
||||
@GetExchange("/{id}")
|
||||
public Person getPerson(@PathVariable Long id) {
|
||||
// ...
|
||||
}
|
||||
|
||||
@PostExchange
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public void add(@RequestBody Person person) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@RestController
|
||||
@HttpExchange("/persons")
|
||||
class PersonController {
|
||||
|
||||
@GetExchange("/{id}")
|
||||
fun getPerson(@PathVariable id: Long): Person {
|
||||
// ...
|
||||
}
|
||||
|
||||
@PostExchange
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
fun add(@RequestBody person: Person) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
There some differences between `@HttpExchange` and `@RequestMapping` since the
|
||||
former needs to remain suitable for client and server use. For example, while
|
||||
`@RequestMapping` can be declared to handle any number of paths and each path can
|
||||
be a pattern, `@HttpExchange` must be declared with a single, concrete path. There are
|
||||
also differences in the supported method parameters. Generally, `@HttpExchange` supports
|
||||
a subset of method parameters that `@RequestMapping` does, excluding any parameters that
|
||||
are server side only. For details see the list of supported method parameters for
|
||||
xref:integration/rest-clients.adoc#rest-http-interface-method-parameters[HTTP interface] and for
|
||||
xref:web/webflux/controller/ann-methods/arguments.adoc[@RequestMapping].
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
[[mvc-ann-validation]]
|
||||
= Validation
|
||||
|
||||
[.small]#xref:web/webmvc/mvc-controller/ann-validation.adoc[See equivalent in the Servlet stack]#
|
||||
|
||||
Spring WebFlux has built-in xref:core/validation/validator.adoc[Validation] support for
|
||||
`@RequestMapping` methods, including the option to use
|
||||
xref:core/validation/beanvalidation.adoc[Java Bean Validation].
|
||||
The validation support works on two levels.
|
||||
|
||||
First, method parameters such as
|
||||
xref:web/webflux/controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
|
||||
xref:web/webflux/controller/ann-methods/requestbody.adoc[@RequestBody], and
|
||||
xref:web/webflux/controller/ann-methods/multipart-forms.adoc[@RequestPart] do perform
|
||||
validation if annotated with Jakarta's `@Valid` or Spring's `@Validated` annotation, and
|
||||
raise `MethodArgumentNotValidException` in case of validation errors. If you want to handle
|
||||
the errors in the controller method instead, you can declare an `Errors` or `BindingResult`
|
||||
method parameter immediately after the validated parameter.
|
||||
|
||||
Second, if https://beanvalidation.org/[Java Bean Validation] is present _AND_ other method
|
||||
parameters, e.g. `@RequestHeader`, `@RequestParam`, `@PathVariable` have `@Constraint`
|
||||
annotations, then method validation is applied to all method arguments, raising
|
||||
`HandlerMethodValidationException` in case of validation errors. You can still declare an
|
||||
`Errors` or `BindingResult` after an `@Valid` method parameter, and handle validation
|
||||
errors within the controller method, as long as there are no validation errors on other
|
||||
method arguments.
|
||||
|
||||
You can configure a `Validator` globally through the
|
||||
xref:web/webflux/config.adoc#webflux-config-validation[WebMvc config], or locally
|
||||
through an xref:web/webflux/controller/ann-initbinder.adoc[@InitBinder] method in an
|
||||
`@Controller` or `@ControllerAdvice`. You can also use multiple validators.
|
||||
|
||||
NOTE: If a controller has a class level `@Validated`, then
|
||||
xref:core/validation/beanvalidation.adoc#validation-beanvalidation-spring-method[method validation is applied]
|
||||
through an AOP proxy. In order to take advantage of the Spring MVC built-in support for
|
||||
method validation added in Spring Framework 6.1, you need to remove the class level
|
||||
`@Validated` annotation from the controller.
|
||||
|
||||
The xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses] section provides further
|
||||
details on how `MethodArgumentNotValidException` and `HandlerMethodValidationException`
|
||||
are handled, and also how their rendering can be customized through a `MessageSource` and
|
||||
locale and language specific resource bundles.
|
||||
|
||||
For further custom handling of method validation errors, you can extend
|
||||
`ResponseEntityExceptionHandler` or use an `@ExceptionHandler` method in a controller
|
||||
or in a `@ControllerAdvice`, and handle `HandlerMethodValidationException` directly.
|
||||
The exception contains a list of``ParameterValidationResult``s that group validation errors
|
||||
by method parameter. You can either iterate over those, or provide a visitor with callback
|
||||
methods by controller method parameter type:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
HandlerMethodValidationException ex = ... ;
|
||||
|
||||
ex.visitResults(new HandlerMethodValidationException.Visitor() {
|
||||
|
||||
@Override
|
||||
public void requestHeader(RequestHeader requestHeader, ParameterValidationResult result) {
|
||||
// ...
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestParam(@Nullable RequestParam requestParam, ParameterValidationResult result) {
|
||||
// ...
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modelAttribute(@Nullable ModelAttribute modelAttribute, ParameterErrors errors) {
|
||||
|
||||
// ...
|
||||
|
||||
@Override
|
||||
public void other(ParameterValidationResult result) {
|
||||
// ...
|
||||
}
|
||||
});
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// HandlerMethodValidationException
|
||||
val ex
|
||||
|
||||
ex.visitResults(object : HandlerMethodValidationException.Visitor {
|
||||
|
||||
override fun requestHeader(requestHeader: RequestHeader, result: ParameterValidationResult) {
|
||||
// ...
|
||||
}
|
||||
|
||||
override fun requestParam(requestParam: RequestParam?, result: ParameterValidationResult) {
|
||||
// ...
|
||||
}
|
||||
|
||||
override fun modelAttribute(modelAttribute: ModelAttribute?, errors: ParameterErrors) {
|
||||
// ...
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
override fun other(result: ParameterValidationResult) {
|
||||
// ...
|
||||
}
|
||||
})
|
||||
----
|
||||
======
|
||||
@@ -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
|
||||
|
||||
@@ -6,6 +6,26 @@ This section describes options for client-side access to REST endpoints.
|
||||
|
||||
|
||||
|
||||
[[webmvc-restclient]]
|
||||
== `RestClient`
|
||||
|
||||
`RestClient` is a synchronous HTTP client that exposes a modern, fluent API.
|
||||
|
||||
See xref:integration/rest-clients.adoc#rest-restclient[`RestClient`] for more details.
|
||||
|
||||
|
||||
|
||||
|
||||
[[webmvc-webclient]]
|
||||
== `WebClient`
|
||||
|
||||
`WebClient` is a reactive client to perform HTTP requests with a fluent API.
|
||||
|
||||
See xref:web/webflux-webclient.adoc[WebClient] for more details.
|
||||
|
||||
|
||||
|
||||
|
||||
[[webmvc-resttemplate]]
|
||||
== `RestTemplate`
|
||||
|
||||
@@ -13,37 +33,8 @@ This section describes options for client-side access to REST endpoints.
|
||||
Spring REST client and exposes a simple, template-method API over underlying HTTP client
|
||||
libraries.
|
||||
|
||||
NOTE: As of 5.0 the `RestTemplate` is in maintenance mode, with only requests for minor
|
||||
changes and bugs to be accepted. Please, consider using the
|
||||
xref:web/webflux-webclient.adoc[WebClient] which offers a more modern API and
|
||||
supports sync, async, and streaming scenarios.
|
||||
|
||||
See xref:integration/rest-clients.adoc[REST Endpoints] for details.
|
||||
|
||||
|
||||
|
||||
|
||||
[[webmvc-webclient]]
|
||||
== `WebClient`
|
||||
|
||||
`WebClient` is a non-blocking, reactive client to perform HTTP requests. It was
|
||||
introduced in 5.0 and offers a modern alternative to the `RestTemplate`, with efficient
|
||||
support for both synchronous and asynchronous, as well as streaming scenarios.
|
||||
|
||||
In contrast to `RestTemplate`, `WebClient` supports the following:
|
||||
|
||||
* Non-blocking I/O.
|
||||
* Reactive Streams back pressure.
|
||||
* High concurrency with fewer hardware resources.
|
||||
* Functional-style, fluent API that takes advantage of Java 8 lambdas.
|
||||
* Synchronous and asynchronous interactions.
|
||||
* Streaming up to or streaming down from a server.
|
||||
|
||||
See xref:web/webflux-webclient.adoc[WebClient] for more details.
|
||||
|
||||
|
||||
|
||||
|
||||
[[webmvc-http-interface]]
|
||||
== HTTP Interface
|
||||
|
||||
|
||||
@@ -69,9 +69,10 @@ 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
|
||||
|
||||
@@ -92,7 +92,7 @@ Kotlin::
|
||||
======
|
||||
|
||||
The return value can then be obtained by running the given task through the
|
||||
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-configuration-spring-mvc[configured] `TaskExecutor`.
|
||||
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-configuration-spring-mvc[configured] `AsyncTaskExecutor`.
|
||||
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ Here is a very concise overview of Servlet asynchronous request processing:
|
||||
|
||||
* The controller returns a `Callable`.
|
||||
* Spring MVC calls `request.startAsync()` and submits the `Callable` to
|
||||
a `TaskExecutor` for processing in a separate thread.
|
||||
an `AsyncTaskExecutor` for processing in a separate thread.
|
||||
* Meanwhile, the `DispatcherServlet` and all filters exit the Servlet container thread,
|
||||
but the response remains open.
|
||||
* Eventually the `Callable` produces a result, and Spring MVC dispatches the request back
|
||||
@@ -404,11 +404,10 @@ TIP: Spring MVC supports Reactor and RxJava through the
|
||||
|
||||
For streaming to the response, reactive back pressure is supported, but writes to the
|
||||
response are still blocking and are run on a separate thread through the
|
||||
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-configuration-spring-mvc[configured] `TaskExecutor`, to avoid
|
||||
blocking the upstream source (such as a `Flux` returned from `WebClient`).
|
||||
By default, `SimpleAsyncTaskExecutor` is used for the blocking writes, but that is not
|
||||
suitable under load. If you plan to stream with a reactive type, you should use the
|
||||
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-configuration-spring-mvc[MVC configuration] to configure a task executor.
|
||||
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-configuration-spring-mvc[configured]
|
||||
`AsyncTaskExecutor`, to avoid blocking the upstream source such as a `Flux` returned
|
||||
from `WebClient`.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -494,7 +493,7 @@ In `web.xml` configuration, you can add `<async-supported>true</async-supported>
|
||||
[[mvc-ann-async-configuration-spring-mvc]]
|
||||
=== Spring MVC
|
||||
|
||||
The MVC configuration exposes the following options related to asynchronous request processing:
|
||||
The MVC configuration exposes the following options for asynchronous request processing:
|
||||
|
||||
* Java configuration: Use the `configureAsyncSupport` callback on `WebMvcConfigurer`.
|
||||
* XML namespace: Use the `<async-support>` element under `<mvc:annotation-driven>`.
|
||||
@@ -504,10 +503,9 @@ You can configure the following:
|
||||
* Default timeout value for async requests, which if not set, depends
|
||||
on the underlying Servlet container.
|
||||
* `AsyncTaskExecutor` to use for blocking writes when streaming with
|
||||
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-reactive-types[Reactive Types] and for executing `Callable` instances returned from
|
||||
controller methods. We highly recommended configuring this property if you
|
||||
stream with reactive types or have controller methods that return `Callable`, since
|
||||
by default, it is a `SimpleAsyncTaskExecutor`.
|
||||
xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-reactive-types[Reactive Types] and for
|
||||
executing `Callable` instances returned from controller methods.
|
||||
The one used by default is not suitable for production under load.
|
||||
* `DeferredResultProcessingInterceptor` implementations and `CallableProcessingInterceptor` implementations.
|
||||
|
||||
Note that you can also set the default timeout value on a `DeferredResult`,
|
||||
|
||||
@@ -67,24 +67,25 @@ from an existing `ProblemDetail`. This could be done centrally, e.g. from an
|
||||
|
||||
|
||||
[[mvc-ann-rest-exceptions-i18n]]
|
||||
== Internationalization
|
||||
== Customization and i18n
|
||||
[.small]#xref:web/webflux/ann-rest-exceptions.adoc#webflux-ann-rest-exceptions-i18n[See equivalent in the Reactive stack]#
|
||||
|
||||
It is a common requirement to internationalize error response details, and good practice
|
||||
to customize the problem details for Spring MVC exceptions. This is supported as follows:
|
||||
It is a common requirement to customize and internationalize error response details.
|
||||
It is also good practice to customize the problem details for Spring MVC exceptions
|
||||
to avoid revealing implementation details. This section describes the support for that.
|
||||
|
||||
- Each `ErrorResponse` exposes a message code and arguments to resolve the "detail" field
|
||||
through a xref:core/beans/context-introduction.adoc#context-functionality-messagesource[MessageSource].
|
||||
The actual message code value is parameterized with placeholders, e.g.
|
||||
`+"HTTP method {0} not supported"+` to be expanded from the arguments.
|
||||
- Each `ErrorResponse` also exposes a message code to resolve the "title" field.
|
||||
- `ResponseEntityExceptionHandler` uses the message code and arguments to resolve the
|
||||
"detail" and the "title" fields.
|
||||
An `ErrorResponse` exposes message codes for "type", "title", and "detail", as well as
|
||||
message code arguments for the "detail" field. `ResponseEntityExceptionHandler` resolves
|
||||
these through a xref:core/beans/context-introduction.adoc#context-functionality-messagesource[MessageSource]
|
||||
and updates the corresponding `ProblemDetail` fields accordingly.
|
||||
|
||||
By default, the message code for the "detail" field is "problemDetail." + the fully
|
||||
qualified exception class name. Some exceptions may expose additional message codes in
|
||||
which case a suffix is added to the default message code. The table below lists message
|
||||
arguments and codes for Spring MVC exceptions:
|
||||
The default strategy for message codes follows the pattern:
|
||||
|
||||
`problemDetail.[type|title|detail].[fully qualified exception class name]`
|
||||
|
||||
An `ErrorResponse` may expose more than one message code, typically adding a suffix
|
||||
to the default message code. The table below lists message codes, and arguments for
|
||||
Spring MVC exceptions:
|
||||
|
||||
[[mvc-ann-rest-exceptions-codes]]
|
||||
[cols="1,1,2", options="header"]
|
||||
@@ -99,6 +100,11 @@ arguments and codes for Spring MVC exceptions:
|
||||
| (default)
|
||||
| `+{0}+` property name, `+{1}+` property value
|
||||
|
||||
| `HandlerMethodValidationException`
|
||||
| (default)
|
||||
| `+{0}+` list all validation errors.
|
||||
Message codes and arguments for each error are also resolved via `MessageSource`.
|
||||
|
||||
| `HttpMediaTypeNotAcceptableException`
|
||||
| (default)
|
||||
| `+{0}+` list of supported media types
|
||||
@@ -130,8 +136,7 @@ arguments and codes for Spring MVC exceptions:
|
||||
| `MethodArgumentNotValidException`
|
||||
| (default)
|
||||
| `+{0}+` the list of global errors, `+{1}+` the list of field errors.
|
||||
Message codes and arguments for each error within the `BindingResult` are also resolved
|
||||
via `MessageSource`.
|
||||
Message codes and arguments for each error are also resolvedvia `MessageSource`.
|
||||
|
||||
| `MissingRequestHeaderException`
|
||||
| (default)
|
||||
@@ -161,6 +166,10 @@ arguments and codes for Spring MVC exceptions:
|
||||
| (default)
|
||||
|
|
||||
|
||||
| `NoResourceFoundException`
|
||||
| (default)
|
||||
|
|
||||
|
||||
| `TypeMismatchException`
|
||||
| (default)
|
||||
| `+{0}+` property name, `+{1}+` property value
|
||||
@@ -171,8 +180,13 @@ arguments and codes for Spring MVC exceptions:
|
||||
|
||||
|===
|
||||
|
||||
By default, the message code for the "title" field is "problemDetail.title." + the fully
|
||||
qualified exception class name.
|
||||
NOTE: Unlike other exceptions, the message arguments for
|
||||
`MethodArgumentValidException` and `HandlerMethodValidationException` are baed on a list of
|
||||
`MessageSourceResolvable` errors that can also be customized through a
|
||||
xref:core/beans/context-introduction.adoc#context-functionality-messagesource[MessageSource]
|
||||
resource bundle. See
|
||||
xref:core/validation/beanvalidation.adoc#validation-beanvalidation-spring-method-i18n[Customizing Validation Errors]
|
||||
for more details.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,11 +3,18 @@
|
||||
|
||||
[.small]#xref:web/webflux/config.adoc#webflux-config-message-codecs[See equivalent in the Reactive stack]#
|
||||
|
||||
You can customize `HttpMessageConverter` in Java configuration by overriding
|
||||
{api-spring-framework}/web/servlet/config/annotation/WebMvcConfigurer.html#configureMessageConverters-java.util.List-[`configureMessageConverters()`]
|
||||
(to replace the default converters created by Spring MVC) or by overriding
|
||||
{api-spring-framework}/web/servlet/config/annotation/WebMvcConfigurer.html#extendMessageConverters-java.util.List-[`extendMessageConverters()`]
|
||||
(to customize the default converters or add additional converters to the default ones).
|
||||
You can set the `HttpMessageConverter` instances to use in Java configuration,
|
||||
replacing the ones used by default, by overriding
|
||||
{api-spring-framework}/web/servlet/config/annotation/WebMvcConfigurer.html#configureMessageConverters-java.util.List-[`configureMessageConverters()`].
|
||||
You can also customize the list of configured message converters at the end by overriding
|
||||
{api-spring-framework}/web/servlet/config/annotation/WebMvcConfigurer.html#extendMessageConverters-java.util.List-[`extendMessageConverters()`].
|
||||
|
||||
TIP: In a Spring Boot application, the `WebMvcAutoConfiguration` adds any
|
||||
`HttpMessageConverter` beans it detects, in addition to default converters. Hence, in a
|
||||
Boot application, prefer to use the
|
||||
https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-config/message-converters.html[HttpMessageConverters]
|
||||
mechanism. Or alternatively, use `extendMessageConverters` to modify message converters
|
||||
at the end.
|
||||
|
||||
The following example adds XML and Jackson JSON converters with a customized
|
||||
`ObjectMapper` instead of the default ones:
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
By default, if xref:core/validation/beanvalidation.adoc#validation-beanvalidation-overview[Bean Validation] is present
|
||||
on the classpath (for example, Hibernate Validator), the `LocalValidatorFactoryBean` is
|
||||
registered as a global xref:core/validation/validator.adoc[Validator] for use with `@Valid` and
|
||||
`Validated` on controller method arguments.
|
||||
`@Validated` on controller method arguments.
|
||||
|
||||
In Java configuration, you can customize the global `Validator` instance, as the
|
||||
following example shows:
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
[[mvc-ann-initbinder]]
|
||||
= `DataBinder`
|
||||
= `@InitBinder`
|
||||
|
||||
[.small]#xref:web/webflux/controller/ann-initbinder.adoc[See equivalent in the Reactive stack]#
|
||||
|
||||
`@Controller` or `@ControllerAdvice` classes can have `@InitBinder` methods that
|
||||
initialize instances of `WebDataBinder`, and those, in turn, can:
|
||||
`@Controller` or `@ControllerAdvice` classes can have `@InitBinder` methods to
|
||||
initialize `WebDataBinder` instances that in turn can:
|
||||
|
||||
* Bind request parameters (that is, form or query data) to a model object.
|
||||
* Convert String-based request values (such as request parameters, path variables,
|
||||
headers, cookies, and others) to the target type of controller method arguments.
|
||||
* Format model object values as `String` values when rendering HTML forms.
|
||||
* Bind request parameters to a model object.
|
||||
* Convert request values from string to object property types.
|
||||
* Format model object properties as strings when rendering HTML forms.
|
||||
|
||||
`@InitBinder` methods can register controller-specific `java.beans.PropertyEditor` or
|
||||
Spring `Converter` and `Formatter` components. In addition, you can use the
|
||||
xref:web/webmvc/mvc-config/conversion.adoc[MVC config] to register `Converter` and `Formatter`
|
||||
types in a globally shared `FormattingConversionService`.
|
||||
In an `@Controller`, `DataBinder` customizations apply locally within the controller,
|
||||
or even to a specific model attribute referenced by name through the annotation.
|
||||
In an `@ControllerAdvice` customizations can apply to all or a subset of controllers.
|
||||
|
||||
`@InitBinder` methods support many of the same arguments that `@RequestMapping` methods
|
||||
do, except for `@ModelAttribute` (command object) arguments. Typically, they are declared
|
||||
with a `WebDataBinder` argument (for registrations) and a `void` return value.
|
||||
The following listing shows an example:
|
||||
You can register `PropertyEditor`, `Converter`, and `Formatter` components in the
|
||||
`DataBinder` for type conversion. Alternatively, you can use the
|
||||
xref:web/webmvc/mvc-config/conversion.adoc[MVC config] to register `Converter` and
|
||||
`Formatter` components in a globally shared `FormattingConversionService`.
|
||||
|
||||
`@InitBinder` methods can have many of the same arguments that `@RequestMapping` methods
|
||||
have, with the notable exception of `@ModelAttribute`. Typically, such methods have a
|
||||
`WebDataBinder` argument (for registrations) and a `void` return value, for example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
||||
+121
-94
@@ -3,11 +3,8 @@
|
||||
|
||||
[.small]#xref:web/webflux/controller/ann-methods/modelattrib-method-args.adoc[See equivalent in the Reactive stack]#
|
||||
|
||||
You can use the `@ModelAttribute` annotation on a method argument to access an attribute from
|
||||
the model or have it be instantiated if not present. The model attribute is also overlain with
|
||||
values from HTTP Servlet request parameters whose names match to field names. This is referred
|
||||
to as data binding, and it saves you from having to deal with parsing and converting individual
|
||||
query parameters and form fields. The following example shows how to do so:
|
||||
The `@ModelAttribute` method parameter annotation binds request parameters onto a model
|
||||
object. For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -20,7 +17,7 @@ Java::
|
||||
// method logic...
|
||||
}
|
||||
----
|
||||
<1> Bind an instance of `Pet`.
|
||||
<1> Bind to an instance of `Pet`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
@@ -31,30 +28,27 @@ fun processSubmit(@ModelAttribute pet: Pet): String { // <1>
|
||||
// method logic...
|
||||
}
|
||||
----
|
||||
<1> Bind an instance of `Pet`.
|
||||
<1> Bind to an instance of `Pet`.
|
||||
======
|
||||
|
||||
The `Pet` instance above is sourced in one of the following ways:
|
||||
The `Pet` instance may be:
|
||||
|
||||
* Retrieved from the model where it may have been added by a
|
||||
* Accessed from the model where it could have been added by a
|
||||
xref:web/webmvc/mvc-controller/ann-modelattrib-methods.adoc[@ModelAttribute method].
|
||||
* Retrieved from the HTTP session if the model attribute was listed in
|
||||
* Accessed from the HTTP session if the model attribute was listed in
|
||||
the class-level xref:web/webmvc/mvc-controller/ann-methods/sessionattributes.adoc[`@SessionAttributes`] annotation.
|
||||
* Obtained through a `Converter` where the model attribute name matches the name of a
|
||||
request value such as a path variable or a request parameter (see next example).
|
||||
* Instantiated using its default constructor.
|
||||
* Obtained through a `Converter` if the model attribute name matches the name of a
|
||||
request value such as a path variable or a request parameter (example follows).
|
||||
* Instantiated through a default constructor.
|
||||
* Instantiated through a "`primary constructor`" with arguments that match to Servlet
|
||||
request parameters. Argument names are determined through JavaBeans
|
||||
`@ConstructorProperties` or through runtime-retained parameter names in the bytecode.
|
||||
request parameters. Argument names are determined through runtime-retained parameter
|
||||
names in the bytecode.
|
||||
|
||||
One alternative to using a xref:web/webmvc/mvc-controller/ann-modelattrib-methods.adoc[@ModelAttribute method] to
|
||||
supply it or relying on the framework to create the model attribute, is to have a
|
||||
`Converter<String, T>` to provide the instance. This is applied when the model attribute
|
||||
name matches to the name of a request value such as a path variable or a request
|
||||
parameter, and there is a `Converter` from `String` to the model attribute type.
|
||||
In the following example, the model attribute name is `account` which matches the URI
|
||||
path variable `account`, and there is a registered `Converter<String, Account>` which
|
||||
could load the `Account` from a data store:
|
||||
As mentioned above, a `Converter<String, T>` may be used to obtain the model object if
|
||||
the model attribute name matches to the name of a request value such as a path variable or a
|
||||
request parameter, _and_ there is a compatible `Converter<String, T>`. In the below example,
|
||||
the model attribute name `account` matches URI path variable `account`, and there is a
|
||||
registered `Converter<String, Account>` that perhaps retrieves it from a persistence store:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -67,7 +61,6 @@ Java::
|
||||
// ...
|
||||
}
|
||||
----
|
||||
<1> Bind an instance of `Account` using an explicit attribute name.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
@@ -78,19 +71,101 @@ Kotlin::
|
||||
// ...
|
||||
}
|
||||
----
|
||||
<1> Bind an instance of `Account` using an explicit attribute name.
|
||||
======
|
||||
|
||||
After the model attribute instance is obtained, data binding is applied. The
|
||||
`WebDataBinder` class matches Servlet request parameter names (query parameters and form
|
||||
fields) to field names on the target `Object`. Matching fields are populated after type
|
||||
conversion is applied, where necessary. For more on data binding (and validation), see
|
||||
xref:web/webmvc/mvc-config/validation.adoc[Validation]. For more on customizing data binding, see
|
||||
xref:web/webmvc/mvc-controller/ann-initbinder.adoc[`DataBinder`].
|
||||
By default, both constructor and property
|
||||
xref:core/validation/beans-beans.adoc#beans-binding[data binding] are applied. However,
|
||||
model object design requires careful consideration, and for security reasons it is
|
||||
recommended either to use an object tailored specifically for web binding, or to apply
|
||||
constructor binding only. If property binding must still be used, then _allowedFields_
|
||||
patterns should be set to limit which properties can be set. For further details on this
|
||||
and example configuration, see
|
||||
xref:web/webmvc/mvc-controller/ann-initbinder.adoc#mvc-ann-initbinder-model-design[model design].
|
||||
|
||||
Data binding can result in errors. By default, a `BindException` is raised. However, to check
|
||||
for such errors in the controller method, you can add a `BindingResult` argument immediately next
|
||||
to the `@ModelAttribute`, as the following example shows:
|
||||
When using constructor binding, you can customize request parameter names through an
|
||||
`@BindParam` annotation. For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
class Account {
|
||||
|
||||
private final String firstName;
|
||||
|
||||
public Account(@BindParam("first-name") String firstName) {
|
||||
this.firstName = firstName;
|
||||
}
|
||||
}
|
||||
----
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class Account(@BindParam("first-name") val firstName: String)
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: The `@BindParam` may also be placed on the fields that correspond to constructor
|
||||
parameters. While `@BindParam` is supported out of the box, you can also use a
|
||||
different annotation by setting a `DataBinder.NameResolver` on `DataBinder`
|
||||
|
||||
In some cases, you may want access to a model attribute without data binding. For such
|
||||
cases, you can inject the `Model` into the controller and access it directly or,
|
||||
alternatively, set `@ModelAttribute(binding=false)`, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@ModelAttribute
|
||||
public AccountForm setUpForm() {
|
||||
return new AccountForm();
|
||||
}
|
||||
|
||||
@ModelAttribute
|
||||
public Account findAccount(@PathVariable String accountId) {
|
||||
return accountRepository.findOne(accountId);
|
||||
}
|
||||
|
||||
@PostMapping("update")
|
||||
public String update(AccountForm form, BindingResult result,
|
||||
@ModelAttribute(binding=false) Account account) { // <1>
|
||||
// ...
|
||||
}
|
||||
----
|
||||
<1> Setting `@ModelAttribute(binding=false)`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@ModelAttribute
|
||||
fun setUpForm(): AccountForm {
|
||||
return AccountForm()
|
||||
}
|
||||
|
||||
@ModelAttribute
|
||||
fun findAccount(@PathVariable accountId: String): Account {
|
||||
return accountRepository.findOne(accountId)
|
||||
}
|
||||
|
||||
@PostMapping("update")
|
||||
fun update(form: AccountForm, result: BindingResult,
|
||||
@ModelAttribute(binding = false) account: Account): String { // <1>
|
||||
// ...
|
||||
}
|
||||
----
|
||||
<1> Setting `@ModelAt\tribute(binding=false)`.
|
||||
======
|
||||
|
||||
If data binding results in errors, by default a `MethodArgumentNotValidException` is raised,
|
||||
but you can also add a `BindingResult` argument immediately next to the `@ModelAttribute`
|
||||
in order to handle such errors in the controller method. For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -123,61 +198,10 @@ Kotlin::
|
||||
<1> Adding a `BindingResult` next to the `@ModelAttribute`.
|
||||
======
|
||||
|
||||
In some cases, you may want access to a model attribute without data binding. For such
|
||||
cases, you can inject the `Model` into the controller and access it directly or,
|
||||
alternatively, set `@ModelAttribute(binding=false)`, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@ModelAttribute
|
||||
public AccountForm setUpForm() {
|
||||
return new AccountForm();
|
||||
}
|
||||
|
||||
@ModelAttribute
|
||||
public Account findAccount(@PathVariable String accountId) {
|
||||
return accountRepository.findOne(accountId);
|
||||
}
|
||||
|
||||
@PostMapping("update")
|
||||
public String update(@Valid AccountForm form, BindingResult result,
|
||||
@ModelAttribute(binding=false) Account account) { // <1>
|
||||
// ...
|
||||
}
|
||||
----
|
||||
<1> Setting `@ModelAttribute(binding=false)`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@ModelAttribute
|
||||
fun setUpForm(): AccountForm {
|
||||
return AccountForm()
|
||||
}
|
||||
|
||||
@ModelAttribute
|
||||
fun findAccount(@PathVariable accountId: String): Account {
|
||||
return accountRepository.findOne(accountId)
|
||||
}
|
||||
|
||||
@PostMapping("update")
|
||||
fun update(@Valid form: AccountForm, result: BindingResult,
|
||||
@ModelAttribute(binding = false) account: Account): String { // <1>
|
||||
// ...
|
||||
}
|
||||
----
|
||||
<1> Setting `@ModelAttribute(binding=false)`.
|
||||
======
|
||||
|
||||
You can automatically apply validation after data binding by adding the
|
||||
`jakarta.validation.Valid` annotation or Spring's `@Validated` annotation
|
||||
(xref:core/validation/beanvalidation.adoc[Bean Validation] and
|
||||
xref:web/webmvc/mvc-config/validation.adoc[Spring validation]). The following example shows how to do so:
|
||||
`jakarta.validation.Valid` annotation or Spring's `@Validated` annotation.
|
||||
See xref:core/validation/beanvalidation.adoc[Bean Validation] and
|
||||
xref:web/webmvc/mvc-config/validation.adoc[Spring validation]. For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -210,10 +234,13 @@ Kotlin::
|
||||
<1> Validate the `Pet` instance.
|
||||
======
|
||||
|
||||
Note that using `@ModelAttribute` is optional (for example, to set its attributes).
|
||||
By default, any argument that is not a simple value type (as determined by
|
||||
{api-spring-framework}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty])
|
||||
and is not resolved by any other argument resolver is treated as if it were annotated
|
||||
with `@ModelAttribute`.
|
||||
|
||||
If there is no `BindingResult` parameter after the `@ModelAttribute`, then
|
||||
`MethodArgumentNotValueException` is raised with the validation errors. However, if method
|
||||
validation applies because other parameters have `@jakarta.validation.Constraint` annotations,
|
||||
then `HandlerMethodValidationException` is raised instead. For more details, see the section
|
||||
xref:web/webmvc/mvc-controller/ann-validation.adoc[Validation].
|
||||
|
||||
TIP: Using `@ModelAttribute` is optional. By default, any parameter that is not a simple
|
||||
value type as determined by
|
||||
{api-spring-framework}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty]
|
||||
_AND_ that is not resolved by any other argument resolver is treated as an `@ModelAttribute`.
|
||||
|
||||
+5
-4
@@ -188,8 +188,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@PostMapping("/")
|
||||
public String handle(@Valid @RequestPart("meta-data") MetaData metadata,
|
||||
BindingResult result) {
|
||||
public String handle(@Valid @RequestPart("meta-data") MetaData metadata, Errors errors) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
@@ -199,12 +198,14 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@PostMapping("/")
|
||||
fun handle(@Valid @RequestPart("meta-data") metadata: MetaData,
|
||||
result: BindingResult): String {
|
||||
fun handle(@Valid @RequestPart("meta-data") metadata: MetaData, errors: Errors): String {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
If method validation applies because other parameters have `@Constraint` annotations,
|
||||
then `HandlerMethodValidationException` is raised instead. For more details, see the
|
||||
section on xref:web/webmvc/mvc-controller/ann-validation.adoc[Validation].
|
||||
|
||||
|
||||
|
||||
+5
-2
@@ -48,7 +48,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@PostMapping("/accounts")
|
||||
public void handle(@Valid @RequestBody Account account, BindingResult result) {
|
||||
public void handle(@Valid @RequestBody Account account, Errors errors) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
@@ -58,10 +58,13 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@PostMapping("/accounts")
|
||||
fun handle(@Valid @RequestBody account: Account, result: BindingResult) {
|
||||
fun handle(@Valid @RequestBody account: Account, errors: Errors) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
If method validation applies because other parameters have `@Constraint` annotations,
|
||||
then `HandlerMethodValidationException` is raised instead. For more details, see the
|
||||
section on xref:web/webmvc/mvc-controller/ann-validation.adoc[Validation].
|
||||
|
||||
|
||||
+75
-1
@@ -1,8 +1,17 @@
|
||||
[[mvc-ann-requestmapping]]
|
||||
= Request Mapping
|
||||
= Mapping Requests
|
||||
|
||||
[.small]#xref:web/webflux/controller/ann-requestmapping.adoc[See equivalent in the Reactive stack]#
|
||||
|
||||
This section discusses request mapping for annotated controllers.
|
||||
|
||||
|
||||
|
||||
[[mvc-ann-requestmapping-annotation]]
|
||||
== `@RequestMapping`
|
||||
|
||||
[.small]#xref:web/webflux/controller/ann-requestmapping.adoc#webflux-ann-requestmapping-annotation[See equivalent in the Reactive stack]#
|
||||
|
||||
You can use the `@RequestMapping` annotation to map requests to controllers methods. It has
|
||||
various attributes to match by URL, HTTP method, request parameters, headers, and media
|
||||
types. You can use it at the class level to express shared mappings or at the method level
|
||||
@@ -549,3 +558,68 @@ Kotlin::
|
||||
|
||||
|
||||
|
||||
[[mvc-ann-httpexchange-annotation]]
|
||||
== `@HttpExchange`
|
||||
[.small]#xref:web/webflux/controller/ann-requestmapping.adoc#webflux-ann-httpexchange-annotation[See equivalent in the Reactive stack]#
|
||||
|
||||
As an alternative to `@RequestMapping`, you can also handle requests with `@HttpExchange`
|
||||
methods. Such methods are declared on an
|
||||
xref:integration/rest-clients.adoc#rest-http-interface[HTTP Interface] and can be used as
|
||||
a client via `HttpServiceProxyFactory` or implemented by a server `@Controller`.
|
||||
|
||||
For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@RestController
|
||||
@HttpExchange("/persons")
|
||||
class PersonController {
|
||||
|
||||
@GetExchange("/{id}")
|
||||
public Person getPerson(@PathVariable Long id) {
|
||||
// ...
|
||||
}
|
||||
|
||||
@PostExchange
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public void add(@RequestBody Person person) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@RestController
|
||||
@HttpExchange("/persons")
|
||||
class PersonController {
|
||||
|
||||
@GetExchange("/{id}")
|
||||
fun getPerson(@PathVariable id: Long): Person {
|
||||
// ...
|
||||
}
|
||||
|
||||
@PostExchange
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
fun add(@RequestBody person: Person) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
There some differences between `@HttpExchange` and `@RequestMapping` since the
|
||||
former needs to remain suitable for client and server use. For example, while
|
||||
`@RequestMapping` can be declared to handle any number of paths and each path can
|
||||
be a pattern, `@HttpExchange` must be declared with a single, concrete path. There are
|
||||
also differences in the supported method parameters. Generally, `@HttpExchange` supports
|
||||
a subset of method parameters that `@RequestMapping` does, excluding any parameters that
|
||||
are server side only. For details see the list of supported method parameters for
|
||||
xref:integration/rest-clients.adoc#rest-http-interface-method-parameters[HTTP interface] and for
|
||||
xref:web/webmvc/mvc-controller/ann-methods/arguments.adoc[@RequestMapping].
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
[[mvc-ann-validation]]
|
||||
= Validation
|
||||
|
||||
[.small]#xref:web/webflux/controller/ann-validation.adoc[See equivalent in the Reactive stack]#
|
||||
|
||||
Spring MVC has built-in xref:core/validation/validator.adoc[Validation] support for
|
||||
`@RequestMapping` methods, including the option to use
|
||||
xref:core/validation/beanvalidation.adoc[Java Bean Validation].
|
||||
The validation support works on two levels.
|
||||
|
||||
First, method parameters such as
|
||||
xref:web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
|
||||
xref:web/webmvc/mvc-controller/ann-methods/requestbody.adoc[@RequestBody], and
|
||||
xref:web/webmvc/mvc-controller/ann-methods/multipart-forms.adoc[@RequestPart] do perform
|
||||
validation if annotated with Jakarta's `@Valid` or Spring's `@Validated` annotation, and
|
||||
raise `MethodArgumentNotValidException` in case of validation errors. If you want to handle
|
||||
the errors in the controller method instead, you can declare an `Errors` or `BindingResult`
|
||||
method parameter immediately after the validated parameter.
|
||||
|
||||
Second, if https://beanvalidation.org/[Java Bean Validation] is present _AND_ other method
|
||||
parameters, e.g. `@RequestHeader`, `@RequestParam`, `@PathVariable` have `@Constraint`
|
||||
annotations, then method validation is applied to all method arguments, raising
|
||||
`HandlerMethodValidationException` in case of validation errors. You can still declare an
|
||||
`Errors` or `BindingResult` after an `@Valid` method parameter, and handle validation
|
||||
errors within the controller method, as long as there are no validation errors on other
|
||||
method arguments. Method validation is also applied to the return value if the method
|
||||
is annotated with `@Valid` or has other `@Constraint` annotations.
|
||||
|
||||
You can configure a `Validator` globally through the
|
||||
xref:web/webmvc/mvc-config/validation.adoc[WebMvc config], or locally through an
|
||||
xref:web/webmvc/mvc-controller/ann-initbinder.adoc[@InitBinder] method in an
|
||||
`@Controller` or `@ControllerAdvice`. You can also use multiple validators.
|
||||
|
||||
NOTE: If a controller has a class level `@Validated`, then
|
||||
xref:core/validation/beanvalidation.adoc#validation-beanvalidation-spring-method[method validation is applied]
|
||||
through an AOP proxy. In order to take advantage of the Spring MVC built-in support for
|
||||
method validation added in Spring Framework 6.1, you need to remove the class level
|
||||
`@Validated` annotation from the controller.
|
||||
|
||||
The xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses] section provides further
|
||||
details on how `MethodArgumentNotValidException` and `HandlerMethodValidationException`
|
||||
are handled, and also how their rendering can be customized through a `MessageSource` and
|
||||
locale and language specific resource bundles.
|
||||
|
||||
For further custom handling of method validation errors, you can extend
|
||||
`ResponseEntityExceptionHandler` or use an `@ExceptionHandler` method in a controller
|
||||
or in a `@ControllerAdvice`, and handle `HandlerMethodValidationException` directly.
|
||||
The exception contains a list of``ParameterValidationResult``s that group validation errors
|
||||
by method parameter. You can either iterate over those, or provide a visitor with callback
|
||||
methods by controller method parameter type:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
HandlerMethodValidationException ex = ... ;
|
||||
|
||||
ex.visitResults(new HandlerMethodValidationException.Visitor() {
|
||||
|
||||
@Override
|
||||
public void requestHeader(RequestHeader requestHeader, ParameterValidationResult result) {
|
||||
// ...
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestParam(@Nullable RequestParam requestParam, ParameterValidationResult result) {
|
||||
// ...
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modelAttribute(@Nullable ModelAttribute modelAttribute, ParameterErrors errors) {
|
||||
|
||||
// ...
|
||||
|
||||
@Override
|
||||
public void other(ParameterValidationResult result) {
|
||||
// ...
|
||||
}
|
||||
});
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// HandlerMethodValidationException
|
||||
val ex
|
||||
|
||||
ex.visitResults(object : HandlerMethodValidationException.Visitor {
|
||||
|
||||
override fun requestHeader(requestHeader: RequestHeader, result: ParameterValidationResult) {
|
||||
// ...
|
||||
}
|
||||
|
||||
override fun requestParam(requestParam: RequestParam?, result: ParameterValidationResult) {
|
||||
// ...
|
||||
}
|
||||
|
||||
override fun modelAttribute(modelAttribute: ModelAttribute?, errors: ParameterErrors) {
|
||||
// ...
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
override fun other(result: ParameterValidationResult) {
|
||||
// ...
|
||||
}
|
||||
})
|
||||
----
|
||||
======
|
||||
@@ -62,8 +62,7 @@ initialization parameters (`init-param` elements) to the Servlet declaration in
|
||||
The exception can then be caught with a `HandlerExceptionResolver` (for example, by using an
|
||||
`@ExceptionHandler` controller method) and handled as any others.
|
||||
|
||||
By default, this is set to `false`, in which case the `DispatcherServlet` sets the
|
||||
response status to 404 (NOT_FOUND) without raising an exception.
|
||||
As of 6.1, this property is set to `true` and deprecated.
|
||||
|
||||
Note that, if xref:web/webmvc/mvc-config/default-servlet-handler.adoc[default servlet handling] is
|
||||
also configured, unresolved requests are always forwarded to the default servlet
|
||||
|
||||
@@ -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
|
||||
endpoints, 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 @@ endpoints, 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
|
||||
@@ -32,7 +32,7 @@ client needs to connect for the WebSocket handshake.
|
||||
<2> STOMP messages whose destination header begins with `/app` are routed to
|
||||
`@MessageMapping` methods in `@Controller` classes.
|
||||
<3> Use the built-in message broker for subscriptions and broadcasting and
|
||||
route messages whose destination header begins with `/topic `or `/queue` to the broker.
|
||||
route messages whose destination header begins with `/topic` or `/queue` to the broker.
|
||||
|
||||
|
||||
The following example shows the XML configuration equivalent of the preceding example:
|
||||
@@ -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,29 +1,16 @@
|
||||
In the context of web applications, _data binding_ involves the binding of HTTP request
|
||||
parameters (that is, form data or query parameters) to properties in a model object and
|
||||
its nested objects.
|
||||
xref:core/validation/beans-beans.adoc#beans-binding[Data binding] for web requests involves
|
||||
binding request parameters to a model object. By default, request parameters can be bound
|
||||
to any public property of the model object, which means malicious clients can provide
|
||||
extra values for properties that exist in the model object graph, but are not expected to
|
||||
be set. This is why model object design requires careful consideration.
|
||||
|
||||
Only `public` properties following the
|
||||
https://www.oracle.com/java/technologies/javase/javabeans-spec.html[JavaBeans naming conventions]
|
||||
are exposed for data binding — for example, `public String getFirstName()` and
|
||||
`public void setFirstName(String)` methods for a `firstName` property.
|
||||
|
||||
TIP: The model object, and its nested object graph, is also sometimes referred to as a
|
||||
TIP: The model object, and its nested object graph is also sometimes referred to as a
|
||||
_command object_, _form-backing object_, or _POJO_ (Plain Old Java Object).
|
||||
|
||||
By default, Spring permits binding to all public properties in the model object graph.
|
||||
This means you need to carefully consider what public properties the model has, since a
|
||||
client could target any public property path, even some that are not expected to be
|
||||
targeted for a given use case.
|
||||
|
||||
For example, given an HTTP form data endpoint, a malicious client could supply values for
|
||||
properties that exist in the model object graph but are not part of the HTML form
|
||||
presented in the browser. This could lead to data being set on the model object and any
|
||||
of its nested objects, that is not expected to be updated.
|
||||
|
||||
The recommended approach is to use a _dedicated model object_ that exposes only
|
||||
properties that are relevant for the form submission. For example, on a form for changing
|
||||
a user's email address, the model object should declare a minimum set of properties such
|
||||
as in the following `ChangeEmailForm`.
|
||||
A good practice is to use a _dedicated model object_ rather than exposing your domain
|
||||
model such as JPA or Hibernate entities for web data binding. For example, on a form to
|
||||
change an email address, create a `ChangeEmailForm` model object that declares only
|
||||
the properties required for the input:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -51,13 +38,16 @@ as in the following `ChangeEmailForm`.
|
||||
}
|
||||
----
|
||||
|
||||
If you cannot or do not want to use a _dedicated model object_ for each data
|
||||
binding use case, you **must** limit the properties that are allowed for data binding.
|
||||
Ideally, you can achieve this by registering _allowed field patterns_ via the
|
||||
`setAllowedFields()` method on `WebDataBinder`.
|
||||
Another good practice is to apply
|
||||
xref:core/validation/beans-beans.adoc#beans-constructor-binding[constructor binding],
|
||||
which uses only the request parameters it needs for constructor arguments, and any other
|
||||
input is ignored. This is in contrast to property binding which by default binds every
|
||||
request parameter for which there is a matching property.
|
||||
|
||||
For example, to register allowed field patterns in your application, you can implement an
|
||||
`@InitBinder` method in a `@Controller` or `@ControllerAdvice` component as shown below:
|
||||
If neither a dedicated model object nor constructor binding is sufficient, and you must
|
||||
use property binding, we strongy recommend registering `allowedFields` patterns (case
|
||||
sensitive) on `WebDataBinder` in order to prevent unexpected properties from being set.
|
||||
For example:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -74,22 +64,28 @@ For example, to register allowed field patterns in your application, you can imp
|
||||
}
|
||||
----
|
||||
|
||||
In addition to registering allowed patterns, it is also possible to register _disallowed
|
||||
field patterns_ via the `setDisallowedFields()` method in `DataBinder` and its subclasses.
|
||||
Please note, however, that an "allow list" is safer than a "deny list". Consequently,
|
||||
`setAllowedFields()` should be favored over `setDisallowedFields()`.
|
||||
You can also register `disallowedFields` patterns (case insensitive). However,
|
||||
"allowed" configuration is preferred over "disallowed" as it is more explicit and less
|
||||
prone to mistakes.
|
||||
|
||||
Note that matching against allowed field patterns is case-sensitive; whereas, matching
|
||||
against disallowed field patterns is case-insensitive. In addition, a field matching a
|
||||
disallowed pattern will not be accepted even if it also happens to match a pattern in the
|
||||
allowed list.
|
||||
By default, constructor and property binding are both used. If you want to use
|
||||
constructor binding only, you can set the `declarativeBinding` flag on `WebDataBinder`
|
||||
through an `@InitBinder` method either locally within a controller or globally through an
|
||||
`@ControllerAdvice`. Turning this flag on ensures that only constructor binding is used
|
||||
and that property binding is not used unless `allowedFields` patterns are configured.
|
||||
For example:
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
It is extremely important to properly configure allowed and disallowed field patterns
|
||||
when exposing your domain model directly for data binding purposes. Otherwise, it is a
|
||||
big security risk.
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Controller
|
||||
public class MyController {
|
||||
|
||||
Furthermore, it is strongly recommended that you do **not** use types from your domain
|
||||
model such as JPA or Hibernate entities as the model object in data binding scenarios.
|
||||
====
|
||||
@InitBinder
|
||||
void initBinder(WebDataBinder binder) {
|
||||
binder.setDeclarativeBinding(true);
|
||||
}
|
||||
|
||||
// @RequestMapping methods, etc.
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.docs.integration.observability.httpserver.reactive;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.server.reactive.HttpHandler;
|
||||
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class HttpHandlerConfiguration {
|
||||
|
||||
private final ApplicationContext applicationContext;
|
||||
|
||||
public HttpHandlerConfiguration(ApplicationContext applicationContext) {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public HttpHandler httpHandler(ObservationRegistry registry) {
|
||||
return WebHttpHandlerBuilder.applicationContext(this.applicationContext)
|
||||
.observationRegistry(registry)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -17,9 +17,9 @@
|
||||
package org.springframework.docs.integration.observability.httpserver.reactive;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.server.reactive.observation.ServerRequestObservationContext;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.filter.reactive.ServerHttpObservationFilter;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
@Controller
|
||||
@@ -28,7 +28,7 @@ public class UserController {
|
||||
@ExceptionHandler(MissingUserException.class)
|
||||
ResponseEntity<Void> handleMissingUser(ServerWebExchange exchange, MissingUserException exception) {
|
||||
// We want to record this exception with the observation
|
||||
ServerHttpObservationFilter.findObservationContext(exchange)
|
||||
ServerRequestObservationContext.findCurrent(exchange.getAttributes())
|
||||
.ifPresent(context -> context.setError(exception));
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user