mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
657 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b932df6ad2 | |||
| 494ed4e852 | |||
| 39cd31613b | |||
| ca2b3c170c | |||
| 019ce4418c | |||
| a0136a276a | |||
| 8d05028c2e | |||
| 4a68c44a27 | |||
| e702733c7b | |||
| 187f641409 | |||
| ac74116d76 | |||
| c5590ae9e6 | |||
| 39b551c334 | |||
| 23696b7db8 | |||
| 7b7cfb27a3 | |||
| f2889b1b43 | |||
| 802967fc98 | |||
| 22328905da | |||
| 67a81004a2 | |||
| 84963ba4d8 | |||
| 07472758ee | |||
| 47c9c7eb07 | |||
| 8568aa691c | |||
| b24221754a | |||
| 7a666cfd86 | |||
| 4a7c24d90f | |||
| d955549037 | |||
| b68f76c86e | |||
| 521cda009b | |||
| 08c9b5c96c | |||
| a942a6e172 | |||
| 9b1d5ea6e0 | |||
| 640b97f33c | |||
| 1fb99e4192 | |||
| 23d89362b0 | |||
| 048e4f0aa2 | |||
| b28153e1d7 | |||
| 88b9c0567d | |||
| c7c9da56da | |||
| 290a41d398 | |||
| 1b563f8ba4 | |||
| dea31dd55e | |||
| 06b91c4ea5 | |||
| a63ebe7e9d | |||
| c4b615052e | |||
| dca5f1dcd8 | |||
| a16e8c34b2 | |||
| 52b8c71dcd | |||
| f941754db6 | |||
| d4495a5654 | |||
| c7c61e13e1 | |||
| 4d722414da | |||
| 87dfa492af | |||
| cf9033a60d | |||
| 1e80694daf | |||
| e17c3d3be4 | |||
| 2e98a8a2a4 | |||
| 592f23674d | |||
| 0b3a05b463 | |||
| ef02f0bad8 | |||
| 836a0b3a40 | |||
| 09f210f5f7 | |||
| aa0fb97ba5 | |||
| 71a117c0fd | |||
| bd27ddf83a | |||
| c531a8a705 | |||
| cd7ba1835c | |||
| 5dc6a16c0b | |||
| 42a4f28962 | |||
| 521d4f24d0 | |||
| 039cbb3466 | |||
| d50c94fca9 | |||
| c1424acb94 | |||
| 36d1bc57d7 | |||
| b1c3b6e34b | |||
| eb8b7c4331 | |||
| 58bd057a24 | |||
| a78704af24 | |||
| d2000efbd8 | |||
| c23fd784a0 | |||
| 68a3993897 | |||
| dfc1b839f3 | |||
| 302c24aaa8 | |||
| 7357812b4e | |||
| 30087e1aea | |||
| cd5758e610 | |||
| b82a0dd31a | |||
| b7280400ee | |||
| a3345920ec | |||
| 381f790329 | |||
| f5a3658535 | |||
| 54a6d89da7 | |||
| 2922a82275 | |||
| a34ceb405c | |||
| 1b25a1506a | |||
| 723c94e5ac | |||
| e5a69dcfdf | |||
| c2248c968c | |||
| 9cc74e78f8 | |||
| 9910df85cd | |||
| 88b3844d9b | |||
| 80f3be6577 | |||
| 946082f806 | |||
| 78fb378aef | |||
| b4e743614e | |||
| accf7ff645 | |||
| 4983a802a7 | |||
| abdccffa39 | |||
| c1287d48e2 | |||
| 528029a0ba | |||
| fea1464562 | |||
| f6bc828569 | |||
| 0e279fe666 | |||
| 56a1c810b5 | |||
| dec5265354 | |||
| 579dbc48d7 | |||
| 6d9a2eb9b8 | |||
| c1d4b610ca | |||
| 5f601ceb45 | |||
| 0955f541cb | |||
| e5e61dfa3f | |||
| 988f3630c4 | |||
| a0ae849856 | |||
| ef0717935b | |||
| 822e2447a0 | |||
| cfd0aee4db | |||
| 132fbe228f | |||
| 4300fec023 | |||
| e9110c0729 | |||
| 24759a75f4 | |||
| 7493ce86b6 | |||
| ce9dc19a3c | |||
| 4b96cd28c0 | |||
| 516a203703 | |||
| 877e0b1483 | |||
| 379ffac508 | |||
| 380184e85a | |||
| 5cba32df32 | |||
| 11c40b5c1c | |||
| 227e75dae4 | |||
| de828e9764 | |||
| 85a781d517 | |||
| 7f916e0ee3 | |||
| 960e885f88 | |||
| bf8f398c19 | |||
| 33705516ff | |||
| beb415dfa3 | |||
| d45c0e6b8a | |||
| 154ca54c9f | |||
| f22a1eece4 | |||
| 6d9736acd0 | |||
| 0de3b30029 | |||
| 45c21042f6 | |||
| 5830aac1d4 | |||
| 0eb61c0f72 | |||
| dc1ef23780 | |||
| cca440eb8e | |||
| 497aa3c069 | |||
| 479879c53a | |||
| 2e57603310 | |||
| be45481d70 | |||
| 5680d20637 | |||
| 8787381892 | |||
| 3cc64968b9 | |||
| 4dc3eac864 | |||
| 0188270138 | |||
| 9430b24eaf | |||
| 41433d445e | |||
| 7ffeb59b40 | |||
| 8d4953d8d6 | |||
| f440d8719c | |||
| 5d6501c75e | |||
| 93f0ec2fa1 | |||
| 85c9279431 | |||
| 06a39f166e | |||
| 46bd133892 | |||
| 7bb9e85723 | |||
| 3aae7a66e6 | |||
| 26ca7c49fb | |||
| 6b8105aef2 | |||
| 481283d2f1 | |||
| 4230c41d97 | |||
| b082348e61 | |||
| f41c7ceaf7 | |||
| 9a068869ef | |||
| d147609e04 | |||
| e9a359f873 | |||
| 78f0688ed0 | |||
| 504b7619bd | |||
| f9ae54d91e | |||
| 2284254d39 | |||
| 6e9607ce99 | |||
| b4bec4ca61 | |||
| 48da9524c3 | |||
| 68189f3de9 | |||
| 10bc93c058 | |||
| 9b93c948a7 | |||
| abe381488a | |||
| cc6dd19324 | |||
| 2fe3321813 | |||
| 8c3fc8c549 | |||
| d3ca6f9f6a | |||
| 2d7b2e59b6 | |||
| 46108deff4 | |||
| 4454b3b5ef | |||
| 2dd22f64e1 | |||
| 6be0432e3d | |||
| 6a5953dca3 | |||
| b4153618a4 | |||
| 0b09f1e12f | |||
| 120ea0a51c | |||
| c46d6286ee | |||
| 750cb73902 | |||
| 5851cdc679 | |||
| 2e833d908a | |||
| 728d5eeb74 | |||
| 89e34ae5ff | |||
| 9ee7c6383f | |||
| ee801d1b28 | |||
| eebdff23e7 | |||
| a2000dba33 | |||
| 4a3ef3e24a | |||
| 4a5dc7c1b0 | |||
| 347d085020 | |||
| f295def2a8 | |||
| dc2dbd9700 | |||
| 6b67972ec4 | |||
| 64fc9ee301 | |||
| ce43d1b1da | |||
| dc73ec76fc | |||
| 888e50175d | |||
| 1080c145e3 | |||
| f726e806cd | |||
| b1f6401e4f | |||
| 169b9abeef | |||
| e72b523995 | |||
| 99bdc4211d | |||
| d47c69746b | |||
| 052bbcc530 | |||
| f9791664ef | |||
| af44b3e6c0 | |||
| 2724c6d8fe | |||
| f7e5c9fbb2 | |||
| 4486ab1cb7 | |||
| 78c96b6d78 | |||
| 43bbe8f3e8 | |||
| 7d612e8958 | |||
| 9a38355896 | |||
| 3ecbc4de13 | |||
| 81c156eefb | |||
| d8c4a33bea | |||
| cfa47fa4fb | |||
| 80949eb30f | |||
| 4ed337247c | |||
| 81cdfafa78 | |||
| d5cb1d9adb | |||
| 9698dbc232 | |||
| 9c15b3fa4c | |||
| c559ec4dfb | |||
| 341ac76209 | |||
| 8ff102115a | |||
| f50a262cf2 | |||
| c570f3b2da | |||
| 0fdf759896 | |||
| c04d4da9a3 | |||
| b737f36f39 | |||
| 8d601384d3 | |||
| ea52ecc5e0 | |||
| 9b5febea20 | |||
| 7627d8c6fc | |||
| 20f91b7dc3 | |||
| e15c150696 | |||
| 7025b7aac2 | |||
| 1e432ff95d | |||
| ae17b11b70 | |||
| 1a783f41aa | |||
| 521fbfdb85 | |||
| 87377d6f3e | |||
| af2934c09b | |||
| 17ee82e004 | |||
| a82108ec73 | |||
| d4401cc69a | |||
| 2367314b52 | |||
| 80a3c1923f | |||
| c989cba963 | |||
| 3d4d68c26f | |||
| b61552b9df | |||
| 615973cce4 | |||
| 00577ed80a | |||
| 9b2b485444 | |||
| d586513d66 | |||
| 2b52582dff | |||
| 67958656e4 | |||
| a4db0e7448 | |||
| ef2cae36ac | |||
| efaf41862c | |||
| af5acb6d34 | |||
| 067638ae6e | |||
| 398cc01650 | |||
| 3a518b60e7 | |||
| 298f308ce1 | |||
| 6ffb74def3 | |||
| b55a4d3908 | |||
| db535863dd | |||
| 95a3f3bb6e | |||
| 84cce6018c | |||
| e97fc7be38 | |||
| 9eae0ba50e | |||
| f3c8102882 | |||
| 005d5ef922 | |||
| 5dc26460fb | |||
| 542502b2b6 | |||
| f6d8443781 | |||
| a44341ece3 | |||
| 969b18b0e8 | |||
| 2e9d6a1d4e | |||
| 7e5efdd8dd | |||
| 08e6df8832 | |||
| a738e4d5fd | |||
| 9c4b4ab81e | |||
| 0ee2d41528 | |||
| dc6ce30663 | |||
| 62fa3f11c1 | |||
| 2e56361fe4 | |||
| 9b0162da49 | |||
| ab98210e6d | |||
| 97ad479250 | |||
| 24d6565cad | |||
| e34ad6bf5f | |||
| 179b976964 | |||
| 1ff84671f8 | |||
| e1c22c5385 | |||
| 3f30a1540c | |||
| ae9153e644 | |||
| 003407a7e3 | |||
| a7764dc61d | |||
| 4b4778d569 | |||
| d96a63944c | |||
| ad7c090f4c | |||
| b9bad56fc1 | |||
| fdf0a6f6c7 | |||
| 86266b3d67 | |||
| 68cf3b928b | |||
| 3024c6efa9 | |||
| 500767a0fb | |||
| 5b5319a659 | |||
| 3ce7c52030 | |||
| bafcd1dc1c | |||
| 00b07659d9 | |||
| 9df94357de | |||
| 0e45f4cec4 | |||
| 8815788004 | |||
| b7e4fa16ca | |||
| 645d0db260 | |||
| f4b3c768e8 | |||
| 6b3bf554ce | |||
| c6121da151 | |||
| c5a75219ce | |||
| 89e7174cc4 | |||
| 4f16297e45 | |||
| 11898daed7 | |||
| 4d7da0059e | |||
| a4fcef4a62 | |||
| f2e267b494 | |||
| f9726ae0c8 | |||
| 199a675692 | |||
| 2f921dd13d | |||
| b92877990d | |||
| d7778c0212 | |||
| 531ac89e7e | |||
| 358555929d | |||
| 9230a7db16 | |||
| 7e53a1f048 | |||
| 45a1f98bd6 | |||
| 5faace0eb3 | |||
| 5656eaccb7 | |||
| 3b2f6e74a6 | |||
| 484aee069e | |||
| def7075695 | |||
| 2daa074561 | |||
| 6bd7f0231d | |||
| 70d9f7c62c | |||
| 5856d2e54e | |||
| a8fa98e2a6 | |||
| 73725905ba | |||
| 1e71d3d363 | |||
| c3127249ac | |||
| 91b980f5cb | |||
| d9e86dd68c | |||
| 11c8b22c5a | |||
| 1a52c56bd4 | |||
| 4cc91a2869 | |||
| 17cef18760 | |||
| 00bda65848 | |||
| 6697461003 | |||
| c820e44a99 | |||
| 472dcdb59c | |||
| 6691ff2072 | |||
| 9c6e55939e | |||
| 7e511931b3 | |||
| 4bd898c359 | |||
| 1fba430dfc | |||
| efe85c0d70 | |||
| 2ec0c16889 | |||
| 899de4f3bf | |||
| d1d9d483fe | |||
| 375e0e6827 | |||
| b8b31ff8a1 | |||
| c5c77b93fe | |||
| 6b905049eb | |||
| 88a7ca0b0a | |||
| e8012a64c3 | |||
| b9d366d203 | |||
| f5b0d9509d | |||
| 699da7c383 | |||
| 5bf74cae11 | |||
| 46128cb4b9 | |||
| 5dd48fc068 | |||
| 0ada78ad84 | |||
| 682f4715cf | |||
| c4a34fa26c | |||
| e3f185a696 | |||
| 1b312b6f3f | |||
| dc5a21fbd1 | |||
| e1c8a84fec | |||
| 410fe409d2 | |||
| e0c5068d0b | |||
| faba044735 | |||
| 4d885f9aad | |||
| fdf187ec46 | |||
| 0c42965fc3 | |||
| e1236a8672 | |||
| 7daff59cb1 | |||
| 5d309d5724 | |||
| eeb145ee0b | |||
| 9f5cda958e | |||
| 47779d6a53 | |||
| 2a43cc7574 | |||
| c4831d2586 | |||
| e4569defd9 | |||
| 65cb59517d | |||
| b729008f4d | |||
| 122d8b9e4e | |||
| b16f379788 | |||
| c868bc554f | |||
| 5c77c3739e | |||
| ee04442be7 | |||
| f067ff862b | |||
| 68864674cc | |||
| f1a335708a | |||
| d786635239 | |||
| 50fad9ed05 | |||
| 2b4ffe0391 | |||
| e7eaaaded1 | |||
| f6e52900a2 | |||
| 62b5e42769 | |||
| 1631be5660 | |||
| 9ccc72a9fb | |||
| 01b2856114 | |||
| 6dca7b28cc | |||
| 864b1c95cd | |||
| 168c60c18a | |||
| 1e49334209 | |||
| 79cc0ec4aa | |||
| fdfa4284de | |||
| 49d3ec58fc | |||
| c4405104a8 | |||
| 4b0443090a | |||
| b1bf1b0c82 | |||
| 5d45b94e93 | |||
| 598c972a78 | |||
| 2593b60f2b | |||
| e6f638132c | |||
| b823c46aae | |||
| 4d11307b84 | |||
| 03b6e51225 | |||
| 785598629a | |||
| 3452354a11 | |||
| bb1cdb6b48 | |||
| 37fa82c578 | |||
| 1f2d29ee08 | |||
| cffc8835c6 | |||
| 515c654a46 | |||
| aacb7e1604 | |||
| c86642dfee | |||
| f4a9b12340 | |||
| bd66763f26 | |||
| 2d3b02a89d | |||
| 8552e149b5 | |||
| e0d6b69195 | |||
| a3532bfccc | |||
| 6697f01d05 | |||
| 01c62f86b3 | |||
| 9912a52bb8 | |||
| 419e34e571 | |||
| f0e16bd31b | |||
| 43107e7eb1 | |||
| 81bd6be1c0 | |||
| d6c84c43ec | |||
| 70247c4a94 | |||
| 87b35e7d8e | |||
| a51c22b266 | |||
| 318d460256 | |||
| d7cfdc633a | |||
| 085af10afd | |||
| a8273a3009 | |||
| 580d9f81e2 | |||
| 79b0d71514 | |||
| f6b36a689a | |||
| 4b6126c057 | |||
| a108e701bc | |||
| 0ad561d379 | |||
| 1372265bd9 | |||
| 476ef0c3ca | |||
| 534d3229fe | |||
| 07097976ef | |||
| fb4fbeab50 | |||
| b169dc50ad | |||
| 02e32baa80 | |||
| 549f6c1e80 | |||
| 16b4c25f7d | |||
| af2e13e211 | |||
| 7c9307e970 | |||
| 4f599b7396 | |||
| 2784f6008e | |||
| be9ee9112c | |||
| 19a87e968e | |||
| 207b9a14f4 | |||
| 05ebca8677 | |||
| f846d9484c | |||
| 50069ef029 | |||
| 777ff3f1a8 | |||
| 89466cb33c | |||
| bf3a478990 | |||
| efb97cca82 | |||
| b692c0ed03 | |||
| 6eed2b0aee | |||
| 7876db03c6 | |||
| ffddbb586e | |||
| a3c11fc033 | |||
| 321de9ab9b | |||
| ec5f566ba5 | |||
| 2d6b77336b | |||
| 9a1ee48d73 | |||
| e22d1efdc0 | |||
| ff8097d37c | |||
| 7d44a4dcad | |||
| fdb454b9a4 | |||
| b4174377c2 | |||
| 174eae377f | |||
| 243ec88e95 | |||
| 9f3fd103ef | |||
| 0ad3800f54 | |||
| 473efb6d4f | |||
| 153f8895cb | |||
| f5b4f7d9e8 | |||
| 989625d2d4 | |||
| 28e5468162 | |||
| b1c0b65666 | |||
| 490aaa1ed8 | |||
| c7d2d6716d | |||
| eefe65d95a | |||
| 3c5d46166e | |||
| cfa3aa001f | |||
| ea5ef098cf | |||
| adcf236a3d | |||
| 72a9864788 | |||
| a6e87b40c7 | |||
| 094479b55f | |||
| db2c532c07 | |||
| 4a450c6fab | |||
| ed1bfb8177 | |||
| a35384fd57 | |||
| 9d31537ae5 | |||
| dee1b726f9 | |||
| 7cfff4049d | |||
| 45080e3724 | |||
| c3b5f5bf90 | |||
| 7e5afc8bbb | |||
| 7474af4f09 | |||
| 28a7b6103a | |||
| 3cddb0434d | |||
| 36a72115f9 | |||
| 3c0b5459be | |||
| 088be2d017 | |||
| a155a6b3e2 | |||
| a338a16b29 | |||
| 7613bdfdf9 | |||
| 57f27fa42f | |||
| 55d9d151fb | |||
| 70f31dee45 | |||
| 699f93fed7 | |||
| 4c6ca05af5 | |||
| 75c7596259 | |||
| 17d362fa85 | |||
| a428955438 | |||
| 4afac17e58 | |||
| 8bd8c4f627 | |||
| 0390709577 | |||
| 9f2970bc5c | |||
| 232225b2aa | |||
| 5caf714ff4 | |||
| cd11219fa7 | |||
| 44c652ec98 | |||
| cd8bc2f82a | |||
| 12f6330fae | |||
| fc0ea465e1 | |||
| 459338f6fd | |||
| f0add920f5 | |||
| 33c149077a | |||
| e00a882333 | |||
| 3ed5a90b7c | |||
| 3476402a75 | |||
| b04803de99 | |||
| f443cf965a | |||
| c9292f8e09 | |||
| bd65a19d71 | |||
| 85cb6cc5fb | |||
| 5f8a031c22 | |||
| dc564f3ef2 | |||
| d7ce13c763 | |||
| 7b9037b054 | |||
| 3162afbf16 | |||
| 1bd523f6b6 | |||
| 4c0d0ba5b3 | |||
| 212346a86d | |||
| 564803f56a | |||
| df708d16e4 | |||
| 848dedb576 | |||
| 4516e0d413 | |||
| a23375c49d | |||
| 53b937976d | |||
| 12f01f9b5f | |||
| 917978cbc2 | |||
| 3f5c3b1747 | |||
| bec7210b4b | |||
| 0a94dce41d | |||
| 9b3afcdac7 | |||
| 8eb524dc4d | |||
| dee8108bbc | |||
| 045c5dc1b4 | |||
| 24f8eac12a | |||
| eaf7a28250 | |||
| 7965c1969f | |||
| d0574197ea | |||
| 63b2787da6 | |||
| 1ff683b259 | |||
| 22bf4df290 | |||
| b56fc50c27 | |||
| d2aa6a98f2 | |||
| bf0819390f | |||
| 503ccb577c | |||
| 2f7e650122 | |||
| 68931a2091 | |||
| 7f79ccbec0 | |||
| 43c2e51d6e | |||
| 66b8f369dc |
@@ -0,0 +1,33 @@
|
||||
name: Send notification
|
||||
description: Sends a Google Chat message as a notification of the job's outcome
|
||||
inputs:
|
||||
webhook-url:
|
||||
description: 'Google Chat Webhook URL'
|
||||
required: true
|
||||
status:
|
||||
description: 'Status of the job'
|
||||
required: true
|
||||
build-scan-url:
|
||||
description: 'URL of the build scan to include in the notification'
|
||||
run-name:
|
||||
description: 'Name of the run to include in the notification'
|
||||
default: ${{ format('{0} {1}', github.ref_name, github.job) }}
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- shell: bash
|
||||
run: |
|
||||
echo "BUILD_SCAN=${{ inputs.build-scan-url == '' && ' [build scan unavailable]' || format(' [<{0}|Build Scan>]', inputs.build-scan-url) }}" >> "$GITHUB_ENV"
|
||||
echo "RUN_URL=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" >> "$GITHUB_ENV"
|
||||
- shell: bash
|
||||
if: ${{ inputs.status == 'success' }}
|
||||
run: |
|
||||
curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: "<${{ env.RUN_URL }}|${{ inputs.run-name }}> was successful ${{ env.BUILD_SCAN }}"}' || true
|
||||
- shell: bash
|
||||
if: ${{ inputs.status == 'failure' }}
|
||||
run: |
|
||||
curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: "<users/all> *<${{ env.RUN_URL }}|${{ inputs.run-name }}> failed* ${{ env.BUILD_SCAN }}"}' || true
|
||||
- shell: bash
|
||||
if: ${{ inputs.status == 'cancelled' }}
|
||||
run: |
|
||||
curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: "<${{ env.RUN_URL }}|${{ inputs.run-name }}> was cancelled"}' || true
|
||||
@@ -18,11 +18,13 @@ jobs:
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-java@v3
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
distribution: 'liberica'
|
||||
java-version: 17
|
||||
- name: Download BackportBot
|
||||
run: wget https://github.com/spring-io/backport-bot/releases/download/latest/backport-bot-0.0.1-SNAPSHOT.jar
|
||||
- name: Backport
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
name: Build and deploy snapshot
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 6.1.x
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
build-and-deploy-snapshot:
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
name: Build and deploy snapshot
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'liberica'
|
||||
java-version: 17
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5
|
||||
with:
|
||||
cache-read-only: false
|
||||
- name: Configure Gradle properties
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p $HOME/.gradle
|
||||
echo 'systemProp.user.name=spring-builds+github' >> $HOME/.gradle/gradle.properties
|
||||
echo 'systemProp.org.gradle.internal.launcher.welcomeMessageEnabled=false' >> $HOME/.gradle/gradle.properties
|
||||
echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties
|
||||
echo 'org.gradle.daemon=4' >> $HOME/.gradle/gradle.properties
|
||||
- name: Build and publish
|
||||
id: build
|
||||
env:
|
||||
CI: 'true'
|
||||
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
|
||||
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
|
||||
GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
|
||||
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
|
||||
run: ./gradlew -PdeploymentRepository=$(pwd)/deployment-repository build publishAllPublicationsToDeploymentRepository
|
||||
- name: Deploy
|
||||
uses: spring-io/artifactory-deploy-action@v0.0.1
|
||||
with:
|
||||
uri: 'https://repo.spring.io'
|
||||
username: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
build-name: ${{ format('spring-framework-{0}', github.ref_name)}}
|
||||
repository: 'libs-snapshot-local'
|
||||
folder: 'deployment-repository'
|
||||
signing-key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
signing-passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||
artifact-properties: |
|
||||
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
|
||||
/**/framework-api-*-docs.zip::zip.type=docs
|
||||
/**/framework-api-*-schema.zip::zip.type=schema
|
||||
- name: Send notification
|
||||
uses: ./.github/actions/send-notification
|
||||
if: always()
|
||||
with:
|
||||
webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
|
||||
status: ${{ job.status }}
|
||||
build-scan-url: ${{ steps.build.outputs.build-scan-url }}
|
||||
run-name: ${{ format('{0} | Linux | Java 17', github.ref_name) }}
|
||||
@@ -0,0 +1,80 @@
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 6.1.x
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
ci:
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- id: ubuntu-latest
|
||||
name: Linux
|
||||
java:
|
||||
- version: 17
|
||||
toolchain: false
|
||||
- version: 21
|
||||
toolchain: true
|
||||
exclude:
|
||||
- os:
|
||||
name: Linux
|
||||
java:
|
||||
version: 17
|
||||
name: '${{ matrix.os.name}} | Java ${{ matrix.java.version}}'
|
||||
runs-on: ${{ matrix.os.id }}
|
||||
steps:
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'liberica'
|
||||
java-version: |
|
||||
${{ matrix.java.version }}
|
||||
${{ matrix.java.toolchain && '17' || '' }}
|
||||
- name: Prepare Windows runner
|
||||
if: ${{ runner.os == 'Windows' }}
|
||||
run: |
|
||||
git config --global core.autocrlf true
|
||||
git config --global core.longPaths true
|
||||
Stop-Service -name Docker
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5
|
||||
with:
|
||||
cache-read-only: false
|
||||
- name: Configure Gradle properties
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p $HOME/.gradle
|
||||
echo 'systemProp.user.name=spring-builds+github' >> $HOME/.gradle/gradle.properties
|
||||
echo 'systemProp.org.gradle.internal.launcher.welcomeMessageEnabled=false' >> $HOME/.gradle/gradle.properties
|
||||
echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties
|
||||
echo 'org.gradle.daemon=4' >> $HOME/.gradle/gradle.properties
|
||||
- name: Configure toolchain properties
|
||||
if: ${{ matrix.java.toolchain }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo toolchainVersion=${{ matrix.java.version }} >> $HOME/.gradle/gradle.properties
|
||||
echo systemProp.org.gradle.java.installations.auto-detect=false >> $HOME/.gradle/gradle.properties
|
||||
echo systemProp.org.gradle.java.installations.auto-download=false >> $HOME/.gradle/gradle.properties
|
||||
echo systemProp.org.gradle.java.installations.paths=${{ format('$JAVA_HOME_{0}_X64', matrix.java.version) }} >> $HOME/.gradle/gradle.properties
|
||||
- name: Build
|
||||
id: build
|
||||
env:
|
||||
CI: 'true'
|
||||
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
|
||||
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
|
||||
GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
|
||||
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
|
||||
run: ./gradlew check antora
|
||||
- name: Send notification
|
||||
uses: ./.github/actions/send-notification
|
||||
if: always()
|
||||
with:
|
||||
webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
|
||||
status: ${{ job.status }}
|
||||
build-scan-url: ${{ steps.build.outputs.build-scan-url }}
|
||||
run-name: ${{ format('{0} | {1} | Java {2}', github.ref_name, matrix.os.name, matrix.java.version) }}
|
||||
@@ -1,12 +1,14 @@
|
||||
name: Deploy Docs
|
||||
on:
|
||||
push:
|
||||
branches-ignore: [ gh-pages ]
|
||||
tags: '**'
|
||||
branches:
|
||||
- 'main'
|
||||
- '*.x'
|
||||
- '!gh-pages'
|
||||
tags:
|
||||
- 'v*'
|
||||
repository_dispatch:
|
||||
types: request-build-reference # legacy
|
||||
schedule:
|
||||
- cron: '0 10 * * *' # Once per day at 10am UTC
|
||||
workflow_dispatch:
|
||||
permissions:
|
||||
actions: write
|
||||
@@ -15,8 +17,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'spring-projects'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: docs-build
|
||||
fetch-depth: 1
|
||||
|
||||
@@ -9,5 +9,5 @@ jobs:
|
||||
name: "Validation"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
- uses: actions/checkout@v4
|
||||
- uses: gradle/wrapper-validation-action@v2
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# Enable auto-env through the sdkman_auto_env config
|
||||
# Add key=value pairs of SDKs to use below
|
||||
java=17.0.8.1-librca
|
||||
java=17.0.10-librca
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [](https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-6.1.x?groups=Build") [](https://ge.spring.io/scans?search.rootProjectNames=spring)
|
||||
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [](https://github.com/spring-projects/spring-framework/actions/workflows/build-and-deploy-snapshot.yml?query=branch%3A6.1.x) [](https://ge.spring.io/scans?search.rootProjectNames=spring)
|
||||
|
||||
This is the home of the Spring Framework: the foundation for all [Spring projects](https://spring.io/projects). Collectively the Spring Framework and the family of Spring projects are often referred to simply as "Spring".
|
||||
|
||||
@@ -31,7 +31,7 @@ Information regarding CI builds can be found in the [Spring Framework Concourse
|
||||
|
||||
## Stay in Touch
|
||||
|
||||
Follow [@SpringCentral](https://twitter.com/springcentral), [@SpringFramework](https://twitter.com/springframework), and its [team members](https://twitter.com/springframework/lists/team/members) on Twitter. In-depth articles can be found at [The Spring Blog](https://spring.io/blog/), and releases are announced via our [releases feed](https://spring.io/blog/category/releases).
|
||||
Follow [@SpringCentral](https://twitter.com/springcentral), [@SpringFramework](https://twitter.com/springframework), and its [team members](https://twitter.com/springframework/lists/team/members) on 𝕏. In-depth articles can be found at [The Spring Blog](https://spring.io/blog/), and releases are announced via our [releases feed](https://spring.io/blog/category/releases).
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+2
-3
@@ -4,7 +4,7 @@ plugins {
|
||||
id 'org.jetbrains.kotlin.plugin.serialization' version "${kotlinVersion}" apply false
|
||||
id 'org.jetbrains.dokka' version '1.8.20'
|
||||
id 'org.unbroken-dome.xjc' version '2.0.0' apply false
|
||||
id 'com.github.ben-manes.versions' version '0.49.0'
|
||||
id 'com.github.ben-manes.versions' version '0.51.0'
|
||||
id 'com.github.johnrengelman.shadow' version '8.1.1' apply false
|
||||
id 'de.undercouch.download' version '5.4.0'
|
||||
id 'me.champeau.jmh' version '0.7.2' apply false
|
||||
@@ -90,7 +90,6 @@ configure([rootProject] + javaProjects) { project ->
|
||||
"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 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",
|
||||
@@ -104,7 +103,7 @@ configure([rootProject] + javaProjects) { project ->
|
||||
// TODO Uncomment link to JUnit 5 docs once we execute Gradle with Java 18+.
|
||||
// See https://github.com/spring-projects/spring-framework/issues/27497
|
||||
//
|
||||
// "https://junit.org/junit5/docs/5.10.1/api/",
|
||||
// "https://junit.org/junit5/docs/5.10.2/api/",
|
||||
"https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/",
|
||||
//"https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/",
|
||||
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
org.gradle.caching=true
|
||||
javaFormatVersion=0.0.39
|
||||
javaFormatVersion=0.0.41
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -50,12 +50,12 @@ public class CheckstyleConventions {
|
||||
project.getPlugins().apply(CheckstylePlugin.class);
|
||||
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g"));
|
||||
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
|
||||
checkstyle.setToolVersion("10.12.5");
|
||||
checkstyle.setToolVersion("10.15.0");
|
||||
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
|
||||
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
|
||||
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
|
||||
checkstyleDependencies
|
||||
.add(project.getDependencies().create("io.spring.javaformat:spring-javaformat-checkstyle:" + version));
|
||||
checkstyleDependencies.add(
|
||||
project.getDependencies().create("io.spring.javaformat:spring-javaformat-checkstyle:" + version));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -1,8 +1,10 @@
|
||||
== Spring Framework Concourse pipeline
|
||||
|
||||
NOTE: CI is being migrated to GitHub Actions.
|
||||
|
||||
The Spring Framework uses https://concourse-ci.org/[Concourse] for its CI build and other automated tasks.
|
||||
The Spring team has a dedicated Concourse instance available at https://ci.spring.io with a build pipeline
|
||||
for https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-6.0.x[Spring Framework 6.0.x].
|
||||
for https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-6.1.x[Spring Framework 6.1.x].
|
||||
|
||||
=== Setting up your development environment
|
||||
|
||||
@@ -51,7 +53,7 @@ The pipeline can be deployed using the following command:
|
||||
|
||||
[source]
|
||||
----
|
||||
$ fly -t spring set-pipeline -p spring-framework-6.0.x -c ci/pipeline.yml -l ci/parameters.yml
|
||||
$ fly -t spring set-pipeline -p spring-framework-6.1.x -c ci/pipeline.yml -l ci/parameters.yml
|
||||
----
|
||||
|
||||
NOTE: This assumes that you have credhub integration configured with the appropriate secrets.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ubuntu:jammy-20231128
|
||||
FROM ubuntu:jammy-20240125
|
||||
|
||||
ADD setup.sh /setup.sh
|
||||
ADD get-jdk-url.sh /get-jdk-url.sh
|
||||
@@ -7,6 +7,6 @@ RUN ./setup.sh
|
||||
ENV JAVA_HOME /opt/openjdk/java17
|
||||
ENV JDK17 /opt/openjdk/java17
|
||||
ENV JDK21 /opt/openjdk/java21
|
||||
ENV JDK22 /opt/openjdk/java22
|
||||
ENV JDK23 /opt/openjdk/java23
|
||||
|
||||
ENV PATH $JAVA_HOME/bin:$PATH
|
||||
|
||||
@@ -3,13 +3,13 @@ set -e
|
||||
|
||||
case "$1" in
|
||||
java17)
|
||||
echo "https://download.bell-sw.com/java/17.0.9+11/bellsoft-jdk17.0.9+11-linux-amd64.tar.gz"
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/17.0.10%2B13/bellsoft-jdk17.0.10+13-linux-amd64.tar.gz"
|
||||
;;
|
||||
java21)
|
||||
echo "https://download.bell-sw.com/java/21.0.1+12/bellsoft-jdk21.0.1+12-linux-amd64.tar.gz"
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/21.0.2%2B14/bellsoft-jdk21.0.2+14-linux-amd64.tar.gz"
|
||||
;;
|
||||
java22)
|
||||
echo "https://download.java.net/java/early_access/jdk22/27/GPL/openjdk-22-ea+27_linux-x64_bin.tar.gz"
|
||||
java23)
|
||||
echo "https://download.java.net/java/early_access/jdk23/10/GPL/openjdk-23-ea+10_linux-x64_bin.tar.gz"
|
||||
;;
|
||||
*)
|
||||
echo $"Unknown java version"
|
||||
|
||||
+1
-2
@@ -3,9 +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: "main"
|
||||
branch: "6.1.x"
|
||||
milestone: "6.1.x"
|
||||
build-name: "spring-framework"
|
||||
pipeline-name: "spring-framework"
|
||||
concourse-url: "https://ci.spring.io"
|
||||
task-timeout: 1h00m
|
||||
|
||||
+25
-163
@@ -23,14 +23,6 @@ anchors:
|
||||
docker-resource-source: &docker-resource-source
|
||||
username: ((docker-hub-username))
|
||||
password: ((docker-hub-password))
|
||||
slack-fail-params: &slack-fail-params
|
||||
text: >
|
||||
:concourse-failed: <https://ci.spring.io/teams/${BUILD_TEAM_NAME}/pipelines/${BUILD_PIPELINE_NAME}/jobs/${BUILD_JOB_NAME}/builds/${BUILD_NAME}|${BUILD_PIPELINE_NAME} ${BUILD_JOB_NAME} failed!>
|
||||
[$TEXT_FILE_CONTENT]
|
||||
text_file: git-repo/build/build-scan-uri.txt
|
||||
silent: true
|
||||
icon_emoji: ":concourse:"
|
||||
username: concourse-ci
|
||||
changelog-task-params: &changelog-task-params
|
||||
name: generated-changelog/tag
|
||||
tag: generated-changelog/tag
|
||||
@@ -64,12 +56,6 @@ resource_types:
|
||||
<<: *docker-resource-source
|
||||
repository: dpb587/github-status-resource
|
||||
tag: master
|
||||
- name: slack-notification
|
||||
type: registry-image
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: cfcommunity/slack-notification-resource
|
||||
tag: latest
|
||||
resources:
|
||||
- name: git-repo
|
||||
type: git
|
||||
@@ -90,13 +76,6 @@ resources:
|
||||
<<: *docker-resource-source
|
||||
repository: ((docker-hub-organization))/spring-framework-ci
|
||||
tag: ((milestone))
|
||||
- name: every-morning
|
||||
type: time
|
||||
icon: alarm
|
||||
source:
|
||||
start: 8:00 AM
|
||||
stop: 9:00 AM
|
||||
location: Europe/Vienna
|
||||
- name: artifactory-repo
|
||||
type: artifactory-resource
|
||||
icon: package-variant
|
||||
@@ -105,35 +84,6 @@ resources:
|
||||
username: ((artifactory-username))
|
||||
password: ((artifactory-password))
|
||||
build_name: ((build-name))
|
||||
- name: repo-status-build
|
||||
type: github-status-resource
|
||||
icon: eye-check-outline
|
||||
source:
|
||||
repository: ((github-repo-name))
|
||||
access_token: ((github-ci-status-token))
|
||||
branch: ((branch))
|
||||
context: 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: jdk21-build
|
||||
- name: repo-status-jdk22-build
|
||||
type: github-status-resource
|
||||
icon: eye-check-outline
|
||||
source:
|
||||
repository: ((github-repo-name))
|
||||
access_token: ((github-ci-status-token))
|
||||
branch: ((branch))
|
||||
context: jdk22-build
|
||||
- name: slack-alert
|
||||
type: slack-notification
|
||||
icon: slack
|
||||
source:
|
||||
url: ((slack-webhook-url))
|
||||
- name: github-pre-release
|
||||
type: github-release
|
||||
icon: briefcase-download-outline
|
||||
@@ -168,113 +118,6 @@ jobs:
|
||||
- put: ci-image
|
||||
params:
|
||||
image: ci-image/image.tar
|
||||
- name: build
|
||||
serial: true
|
||||
public: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
trigger: true
|
||||
- put: repo-status-build
|
||||
params: { state: "pending", commit: "git-repo" }
|
||||
- do:
|
||||
- task: build-project
|
||||
image: ci-image
|
||||
file: git-repo/ci/tasks/build-project.yml
|
||||
privileged: true
|
||||
timeout: ((task-timeout))
|
||||
params:
|
||||
<<: *build-project-task-params
|
||||
on_failure:
|
||||
do:
|
||||
- put: repo-status-build
|
||||
params: { state: "failure", commit: "git-repo" }
|
||||
- put: slack-alert
|
||||
params:
|
||||
<<: *slack-fail-params
|
||||
- put: repo-status-build
|
||||
params: { state: "success", commit: "git-repo" }
|
||||
- put: artifactory-repo
|
||||
params: &artifactory-params
|
||||
signing_key: ((signing-key))
|
||||
signing_passphrase: ((signing-passphrase))
|
||||
repo: libs-snapshot-local
|
||||
folder: distribution-repository
|
||||
build_uri: "https://ci.spring.io/teams/${BUILD_TEAM_NAME}/pipelines/${BUILD_PIPELINE_NAME}/jobs/${BUILD_JOB_NAME}/builds/${BUILD_NAME}"
|
||||
build_number: "${BUILD_PIPELINE_NAME}-${BUILD_JOB_NAME}-${BUILD_NAME}"
|
||||
disable_checksum_uploads: true
|
||||
threads: 8
|
||||
artifact_set:
|
||||
- include:
|
||||
- "/**/framework-api-*.zip"
|
||||
properties:
|
||||
"zip.name": "spring-framework"
|
||||
"zip.displayname": "Spring Framework"
|
||||
"zip.deployed": "false"
|
||||
- include:
|
||||
- "/**/framework-api-*-docs.zip"
|
||||
properties:
|
||||
"zip.type": "docs"
|
||||
- include:
|
||||
- "/**/framework-api-*-schema.zip"
|
||||
properties:
|
||||
"zip.type": "schema"
|
||||
get_params:
|
||||
threads: 8
|
||||
- name: jdk21-build
|
||||
serial: true
|
||||
public: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
- get: every-morning
|
||||
trigger: true
|
||||
- put: repo-status-jdk21-build
|
||||
params: { state: "pending", commit: "git-repo" }
|
||||
- do:
|
||||
- task: check-project
|
||||
image: ci-image
|
||||
file: git-repo/ci/tasks/check-project.yml
|
||||
privileged: true
|
||||
timeout: ((task-timeout))
|
||||
params:
|
||||
TEST_TOOLCHAIN: 21
|
||||
<<: *build-project-task-params
|
||||
on_failure:
|
||||
do:
|
||||
- put: repo-status-jdk21-build
|
||||
params: { state: "failure", commit: "git-repo" }
|
||||
- put: slack-alert
|
||||
params:
|
||||
<<: *slack-fail-params
|
||||
- put: repo-status-jdk21-build
|
||||
params: { state: "success", commit: "git-repo" }
|
||||
- name: jdk22-build
|
||||
serial: true
|
||||
public: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
- put: repo-status-jdk22-build
|
||||
params: { state: "pending", commit: "git-repo" }
|
||||
- do:
|
||||
- task: check-project
|
||||
image: ci-image
|
||||
file: git-repo/ci/tasks/check-project.yml
|
||||
privileged: true
|
||||
timeout: ((task-timeout))
|
||||
params:
|
||||
TEST_TOOLCHAIN: 22
|
||||
<<: *build-project-task-params
|
||||
on_failure:
|
||||
do:
|
||||
- put: repo-status-jdk22-build
|
||||
params: { state: "failure", commit: "git-repo" }
|
||||
- put: slack-alert
|
||||
params:
|
||||
<<: *slack-fail-params
|
||||
- put: repo-status-jdk22-build
|
||||
params: { state: "success", commit: "git-repo" }
|
||||
- name: stage-milestone
|
||||
serial: true
|
||||
plan:
|
||||
@@ -288,9 +131,32 @@ jobs:
|
||||
RELEASE_TYPE: M
|
||||
<<: *gradle-enterprise-task-params
|
||||
- put: artifactory-repo
|
||||
params:
|
||||
<<: *artifactory-params
|
||||
params: &artifactory-params
|
||||
signing_key: ((signing-key))
|
||||
signing_passphrase: ((signing-passphrase))
|
||||
repo: libs-staging-local
|
||||
folder: distribution-repository
|
||||
build_uri: "https://ci.spring.io/teams/${BUILD_TEAM_NAME}/pipelines/${BUILD_PIPELINE_NAME}/jobs/${BUILD_JOB_NAME}/builds/${BUILD_NAME}"
|
||||
build_number: "${BUILD_PIPELINE_NAME}-${BUILD_JOB_NAME}-${BUILD_NAME}"
|
||||
disable_checksum_uploads: true
|
||||
threads: 8
|
||||
artifact_set:
|
||||
- include:
|
||||
- "/**/framework-api-*.zip"
|
||||
properties:
|
||||
"zip.name": "spring-framework"
|
||||
"zip.displayname": "Spring Framework"
|
||||
"zip.deployed": "false"
|
||||
- include:
|
||||
- "/**/framework-api-*-docs.zip"
|
||||
properties:
|
||||
"zip.type": "docs"
|
||||
- include:
|
||||
- "/**/framework-api-*-schema.zip"
|
||||
properties:
|
||||
"zip.type": "schema"
|
||||
get_params:
|
||||
threads: 8
|
||||
- put: git-repo
|
||||
params:
|
||||
repository: stage-git-repo
|
||||
@@ -335,7 +201,6 @@ jobs:
|
||||
- put: artifactory-repo
|
||||
params:
|
||||
<<: *artifactory-params
|
||||
repo: libs-staging-local
|
||||
- put: git-repo
|
||||
params:
|
||||
repository: stage-git-repo
|
||||
@@ -380,7 +245,6 @@ jobs:
|
||||
- put: artifactory-repo
|
||||
params:
|
||||
<<: *artifactory-params
|
||||
repo: libs-staging-local
|
||||
- put: git-repo
|
||||
params:
|
||||
repository: stage-git-repo
|
||||
@@ -425,8 +289,6 @@ jobs:
|
||||
<<: *changelog-task-params
|
||||
|
||||
groups:
|
||||
- name: "builds"
|
||||
jobs: ["build", "jdk21-build", "jdk22-build"]
|
||||
- name: "releases"
|
||||
jobs: ["stage-milestone", "stage-rc", "stage-release", "promote-milestone", "promote-rc", "promote-release", "create-github-release"]
|
||||
- name: "ci-images"
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
source $(dirname $0)/common.sh
|
||||
|
||||
pushd git-repo > /dev/null
|
||||
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Porg.gradle.java.installations.fromEnv=JDK17,JDK21 \
|
||||
--no-daemon --max-workers=4 check
|
||||
popd > /dev/null
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
source $(dirname $0)/common.sh
|
||||
repository=$(pwd)/distribution-repository
|
||||
|
||||
pushd git-repo > /dev/null
|
||||
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Porg.gradle.java.installations.fromEnv=JDK17,JDK21,JDK22 \
|
||||
--no-daemon --max-workers=4 -PdeploymentRepository=${repository} build publishAllPublicationsToDeploymentRepository
|
||||
popd > /dev/null
|
||||
@@ -1,9 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
source $(dirname $0)/common.sh
|
||||
|
||||
pushd git-repo > /dev/null
|
||||
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Porg.gradle.java.installations.fromEnv=JDK17,JDK21 \
|
||||
-PmainToolchain=${MAIN_TOOLCHAIN} -PtestToolchain=${TEST_TOOLCHAIN} --no-daemon --max-workers=4 check antora
|
||||
popd > /dev/null
|
||||
@@ -1,19 +0,0 @@
|
||||
---
|
||||
platform: linux
|
||||
inputs:
|
||||
- name: git-repo
|
||||
caches:
|
||||
- path: gradle
|
||||
params:
|
||||
BRANCH:
|
||||
CI: true
|
||||
GRADLE_ENTERPRISE_ACCESS_KEY:
|
||||
GRADLE_ENTERPRISE_CACHE_USERNAME:
|
||||
GRADLE_ENTERPRISE_CACHE_PASSWORD:
|
||||
GRADLE_ENTERPRISE_URL: https://ge.spring.io
|
||||
run:
|
||||
path: bash
|
||||
args:
|
||||
- -ec
|
||||
- |
|
||||
${PWD}/git-repo/ci/scripts/build-pr.sh
|
||||
@@ -1,22 +0,0 @@
|
||||
---
|
||||
platform: linux
|
||||
inputs:
|
||||
- name: git-repo
|
||||
outputs:
|
||||
- name: distribution-repository
|
||||
- name: git-repo
|
||||
caches:
|
||||
- path: gradle
|
||||
params:
|
||||
BRANCH:
|
||||
CI: true
|
||||
GRADLE_ENTERPRISE_ACCESS_KEY:
|
||||
GRADLE_ENTERPRISE_CACHE_USERNAME:
|
||||
GRADLE_ENTERPRISE_CACHE_PASSWORD:
|
||||
GRADLE_ENTERPRISE_URL: https://ge.spring.io
|
||||
run:
|
||||
path: bash
|
||||
args:
|
||||
- -ec
|
||||
- |
|
||||
${PWD}/git-repo/ci/scripts/build-project.sh
|
||||
@@ -1,24 +0,0 @@
|
||||
---
|
||||
platform: linux
|
||||
inputs:
|
||||
- name: git-repo
|
||||
outputs:
|
||||
- name: distribution-repository
|
||||
- name: git-repo
|
||||
caches:
|
||||
- path: gradle
|
||||
params:
|
||||
BRANCH:
|
||||
CI: true
|
||||
MAIN_TOOLCHAIN:
|
||||
TEST_TOOLCHAIN:
|
||||
GRADLE_ENTERPRISE_ACCESS_KEY:
|
||||
GRADLE_ENTERPRISE_CACHE_USERNAME:
|
||||
GRADLE_ENTERPRISE_CACHE_PASSWORD:
|
||||
GRADLE_ENTERPRISE_URL: https://ge.spring.io
|
||||
run:
|
||||
path: bash
|
||||
args:
|
||||
- -ec
|
||||
- |
|
||||
${PWD}/git-repo/ci/scripts/check-project.sh
|
||||
@@ -28,7 +28,7 @@ javadoc {
|
||||
header = rootProject.description
|
||||
use = true
|
||||
overview = project.relativePath("$rootProject.rootDir/framework-docs/src/docs/api/overview.html")
|
||||
destinationDir = file("${project.buildDir}/docs/javadoc-api")
|
||||
destinationDir = file("$project.docsDir/javadoc-api")
|
||||
splitIndex = true
|
||||
links(rootProject.ext.javadocLinks)
|
||||
addBooleanOption('Xdoclint:syntax,reference', true) // only check syntax and reference with doclint
|
||||
@@ -52,7 +52,7 @@ rootProject.tasks.dokkaHtmlMultiModule.configure {
|
||||
tasks.named("javadoc")
|
||||
}
|
||||
moduleName.set("spring-framework")
|
||||
outputDirectory.set(project.file("$buildDir/docs/kdoc-api"))
|
||||
outputDirectory.set(file("$docsDir/kdoc-api"))
|
||||
includes.from("$rootProject.rootDir/framework-docs/src/docs/api/dokka-overview.md")
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ asciidoc:
|
||||
# FIXME: The package is not renamed
|
||||
chomp: 'all'
|
||||
fold: 'all'
|
||||
table-stripes: 'odd'
|
||||
include-java: 'example$docs-src/main/java/org/springframework/docs'
|
||||
spring-site: 'https://spring.io'
|
||||
spring-site-blog: '{spring-site}/blog'
|
||||
|
||||
@@ -28,8 +28,8 @@ antora {
|
||||
'@antora/atlas-extension': '1.0.0-alpha.1',
|
||||
'@antora/collector-extension': '1.0.0-alpha.3',
|
||||
'@asciidoctor/tabs': '1.0.0-beta.3',
|
||||
'@opendevise/antora-release-line-extension': '1.0.0-alpha.2',
|
||||
'@springio/antora-extensions': '1.3.0',
|
||||
'@opendevise/antora-release-line-extension': '1.0.0',
|
||||
'@springio/antora-extensions': '1.8.2',
|
||||
'@springio/asciidoctor-extensions': '1.0.0-alpha.9'
|
||||
]
|
||||
}
|
||||
|
||||
@@ -39,8 +39,8 @@
|
||||
** xref:core/resources.adoc[]
|
||||
** xref:core/validation.adoc[]
|
||||
*** xref:core/validation/validator.adoc[]
|
||||
*** xref:core/validation/conversion.adoc[]
|
||||
*** xref:core/validation/beans-beans.adoc[]
|
||||
*** xref:core/validation/conversion.adoc[]
|
||||
*** xref:core/validation/convert.adoc[]
|
||||
*** xref:core/validation/format.adoc[]
|
||||
*** xref:core/validation/format-configuring-formatting-globaldatetimeformat.adoc[]
|
||||
@@ -421,7 +421,7 @@
|
||||
*** xref:integration/cache/specific-config.adoc[]
|
||||
** xref:integration/observability.adoc[]
|
||||
** xref:integration/checkpoint-restore.adoc[]
|
||||
** xref:integration/class-data-sharing.adoc[]
|
||||
** xref:integration/cds.adoc[]
|
||||
** xref:integration/appendix.adoc[]
|
||||
* xref:languages.adoc[]
|
||||
** xref:languages/kotlin.adoc[]
|
||||
@@ -439,4 +439,5 @@
|
||||
** xref:languages/groovy.adoc[]
|
||||
** xref:languages/dynamic.adoc[]
|
||||
* xref:appendix.adoc[]
|
||||
* {spring-framework-wiki}[Wiki]
|
||||
* {spring-framework-wiki}[Wiki]
|
||||
|
||||
|
||||
@@ -19,15 +19,53 @@ of the classpath -- for example, deployed within the application's JAR file.
|
||||
The following table lists all currently supported Spring properties.
|
||||
|
||||
.Supported Spring Properties
|
||||
[cols="1,1"]
|
||||
|===
|
||||
| Name | Description
|
||||
|
||||
| `spring.aot.enabled`
|
||||
| Indicates the application should run with AOT generated artifacts. See
|
||||
xref:core/aot.adoc[Ahead of Time Optimizations] and
|
||||
{spring-framework-api}++/aot/AotDetector.html#AOT_ENABLED++[`AotDetector`]
|
||||
for details.
|
||||
|
||||
| `spring.beaninfo.ignore`
|
||||
| Instructs Spring to use the `Introspector.IGNORE_ALL_BEANINFO` mode when calling the
|
||||
JavaBeans `Introspector`. See
|
||||
{spring-framework-api}++/beans/StandardBeanInfoFactory.html#IGNORE_BEANINFO_PROPERTY_NAME++[`CachedIntrospectionResults`]
|
||||
for details.
|
||||
|
||||
| `spring.cache.reactivestreams.ignore`
|
||||
| Instructs Spring's caching infrastructure to ignore the presence of Reactive Streams,
|
||||
in particular Reactor's `Mono`/`Flux` in `@Cacheable` method return type declarations. See
|
||||
{spring-framework-api}++/cache/interceptor/CacheAspectSupport.html#IGNORE_REACTIVESTREAMS_PROPERTY_NAME++[`CacheAspectSupport`]
|
||||
for details.
|
||||
|
||||
| `spring.classformat.ignore`
|
||||
| Instructs Spring to ignore class format exceptions during classpath scanning, in
|
||||
particular for unsupported class file versions. See
|
||||
{spring-framework-api}++/context/annotation/ClassPathScanningCandidateComponentProvider.html#IGNORE_CLASSFORMAT_PROPERTY_NAME++[`ClassPathScanningCandidateComponentProvider`]
|
||||
for details.
|
||||
|
||||
| `spring.context.checkpoint`
|
||||
| Property that specifies a common context checkpoint. See
|
||||
xref:integration/checkpoint-restore.adoc#_automatic_checkpointrestore_at_startup[Automatic
|
||||
checkpoint/restore at startup] and
|
||||
{spring-framework-api}++/context/support/DefaultLifecycleProcessor.html#CHECKPOINT_PROPERTY_NAME++[`DefaultLifecycleProcessor`]
|
||||
for details.
|
||||
|
||||
| `spring.context.exit`
|
||||
| Property for terminating the JVM when the context reaches a specific phase. See
|
||||
xref:integration/checkpoint-restore.adoc#_automatic_checkpointrestore_at_startup[Automatic
|
||||
checkpoint/restore at startup] and
|
||||
{spring-framework-api}++/context/support/DefaultLifecycleProcessor.html#EXIT_PROPERTY_NAME++[`DefaultLifecycleProcessor`]
|
||||
for details.
|
||||
|
||||
| `spring.context.expression.maxLength`
|
||||
| The maximum length for
|
||||
xref:core/expressions/evaluation.adoc#expressions-parser-configuration[Spring Expression Language]
|
||||
expressions used in XML bean definitions, `@Value`, etc.
|
||||
|
||||
| `spring.expression.compiler.mode`
|
||||
| The mode to use when compiling expressions for the
|
||||
xref:core/expressions/evaluation.adoc#expressions-compiler-configuration[Spring Expression Language].
|
||||
|
||||
@@ -291,6 +291,8 @@ to consider:
|
||||
* `final` classes cannot be proxied, because they cannot be extended.
|
||||
* `final` methods cannot be advised, because they cannot be overridden.
|
||||
* `private` methods cannot be advised, because they cannot be overridden.
|
||||
* Methods that are not visible, typically package private methods in a parent class
|
||||
from a different package, cannot be advised because they are effectively private.
|
||||
|
||||
NOTE: There is no need to add CGLIB to your classpath. CGLIB is repackaged and included
|
||||
in the `spring-core` JAR. In other words, CGLIB-based AOP works "out of the box", as do
|
||||
|
||||
@@ -168,7 +168,7 @@ Kotlin::
|
||||
======
|
||||
|
||||
NOTE: Pooling stateless service objects is not usually necessary. We do not believe it should
|
||||
be the default choice, as most stateless objects are naturally thread safe, and instance
|
||||
be the default choice, as most stateless objects are naturally thread-safe, and instance
|
||||
pooling is problematic if resources are cached.
|
||||
|
||||
Simpler pooling is available by using auto-proxying. You can set the `TargetSource` implementations
|
||||
|
||||
@@ -521,8 +521,8 @@ standard AspectJ. The following example shows the `aop.xml` file:
|
||||
<aspectj>
|
||||
|
||||
<weaver>
|
||||
<!-- only weave classes in our application-specific packages -->
|
||||
<include within="com.xyz.*"/>
|
||||
<!-- only weave classes in our application-specific packages and sub-packages -->
|
||||
<include within="com.xyz..*"/>
|
||||
</weaver>
|
||||
|
||||
<aspects>
|
||||
@@ -533,6 +533,11 @@ standard AspectJ. The following example shows the `aop.xml` file:
|
||||
</aspectj>
|
||||
----
|
||||
|
||||
NOTE: It is recommended to only weave specific classes (typically those in the
|
||||
application packages, as shown in the `aop.xml` example above) in order
|
||||
to avoid side effects such as AspectJ dump files and warnings.
|
||||
This is also a best practice from an efficiency perspective.
|
||||
|
||||
Now we can move on to the Spring-specific portion of the configuration. We need
|
||||
to configure a `LoadTimeWeaver` (explained later). This load-time weaver is the
|
||||
essential component responsible for weaving the aspect configuration in one or
|
||||
@@ -714,10 +719,29 @@ Furthermore, the compiled aspect classes need to be available on the classpath.
|
||||
|
||||
|
||||
[[aop-aj-ltw-aop_dot_xml]]
|
||||
=== 'META-INF/aop.xml'
|
||||
=== `META-INF/aop.xml`
|
||||
|
||||
The AspectJ LTW infrastructure is configured by using one or more `META-INF/aop.xml`
|
||||
files that are on the Java classpath (either directly or, more typically, in jar files).
|
||||
For example:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd">
|
||||
<aspectj>
|
||||
|
||||
<weaver>
|
||||
<!-- only weave classes in our application-specific packages and sub-packages -->
|
||||
<include within="com.xyz..*"/>
|
||||
</weaver>
|
||||
|
||||
</aspectj>
|
||||
----
|
||||
|
||||
NOTE: It is recommended to only weave specific classes (typically those in the
|
||||
application packages, as shown in the `aop.xml` example above) in order
|
||||
to avoid side effects such as AspectJ dump files and warnings.
|
||||
This is also a best practice from an efficiency perspective.
|
||||
|
||||
The structure and contents of this file is detailed in the LTW part of the
|
||||
{aspectj-docs-devguide}/ltw-configuration.html[AspectJ reference
|
||||
|
||||
@@ -15,10 +15,13 @@ Applying such optimizations early implies the following restrictions:
|
||||
|
||||
* The classpath is fixed and fully defined at build time.
|
||||
* The beans defined in your application cannot change at runtime, meaning:
|
||||
** `@Profile`, in particular profile-specific configuration needs to be chosen at build time.
|
||||
** `@Profile`, in particular profile-specific configuration, needs to be chosen at build time and is automatically enabled at runtime when AOT is enabled.
|
||||
** `Environment` properties that impact the presence of a bean (`@Conditional`) are only considered at build time.
|
||||
* Bean definitions with instance suppliers (lambdas or method references) cannot be transformed ahead-of-time (see related {spring-framework-issues}/29555[spring-framework#29555] issue).
|
||||
* Make sure that the bean type is as precise as possible.
|
||||
* Bean definitions with instance suppliers (lambdas or method references) cannot be transformed ahead-of-time.
|
||||
* Beans registered as singletons (using `registerSingleton`, typically from
|
||||
`ConfigurableListableBeanFactory`) cannot be transformed ahead-of-time either.
|
||||
* As we cannot rely on the instance, make sure that the bean type is as precise as
|
||||
possible.
|
||||
|
||||
TIP: See also the xref:core/aot.adoc#aot.bestpractices[] section.
|
||||
|
||||
@@ -27,7 +30,7 @@ A Spring AOT processed application typically generates:
|
||||
|
||||
* Java source code
|
||||
* Bytecode (usually for dynamic proxies)
|
||||
* {spring-framework-api}/aot/hint/RuntimeHints.html[`RuntimeHints`] for the use of reflection, resource loading, serialization, and JDK proxies.
|
||||
* {spring-framework-api}/aot/hint/RuntimeHints.html[`RuntimeHints`] for the use of reflection, resource loading, serialization, and JDK proxies
|
||||
|
||||
NOTE: At the moment, AOT is focused on allowing Spring applications to be deployed as native images using GraalVM.
|
||||
We intend to support more JVM-based use cases in future generations.
|
||||
@@ -35,7 +38,7 @@ We intend to support more JVM-based use cases in future generations.
|
||||
[[aot.basics]]
|
||||
== AOT engine overview
|
||||
|
||||
The entry point of the AOT engine for processing an `ApplicationContext` arrangement is `ApplicationContextAotGenerator`. It takes care of the following steps, based on a `GenericApplicationContext` that represents the application to optimize and a {spring-framework-api}/aot/generate/GenerationContext.html[`GenerationContext`]:
|
||||
The entry point of the AOT engine for processing an `ApplicationContext` is `ApplicationContextAotGenerator`. It takes care of the following steps, based on a `GenericApplicationContext` that represents the application to optimize and a {spring-framework-api}/aot/generate/GenerationContext.html[`GenerationContext`]:
|
||||
|
||||
* Refresh an `ApplicationContext` for AOT processing. Contrary to a traditional refresh, this version only creates bean definitions, not bean instances.
|
||||
* Invoke the available `BeanFactoryInitializationAotProcessor` implementations and apply their contributions against the `GenerationContext`.
|
||||
@@ -67,7 +70,14 @@ include-code::./AotProcessingSample[tag=aotcontext]
|
||||
In this mode, xref:core/beans/factory-extension.adoc#beans-factory-extension-factory-postprocessors[`BeanFactoryPostProcessor` implementations] are invoked as usual.
|
||||
This includes configuration class parsing, import selectors, classpath scanning, etc.
|
||||
Such steps make sure that the `BeanRegistry` contains the relevant bean definitions for the application.
|
||||
If bean definitions are guarded by conditions (such as `@Profile`), these are discarded at this stage.
|
||||
If bean definitions are guarded by conditions (such as `@Profile`), these are evaluated,
|
||||
and bean definitions that don't match their conditions are discarded at this stage.
|
||||
|
||||
If custom code needs to register extra beans programmatically, make sure that custom
|
||||
registration code uses `BeanDefinitionRegistry` instead of `BeanFactory` as only bean
|
||||
definitions are taken into account. A good pattern is to implement
|
||||
`ImportBeanDefinitionRegistrar` and register it via an `@Import` on one of your
|
||||
configuration classes.
|
||||
|
||||
Because this mode does not actually create bean instances, `BeanPostProcessor` implementations are not invoked, except for specific variants that are relevant for AOT processing.
|
||||
These are:
|
||||
@@ -84,12 +94,12 @@ Once this part completes, the `BeanFactory` contains the bean definitions that a
|
||||
Components that want to participate in this step can implement the {spring-framework-api}/beans/factory/aot/BeanFactoryInitializationAotProcessor.html[`BeanFactoryInitializationAotProcessor`] interface.
|
||||
Each implementation can return an AOT contribution, based on the state of the bean factory.
|
||||
|
||||
An AOT contribution is a component that contributes generated code that reproduces a particular behavior.
|
||||
An AOT contribution is a component that contributes generated code which reproduces a particular behavior.
|
||||
It can also contribute `RuntimeHints` to indicate the need for reflection, resource loading, serialization, or JDK proxies.
|
||||
|
||||
A `BeanFactoryInitializationAotProcessor` implementation can be registered in `META-INF/spring/aot.factories` with a key equal to the fully qualified name of the interface.
|
||||
A `BeanFactoryInitializationAotProcessor` implementation can be registered in `META-INF/spring/aot.factories` with a key equal to the fully-qualified name of the interface.
|
||||
|
||||
A `BeanFactoryInitializationAotProcessor` can also be implemented directly by a bean.
|
||||
The `BeanFactoryInitializationAotProcessor` interface can also be implemented directly by a bean.
|
||||
In this mode, the bean provides an AOT contribution equivalent to the feature it provides with a regular runtime.
|
||||
Consequently, such a bean is automatically excluded from the AOT-optimized context.
|
||||
|
||||
@@ -111,7 +121,7 @@ This interface is used as follows:
|
||||
|
||||
* Implemented by a `BeanPostProcessor` bean, to replace its runtime behavior.
|
||||
For instance xref:core/beans/factory-extension.adoc#beans-factory-extension-bpp-examples-aabpp[`AutowiredAnnotationBeanPostProcessor`] implements this interface to generate code that injects members annotated with `@Autowired`.
|
||||
* Implemented by a type registered in `META-INF/spring/aot.factories` with a key equal to the fully qualified name of the interface.
|
||||
* Implemented by a type registered in `META-INF/spring/aot.factories` with a key equal to the fully-qualified name of the interface.
|
||||
Typically used when the bean definition needs to be tuned for specific features of the core framework.
|
||||
|
||||
[NOTE]
|
||||
@@ -142,8 +152,23 @@ Java::
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class DataSourceConfiguration {
|
||||
|
||||
@Bean
|
||||
fun dataSource() = SimpleDataSource()
|
||||
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
WARNING: Kotlin class names with backticks that use invalid Java identifiers (not starting with a letter, containing spaces, etc.) are not supported.
|
||||
|
||||
Since there isn't any particular condition on this class, `dataSourceConfiguration` and `dataSource` are identified as candidates.
|
||||
The AOT engine will convert the configuration class above to code similar to the following:
|
||||
|
||||
@@ -199,6 +224,17 @@ There is a bean definition for `dataSourceConfiguration` and one for `dataSource
|
||||
When a `datasource` instance is required, a `BeanInstanceSupplier` is called.
|
||||
This supplier invokes the `dataSource()` method on the `dataSourceConfiguration` bean.
|
||||
|
||||
[[aot.running]]
|
||||
== Running with AOT optimizations
|
||||
|
||||
AOT is a mandatory step to transform a Spring application to a native executable, so it
|
||||
is automatically enabled when running in this mode. It is possible to use those optimizations
|
||||
on the JVM by setting the `spring.aot.enabled` System property to `true`.
|
||||
|
||||
NOTE: When AOT optimizations are included, some decisions that have been taken at build-time
|
||||
are hard-coded in the application setup. For instance, profiles that have been enabled at
|
||||
build-time are automatically enabled at runtime as well.
|
||||
|
||||
[[aot.bestpractices]]
|
||||
== Best Practices
|
||||
|
||||
@@ -207,11 +243,33 @@ However, keep in mind that some optimizations are made at build time based on a
|
||||
|
||||
This section lists the best practices that make sure your application is ready for AOT.
|
||||
|
||||
[[aot.bestpractices.bean-registration]]
|
||||
== Programmatic bean registration
|
||||
|
||||
The AOT engine takes care of the `@Configuration` model and any callback that might be
|
||||
invoked as part of processing your configuration. If you need to register additional
|
||||
beans programmatically, make sure to use a `BeanDefinitionRegistry` to register
|
||||
bean definitions.
|
||||
|
||||
This can typically be done via a `BeanDefinitionRegistryPostProcessor`. Note that, if it
|
||||
is registered itself as a bean, it will be invoked again at runtime unless you make
|
||||
sure to implement `BeanFactoryInitializationAotProcessor` as well. A more idiomatic
|
||||
way is to implement `ImportBeanDefinitionRegistrar` and register it using `@Import` on
|
||||
one of your configuration classes. This invokes your custom code as part of configuration
|
||||
class parsing.
|
||||
|
||||
If you declare additional beans programmatically using a different callback, they are
|
||||
likely not going to be handled by the AOT engine, and therefore no hints are going to be
|
||||
generated for them. Depending on the environment, those beans may not be registered at
|
||||
all. For instance, classpath scanning does not work in a native image as there is no
|
||||
notion of a classpath. For cases like this, it is crucial that the scanning happens at
|
||||
build time.
|
||||
|
||||
[[aot.bestpractices.bean-type]]
|
||||
=== Expose The Most Precise Bean Type
|
||||
|
||||
While your application may interact with an interface that a bean implements, it is still very important to declare the most precise type.
|
||||
The AOT engine performs additional checks on the bean type, such as detecting the presence of `@Autowired` members, or lifecycle callback methods.
|
||||
The AOT engine performs additional checks on the bean type, such as detecting the presence of `@Autowired` members or lifecycle callback methods.
|
||||
|
||||
For `@Configuration` classes, make sure that the return type of the factory `@Bean` method is as precise as possible.
|
||||
Consider the following example:
|
||||
@@ -262,10 +320,11 @@ If you are registering bean definitions programmatically, consider using `RootBe
|
||||
|
||||
[[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 {spring-framework-api}/beans/factory/support/AbstractBeanDefinition.html#PREFERRED_CONSTRUCTORS_ATTRIBUTE[`preferredConstructors` attribute] on the related bean definition to indicate which constructor should be used.
|
||||
In case you are working on a code base that you cannot modify, you can set the {spring-framework-api}/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
|
||||
@@ -283,7 +342,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class ClientFactoryBean<T extends AbstractClient> implements FactoryBean<T> {
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
@@ -380,7 +439,7 @@ Java::
|
||||
|
||||
Running an application as a native image requires additional information compared to a regular JVM runtime.
|
||||
For instance, GraalVM needs to know ahead of time if a component uses reflection.
|
||||
Similarly, classpath resources are not shipped in a native image unless specified explicitly.
|
||||
Similarly, classpath resources are not included in a native image unless specified explicitly.
|
||||
Consequently, if the application needs to load a resource, it must be referenced from the corresponding GraalVM native image configuration file.
|
||||
|
||||
The {spring-framework-api}/aot/hint/RuntimeHints.html[`RuntimeHints`] API collects the need for reflection, resource loading, serialization, and JDK proxies at runtime.
|
||||
@@ -415,7 +474,7 @@ include-code::./SpellCheckService[]
|
||||
If at all possible, `@ImportRuntimeHints` should be used as close as possible to the component that requires the hints.
|
||||
This way, if the component is not contributed to the `BeanFactory`, the hints won't be contributed either.
|
||||
|
||||
It is also possible to register an implementation statically by adding an entry in `META-INF/spring/aot.factories` with a key equal to the fully qualified name of the `RuntimeHintsRegistrar` interface.
|
||||
It is also possible to register an implementation statically by adding an entry in `META-INF/spring/aot.factories` with a key equal to the fully-qualified name of the `RuntimeHintsRegistrar` interface.
|
||||
|
||||
|
||||
[[aot.hints.reflective]]
|
||||
@@ -424,7 +483,7 @@ It is also possible to register an implementation statically by adding an entry
|
||||
{spring-framework-api}/aot/hint/annotation/Reflective.html[`@Reflective`] provides an idiomatic way to flag the need for reflection on an annotated element.
|
||||
For instance, `@EventListener` is meta-annotated with `@Reflective` since the underlying implementation invokes the annotated method using reflection.
|
||||
|
||||
By default, only Spring beans are considered and an invocation hint is registered for the annotated element.
|
||||
By default, only Spring beans are considered, and an invocation hint is registered for the annotated element.
|
||||
This can be tuned by specifying a custom `ReflectiveProcessor` implementation via the
|
||||
`@Reflective` annotation.
|
||||
|
||||
|
||||
+2
@@ -155,6 +155,8 @@ If there is no other resolution indicator (such as a qualifier or a primary mark
|
||||
for a non-unique dependency situation, Spring matches the injection point name
|
||||
(that is, the field name or parameter name) against the target bean names and chooses the
|
||||
same-named candidate, if any.
|
||||
|
||||
Since version 6.1, this requires the `-parameters` Java compiler flag to be present.
|
||||
====
|
||||
|
||||
That said, if you intend to express annotation-driven injection by name, do not
|
||||
|
||||
@@ -141,7 +141,7 @@ Kotlin::
|
||||
====
|
||||
After you learn about Spring's IoC container, you may want to know more about Spring's
|
||||
`Resource` abstraction (as described in
|
||||
xref:web/webflux-webclient/client-builder.adoc#webflux-client-builder-reactor-resources[Resources])
|
||||
xref:core/resources.adoc[Resources])
|
||||
which provides a convenient mechanism for reading an InputStream from locations defined
|
||||
in a URI syntax. In particular, `Resource` paths are used to construct applications contexts,
|
||||
as described in xref:core/resources.adoc#resources-app-ctx[Application Contexts and Resource Paths].
|
||||
|
||||
@@ -592,6 +592,40 @@ Kotlin::
|
||||
|
||||
|
||||
|
||||
[[beans-factory-thread-safety]]
|
||||
=== Thread Safety and Visibility
|
||||
|
||||
The Spring core container publishes created singleton instances in a thread-safe manner,
|
||||
guarding access through a singleton lock and guaranteeing visibility in other threads.
|
||||
|
||||
As a consequence, application-provided bean classes do not have to be concerned about the
|
||||
visibility of their initialization state. Regular configuration fields do not have to be
|
||||
marked as `volatile` as long as they are only mutated during the initialization phase,
|
||||
providing visibility guarantees similar to `final` even for setter-based configuration
|
||||
state that is mutable during that initial phase. If such fields get changed after the
|
||||
bean creation phase and its subsequent initial publication, they need to be declared as
|
||||
`volatile` or guarded by a common lock whenever accessed.
|
||||
|
||||
Note that concurrent access to such configuration state in singleton bean instances,
|
||||
e.g. for controller instances or repository instances, is perfectly thread-safe after
|
||||
such safe initial publication from the container side. This includes common singleton
|
||||
`FactoryBean` instances which are processed within the general singleton lock as well.
|
||||
|
||||
For destruction callbacks, the configuration state remains thread-safe but any runtime
|
||||
state accumulated between initialization and destruction should be kept in thread-safe
|
||||
structures (or in `volatile` fields for simple cases) as per common Java guidelines.
|
||||
|
||||
Deeper `Lifecycle` integration as shown above involves runtime-mutable state such as
|
||||
a `runnable` field which will have to be declared as `volatile`. While the common
|
||||
lifecycle callbacks follow a certain order, e.g. a start callback is guaranteed to
|
||||
only happen after full initialization and a stop callback only after an initial start,
|
||||
there is a special case with the common stop before destroy arrangement: It is strongly
|
||||
recommended that the internal state in any such bean also allows for an immediate
|
||||
destroy callback without a preceding stop since this may happen during an extraordinary
|
||||
shutdown after a cancelled bootstrap or in case of a stop timeout caused by another bean.
|
||||
|
||||
|
||||
|
||||
[[beans-factory-aware]]
|
||||
== `ApplicationContextAware` and `BeanNameAware`
|
||||
|
||||
|
||||
@@ -324,7 +324,6 @@ Kotlin::
|
||||
|
||||
|
||||
|
||||
|
||||
[[beans-factory-scopes-application]]
|
||||
=== Application Scope
|
||||
|
||||
@@ -374,7 +373,6 @@ Kotlin::
|
||||
|
||||
|
||||
|
||||
|
||||
[[beans-factory-scopes-websocket]]
|
||||
=== WebSocket Scope
|
||||
|
||||
@@ -384,7 +382,6 @@ xref:web/websocket/stomp/scope.adoc[WebSocket scope] for more details.
|
||||
|
||||
|
||||
|
||||
|
||||
[[beans-factory-scopes-other-injection]]
|
||||
=== Scoped Beans as Dependencies
|
||||
|
||||
@@ -544,6 +541,19 @@ see xref:core/aop/proxying.adoc[Proxying Mechanisms].
|
||||
|
||||
|
||||
|
||||
[[beans-factory-scopes-injection]]
|
||||
=== Injecting Request/Session References Directly
|
||||
|
||||
As an alternative to factory scopes, a Spring `WebApplicationContext` also supports
|
||||
the injection of `HttpServletRequest`, `HttpServletResponse`, `HttpSession`,
|
||||
`WebRequest` and (if JSF is present) `FacesContext` and `ExternalContext` into
|
||||
Spring-managed beans, simply through type-based autowiring next to regular injection
|
||||
points for other beans. Spring generally injects proxies for such request and session
|
||||
objects which has the advantage of working in singleton beans and serializable beans
|
||||
as well, similar to scoped proxies for factory-scoped beans.
|
||||
|
||||
|
||||
|
||||
[[beans-factory-scopes-custom]]
|
||||
== Custom Scopes
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
[[expressions]]
|
||||
= Spring Expression Language (SpEL)
|
||||
|
||||
The Spring Expression Language ("`SpEL`" for short) is a powerful expression language that
|
||||
The Spring Expression Language ("SpEL" for short) is a powerful expression language that
|
||||
supports querying and manipulating an object graph at runtime. The language syntax is
|
||||
similar to Unified EL but offers additional features, most notably method invocation and
|
||||
basic string templating functionality.
|
||||
similar to the https://jakarta.ee/specifications/expression-language/[Jakarta Expression
|
||||
Language] but offers additional features, most notably method invocation and basic string
|
||||
templating functionality.
|
||||
|
||||
While there are several other Java expression languages available -- OGNL, MVEL, and JBoss
|
||||
EL, to name a few -- the Spring Expression Language was created to provide the Spring
|
||||
@@ -33,26 +34,24 @@ populate them are listed at the end of the chapter.
|
||||
The expression language supports the following functionality:
|
||||
|
||||
* Literal expressions
|
||||
* Boolean and relational operators
|
||||
* Regular expressions
|
||||
* Class expressions
|
||||
* Accessing properties, arrays, lists, and maps
|
||||
* Method invocation
|
||||
* Assignment
|
||||
* Calling constructors
|
||||
* Bean references
|
||||
* Array construction
|
||||
* Inline lists
|
||||
* Inline maps
|
||||
* Ternary operator
|
||||
* Array construction
|
||||
* Relational operators
|
||||
* Regular expressions
|
||||
* Logical operators
|
||||
* String operators
|
||||
* Mathematical operators
|
||||
* Assignment
|
||||
* Type expressions
|
||||
* Method invocation
|
||||
* Constructor invocation
|
||||
* Variables
|
||||
* User-defined functions added to the context
|
||||
* reflective invocation of `Method`
|
||||
* various cases of `MethodHandle`
|
||||
* User-defined functions
|
||||
* Bean references
|
||||
* Ternary, Elvis, and safe-navigation operators
|
||||
* Collection projection
|
||||
* Collection selection
|
||||
* Templated expressions
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
[[expressions-evaluation]]
|
||||
= Evaluation
|
||||
|
||||
This section introduces the simple use of SpEL interfaces and its expression language.
|
||||
The complete language reference can be found in
|
||||
This section introduces programmatic use of SpEL's interfaces and its expression language.
|
||||
The complete language reference can be found in the
|
||||
xref:core/expressions/language-ref.adoc[Language Reference].
|
||||
|
||||
The following code introduces the SpEL API to evaluate the literal string expression,
|
||||
`Hello World`.
|
||||
The following code demonstrates how to use the SpEL API to evaluate the literal string
|
||||
expression, `Hello World`.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -18,7 +18,7 @@ Java::
|
||||
Expression exp = parser.parseExpression("'Hello World'"); // <1>
|
||||
String message = (String) exp.getValue();
|
||||
----
|
||||
<1> The value of the message variable is `'Hello World'`.
|
||||
<1> The value of the message variable is `"Hello World"`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
@@ -28,24 +28,24 @@ Kotlin::
|
||||
val exp = parser.parseExpression("'Hello World'") // <1>
|
||||
val message = exp.value as String
|
||||
----
|
||||
<1> The value of the message variable is `'Hello World'`.
|
||||
<1> The value of the message variable is `"Hello World"`.
|
||||
======
|
||||
|
||||
|
||||
The SpEL classes and interfaces you are most likely to use are located in the
|
||||
`org.springframework.expression` package and its sub-packages, such as `spel.support`.
|
||||
|
||||
The `ExpressionParser` interface is responsible for parsing an expression string. In
|
||||
the preceding example, the expression string is a string literal denoted by the surrounding single
|
||||
quotation marks. The `Expression` interface is responsible for evaluating the previously defined
|
||||
expression string. Two exceptions that can be thrown, `ParseException` and
|
||||
`EvaluationException`, when calling `parser.parseExpression` and `exp.getValue`,
|
||||
respectively.
|
||||
The `ExpressionParser` interface is responsible for parsing an expression string. In the
|
||||
preceding example, the expression string is a string literal denoted by the surrounding
|
||||
single quotation marks. The `Expression` interface is responsible for evaluating the
|
||||
defined expression string. The two types of exceptions that can be thrown when calling
|
||||
`parser.parseExpression(...)` and `exp.getValue(...)` are `ParseException` and
|
||||
`EvaluationException`, respectively.
|
||||
|
||||
SpEL supports a wide range of features, such as calling methods, accessing properties,
|
||||
SpEL supports a wide range of features such as calling methods, accessing properties,
|
||||
and calling constructors.
|
||||
|
||||
In the following example of method invocation, we call the `concat` method on the string literal:
|
||||
In the following method invocation example, we call the `concat` method on the string
|
||||
literal, `Hello World`.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -57,7 +57,7 @@ Java::
|
||||
Expression exp = parser.parseExpression("'Hello World'.concat('!')"); // <1>
|
||||
String message = (String) exp.getValue();
|
||||
----
|
||||
<1> The value of `message` is now 'Hello World!'.
|
||||
<1> The value of `message` is now `"Hello World!"`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
@@ -67,10 +67,11 @@ Kotlin::
|
||||
val exp = parser.parseExpression("'Hello World'.concat('!')") // <1>
|
||||
val message = exp.value as String
|
||||
----
|
||||
<1> The value of `message` is now 'Hello World!'.
|
||||
<1> The value of `message` is now `"Hello World!"`.
|
||||
======
|
||||
|
||||
The following example of calling a JavaBean property calls the `String` property `Bytes`:
|
||||
The following example demonstrates how to access the `Bytes` JavaBean property of the
|
||||
string literal, `Hello World`.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -100,10 +101,10 @@ Kotlin::
|
||||
======
|
||||
|
||||
SpEL also supports nested properties by using the standard dot notation (such as
|
||||
`prop1.prop2.prop3`) and also the corresponding setting of property values.
|
||||
`prop1.prop2.prop3`) as well as the corresponding setting of property values.
|
||||
Public fields may also be accessed.
|
||||
|
||||
The following example shows how to use dot notation to get the length of a literal:
|
||||
The following example shows how to use dot notation to get the length of a string literal.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -133,7 +134,7 @@ Kotlin::
|
||||
======
|
||||
|
||||
The String's constructor can be called instead of using a string literal, as the following
|
||||
example shows:
|
||||
example shows.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -145,7 +146,7 @@ Java::
|
||||
Expression exp = parser.parseExpression("new String('hello world').toUpperCase()"); // <1>
|
||||
String message = exp.getValue(String.class);
|
||||
----
|
||||
<1> Construct a new `String` from the literal and make it be upper case.
|
||||
<1> Construct a new `String` from the literal and convert it to upper case.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
@@ -155,10 +156,9 @@ Kotlin::
|
||||
val exp = parser.parseExpression("new String('hello world').toUpperCase()") // <1>
|
||||
val message = exp.getValue(String::class.java)
|
||||
----
|
||||
<1> Construct a new `String` from the literal and make it be upper case.
|
||||
<1> Construct a new `String` from the literal and convert it to upper case.
|
||||
======
|
||||
|
||||
|
||||
Note the use of the generic method: `public <T> T getValue(Class<T> desiredResultType)`.
|
||||
Using this method removes the need to cast the value of the expression to the desired
|
||||
result type. An `EvaluationException` is thrown if the value cannot be cast to the
|
||||
@@ -166,8 +166,8 @@ type `T` or converted by using the registered type converter.
|
||||
|
||||
The more common usage of SpEL is to provide an expression string that is evaluated
|
||||
against a specific object instance (called the root object). The following example shows
|
||||
how to retrieve the `name` property from an instance of the `Inventor` class or
|
||||
create a boolean condition:
|
||||
how to retrieve the `name` property from an instance of the `Inventor` class and how to
|
||||
reference the `name` property in a boolean expression.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -240,7 +240,7 @@ It excludes Java type references, constructors, and bean references. It also req
|
||||
you to explicitly choose the level of support for properties and methods in expressions.
|
||||
By default, the `create()` static factory method enables only read access to properties.
|
||||
You can also obtain a builder to configure the exact level of support needed, targeting
|
||||
one or some combination of the following:
|
||||
one or some combination of the following.
|
||||
|
||||
* Custom `PropertyAccessor` only (no reflection)
|
||||
* Data binding properties for read-only access
|
||||
@@ -252,16 +252,15 @@ one or some combination of the following:
|
||||
|
||||
By default, SpEL uses the conversion service available in Spring core
|
||||
(`org.springframework.core.convert.ConversionService`). This conversion service comes
|
||||
with many built-in converters for common conversions but is also fully extensible so that
|
||||
you can add custom conversions between types. Additionally, it is
|
||||
generics-aware. This means that, when you work with generic types in
|
||||
expressions, SpEL attempts conversions to maintain type correctness for any objects
|
||||
it encounters.
|
||||
with many built-in converters for common conversions, but is also fully extensible so
|
||||
that you can add custom conversions between types. Additionally, it is generics-aware.
|
||||
This means that, when you work with generic types in expressions, SpEL attempts
|
||||
conversions to maintain type correctness for any objects it encounters.
|
||||
|
||||
What does this mean in practice? Suppose assignment, using `setValue()`, is being used
|
||||
to set a `List` property. The type of the property is actually `List<Boolean>`. SpEL
|
||||
recognizes that the elements of the list need to be converted to `Boolean` before
|
||||
being placed in it. The following example shows how to do so:
|
||||
being placed in it. The following example shows how to do so.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -325,7 +324,7 @@ constructor before setting the specified value. If the element type does not hav
|
||||
default constructor, `null` will be added to the array or list. If there is no built-in
|
||||
or custom converter that knows how to set the value, `null` will remain in the array or
|
||||
list at the specified index. The following example demonstrates how to automatically grow
|
||||
the list:
|
||||
the list.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -380,16 +379,25 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
By default, a SpEL expression cannot contain more than 10,000 characters; however, the
|
||||
`maxExpressionLength` is configurable. If you create a `SpelExpressionParser`
|
||||
programmatically, you can specify a custom `maxExpressionLength` when creating the
|
||||
`SpelParserConfiguration` that you provide to the `SpelExpressionParser`. If you wish to
|
||||
set the `maxExpressionLength` used for parsing SpEL expressions within an
|
||||
`ApplicationContext` -- for example, in XML bean definitions, `@Value`, etc. -- you can
|
||||
set a JVM system property or Spring property named `spring.context.expression.maxLength`
|
||||
to the maximum expression length needed by your application (see
|
||||
xref:appendix.adoc#appendix-spring-properties[Supported Spring Properties]).
|
||||
|
||||
|
||||
[[expressions-spel-compilation]]
|
||||
== SpEL Compilation
|
||||
|
||||
Spring Framework 4.1 includes a basic expression compiler. Expressions are usually
|
||||
interpreted, which provides a lot of dynamic flexibility during evaluation but
|
||||
does not provide optimum performance. For occasional expression usage,
|
||||
this is fine, but, when used by other components such as Spring Integration,
|
||||
performance can be very important, and there is no real need for the dynamism.
|
||||
Spring provides a basic compiler for SpEL expressions. Expressions are usually
|
||||
interpreted, which provides a lot of dynamic flexibility during evaluation but does not
|
||||
provide optimum performance. For occasional expression usage, this is fine, but, when
|
||||
used by other components such as Spring Integration, performance can be very important,
|
||||
and there is no real need for the dynamism.
|
||||
|
||||
The SpEL compiler is intended to address this need. During evaluation, the compiler
|
||||
generates a Java class that embodies the expression behavior at runtime and uses that
|
||||
@@ -402,16 +410,17 @@ information can cause trouble later if the types of the various expression eleme
|
||||
change over time. For this reason, compilation is best suited to expressions whose
|
||||
type information is not going to change on repeated evaluations.
|
||||
|
||||
Consider the following basic expression:
|
||||
Consider the following basic expression.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
someArray[0].someProperty.someOtherProperty < 0.1
|
||||
someArray[0].someProperty.someOtherProperty < 0.1
|
||||
----
|
||||
|
||||
Because the preceding expression involves array access, some property de-referencing,
|
||||
and numeric operations, the performance gain can be very noticeable. In an example
|
||||
micro benchmark run of 50000 iterations, it took 75ms to evaluate by using the
|
||||
interpreter and only 3ms using the compiled version of the expression.
|
||||
Because the preceding expression involves array access, some property de-referencing, and
|
||||
numeric operations, the performance gain can be very noticeable. In an example micro
|
||||
benchmark run of 50,000 iterations, it took 75ms to evaluate by using the interpreter and
|
||||
only 3ms using the compiled version of the expression.
|
||||
|
||||
|
||||
[[expressions-compiler-configuration]]
|
||||
@@ -419,33 +428,34 @@ interpreter and only 3ms using the compiled version of the expression.
|
||||
|
||||
The compiler is not turned on by default, but you can turn it on in either of two
|
||||
different ways. You can turn it on by using the parser configuration process
|
||||
(xref:core/expressions/evaluation.adoc#expressions-parser-configuration[discussed earlier]) or by using a Spring property
|
||||
when SpEL usage is embedded inside another component. This section discusses both of
|
||||
these options.
|
||||
(xref:core/expressions/evaluation.adoc#expressions-parser-configuration[discussed
|
||||
earlier]) or by using a Spring property when SpEL usage is embedded inside another
|
||||
component. This section discusses both of these options.
|
||||
|
||||
The compiler can operate in one of three modes, which are captured in the
|
||||
`org.springframework.expression.spel.SpelCompilerMode` enum. The modes are as follows:
|
||||
`org.springframework.expression.spel.SpelCompilerMode` enum. The modes are as follows.
|
||||
|
||||
* `OFF` (default): The compiler is switched off.
|
||||
* `IMMEDIATE`: In immediate mode, the expressions are compiled as soon as possible. This
|
||||
is typically after the first interpreted evaluation. If the compiled expression fails
|
||||
(typically due to a type changing, as described earlier), the caller of the expression
|
||||
evaluation receives an exception.
|
||||
* `MIXED`: In mixed mode, the expressions silently switch between interpreted and compiled
|
||||
mode over time. After some number of interpreted runs, they switch to compiled
|
||||
form and, if something goes wrong with the compiled form (such as a type changing, as
|
||||
described earlier), the expression automatically switches back to interpreted form
|
||||
again. Sometime later, it may generate another compiled form and switch to it. Basically,
|
||||
the exception that the user gets in `IMMEDIATE` mode is instead handled internally.
|
||||
is typically after the first interpreted evaluation. If the compiled expression fails
|
||||
(typically due to a type changing, as described earlier), the caller of the expression
|
||||
evaluation receives an exception.
|
||||
* `MIXED`: In mixed mode, the expressions silently switch between interpreted and
|
||||
compiled mode over time. After some number of interpreted runs, they switch to compiled
|
||||
form and, if something goes wrong with the compiled form (such as a type changing, as
|
||||
described earlier), the expression automatically switches back to interpreted form
|
||||
again. Sometime later, it may generate another compiled form and switch to it.
|
||||
Basically, the exception that the user gets in `IMMEDIATE` mode is instead handled
|
||||
internally.
|
||||
|
||||
`IMMEDIATE` mode exists because `MIXED` mode could cause issues for expressions that
|
||||
have side effects. If a compiled expression blows up after partially succeeding, it
|
||||
may have already done something that has affected the state of the system. If this
|
||||
has happened, the caller may not want it to silently re-run in interpreted mode,
|
||||
since part of the expression may be running twice.
|
||||
since part of the expression may be run twice.
|
||||
|
||||
After selecting a mode, use the `SpelParserConfiguration` to configure the parser. The
|
||||
following example shows how to do so:
|
||||
following example shows how to do so.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -482,15 +492,16 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
When you specify the compiler mode, you can also specify a classloader (passing null is allowed).
|
||||
Compiled expressions are defined in a child classloader created under any that is supplied.
|
||||
It is important to ensure that, if a classloader is specified, it can see all the types involved in
|
||||
the expression evaluation process. If you do not specify a classloader, a default classloader is used
|
||||
(typically the context classloader for the thread that is running during expression evaluation).
|
||||
When you specify the compiler mode, you can also specify a `ClassLoader` (passing `null`
|
||||
is allowed). Compiled expressions are defined in a child `ClassLoader` created under any
|
||||
that is supplied. It is important to ensure that, if a `ClassLoader` is specified, it can
|
||||
see all the types involved in the expression evaluation process. If you do not specify a
|
||||
`ClassLoader`, a default `ClassLoader` is used (typically the context `ClassLoader` for
|
||||
the thread that is running during expression evaluation).
|
||||
|
||||
The second way to configure the compiler is for use when SpEL is embedded inside some
|
||||
other component and it may not be possible to configure it through a configuration
|
||||
object. In these cases, it is possible to set the `spring.expression.compiler.mode`
|
||||
object. In such cases, it is possible to set the `spring.expression.compiler.mode`
|
||||
property via a JVM system property (or via the
|
||||
xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism) to one of the
|
||||
`SpelCompilerMode` enum values (`off`, `immediate`, or `mixed`).
|
||||
@@ -499,18 +510,16 @@ xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism) to
|
||||
[[expressions-compiler-limitations]]
|
||||
=== Compiler Limitations
|
||||
|
||||
Since Spring Framework 4.1, the basic compilation framework is in place. However, the framework
|
||||
does not yet support compiling every kind of expression. The initial focus has been on the
|
||||
common expressions that are likely to be used in performance-critical contexts. The following
|
||||
kinds of expression cannot be compiled at the moment:
|
||||
Spring does not support compiling every kind of expression. The primary focus is on
|
||||
common expressions that are likely to be used in performance-critical contexts. The
|
||||
following kinds of expressions cannot be compiled.
|
||||
|
||||
* Expressions involving assignment
|
||||
* Expressions relying on the conversion service
|
||||
* Expressions using custom resolvers or accessors
|
||||
* Expressions using overloaded operators
|
||||
* Expressions using array construction syntax
|
||||
* Expressions using selection or projection
|
||||
|
||||
More types of expressions will be compilable in the future.
|
||||
|
||||
|
||||
|
||||
Compilation of additional kinds of expressions may be supported in the future.
|
||||
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
|
||||
This section lists the classes used in the examples throughout this chapter.
|
||||
|
||||
== `Inventor`
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Inventor.Java::
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
----
|
||||
@@ -80,7 +82,7 @@ Inventor.Java::
|
||||
}
|
||||
----
|
||||
|
||||
Inventor.kt::
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
----
|
||||
@@ -95,9 +97,11 @@ Inventor.kt::
|
||||
----
|
||||
======
|
||||
|
||||
== `PlaceOfBirth`
|
||||
|
||||
[tabs]
|
||||
======
|
||||
PlaceOfBirth.java::
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
----
|
||||
@@ -135,7 +139,7 @@ PlaceOfBirth.java::
|
||||
}
|
||||
----
|
||||
|
||||
PlaceOfBirth.kt::
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
----
|
||||
@@ -145,9 +149,11 @@ PlaceOfBirth.kt::
|
||||
----
|
||||
======
|
||||
|
||||
== `Society`
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Society.java::
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
----
|
||||
@@ -192,7 +198,7 @@ Society.java::
|
||||
}
|
||||
----
|
||||
|
||||
Society.kt::
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
----
|
||||
|
||||
+12
-4
@@ -13,7 +13,7 @@ Java::
|
||||
int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context);
|
||||
|
||||
// Array with initializer
|
||||
int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context);
|
||||
int[] numbers2 = (int[]) parser.parseExpression("new int[] {1, 2, 3}").getValue(context);
|
||||
|
||||
// Multi dimensional array
|
||||
int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);
|
||||
@@ -26,14 +26,22 @@ Kotlin::
|
||||
val numbers1 = parser.parseExpression("new int[4]").getValue(context) as IntArray
|
||||
|
||||
// Array with initializer
|
||||
val numbers2 = parser.parseExpression("new int[]{1,2,3}").getValue(context) as IntArray
|
||||
val numbers2 = parser.parseExpression("new int[] {1, 2, 3}").getValue(context) as IntArray
|
||||
|
||||
// Multi dimensional array
|
||||
val numbers3 = parser.parseExpression("new int[4][5]").getValue(context) as Array<IntArray>
|
||||
----
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
You cannot currently supply an initializer when you construct a multi-dimensional array.
|
||||
====
|
||||
|
||||
|
||||
|
||||
[CAUTION]
|
||||
====
|
||||
Any expression that constructs an array – for example, via `new int[4]` or
|
||||
`new int[] {1, 2, 3}` – cannot be compiled. See
|
||||
xref:core/expressions/evaluation.adoc#expressions-compiler-limitations[Compiler Limitations]
|
||||
for details.
|
||||
====
|
||||
|
||||
+14
-5
@@ -4,7 +4,7 @@
|
||||
Projection lets a collection drive the evaluation of a sub-expression, and the result is
|
||||
a new collection. The syntax for projection is `.![projectionExpression]`. For example,
|
||||
suppose we have a list of inventors but want the list of cities where they were born.
|
||||
Effectively, we want to evaluate 'placeOfBirth.city' for every entry in the inventor
|
||||
Effectively, we want to evaluate `placeOfBirth.city` for every entry in the inventor
|
||||
list. The following example uses projection to do so:
|
||||
|
||||
[tabs]
|
||||
@@ -13,16 +13,18 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// returns ['Smiljan', 'Idvor' ]
|
||||
List placesOfBirth = (List)parser.parseExpression("members.![placeOfBirth.city]");
|
||||
// evaluates to ["Smiljan", "Idvor"]
|
||||
List placesOfBirth = parser.parseExpression("members.![placeOfBirth.city]")
|
||||
.getValue(societyContext, List.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// returns ['Smiljan', 'Idvor' ]
|
||||
val placesOfBirth = parser.parseExpression("members.![placeOfBirth.city]") as List<*>
|
||||
// evaluates to ["Smiljan", "Idvor"]
|
||||
val placesOfBirth = parser.parseExpression("members.![placeOfBirth.city]")
|
||||
.getValue(societyContext) as List<*>
|
||||
----
|
||||
======
|
||||
|
||||
@@ -32,5 +34,12 @@ evaluated against each entry in the map (represented as a Java `Map.Entry`). The
|
||||
of a projection across a map is a list that consists of the evaluation of the projection
|
||||
expression against each map entry.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The Spring Expression Language also supports safe navigation for collection projection.
|
||||
|
||||
See
|
||||
xref:core/expressions/language-ref/operator-safe-navigation.adoc#expressions-operator-safe-navigation-selection-and-projection[Safe Collection Selection and Projection]
|
||||
for details.
|
||||
====
|
||||
|
||||
|
||||
+19
-11
@@ -28,13 +28,14 @@ Kotlin::
|
||||
======
|
||||
|
||||
Selection is supported for arrays and anything that implements `java.lang.Iterable` or
|
||||
`java.util.Map`. For a list or array, the selection criteria is evaluated against each
|
||||
individual element. Against a map, the selection criteria is evaluated against each map
|
||||
entry (objects of the Java type `Map.Entry`). Each map entry has its `key` and `value`
|
||||
accessible as properties for use in the selection.
|
||||
`java.util.Map`. For an array or `Iterable`, the selection expression is evaluated
|
||||
against each individual element. Against a map, the selection expression is evaluated
|
||||
against each map entry (objects of the Java type `Map.Entry`). Each map entry has its
|
||||
`key` and `value` accessible as properties for use in the selection.
|
||||
|
||||
The following expression returns a new map that consists of those elements of the
|
||||
original map where the entry's value is less than 27:
|
||||
Given a `Map` stored in a variable named `#map`, the following expression returns a new
|
||||
map that consists of those elements of the original map where the entry's value is less
|
||||
than 27:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -42,21 +43,28 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
Map newMap = parser.parseExpression("map.?[value<27]").getValue();
|
||||
Map newMap = parser.parseExpression("#map.?[value < 27]").getValue(Map.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val newMap = parser.parseExpression("map.?[value<27]").getValue()
|
||||
val newMap = parser.parseExpression("#map.?[value < 27]").getValue() as Map
|
||||
----
|
||||
======
|
||||
|
||||
In addition to returning all the selected elements, you can retrieve only the first or
|
||||
the last element. To obtain the first element matching the selection, the syntax is
|
||||
`.^[selectionExpression]`. To obtain the last matching selection, the syntax is
|
||||
`.$[selectionExpression]`.
|
||||
the last element. To obtain the first element matching the selection expression, the
|
||||
syntax is `.^[selectionExpression]`. To obtain the last element matching the selection
|
||||
expression, the syntax is `.$[selectionExpression]`.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The Spring Expression Language also supports safe navigation for collection selection.
|
||||
|
||||
See
|
||||
xref:core/expressions/language-ref/operator-safe-navigation.adoc#expressions-operator-safe-navigation-selection-and-projection[Safe Collection Selection and Projection]
|
||||
for details.
|
||||
====
|
||||
|
||||
|
||||
@@ -1,10 +1,26 @@
|
||||
[[expressions-ref-functions]]
|
||||
= Functions
|
||||
|
||||
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 to be invoked via reflection
|
||||
(i.e. a `Method`):
|
||||
You can extend SpEL by registering user-defined functions that can be called within
|
||||
expressions by using the `#functionName(...)` syntax. Functions can be registered as
|
||||
variables in `EvaluationContext` implementations via the `setVariable()` method.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
`StandardEvaluationContext` also defines `registerFunction(...)` methods that provide a
|
||||
convenient way to register a function as a `java.lang.reflect.Method` or a
|
||||
`java.lang.invoke.MethodHandle`.
|
||||
====
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
Since functions share a common namespace with
|
||||
xref:core/expressions/language-ref/variables.adoc[variables] in the evaluation context,
|
||||
care must be taken to ensure that function names and variable names do not overlap.
|
||||
====
|
||||
|
||||
The following example shows how to register a user-defined function to be invoked via
|
||||
reflection using a `java.lang.reflect.Method`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -40,11 +56,7 @@ Java::
|
||||
public abstract class StringUtils {
|
||||
|
||||
public static String reverseString(String input) {
|
||||
StringBuilder backwards = new StringBuilder(input.length());
|
||||
for (int i = 0; i < input.length(); i++) {
|
||||
backwards.append(input.charAt(input.length() - 1 - i));
|
||||
}
|
||||
return backwards.toString();
|
||||
return new StringBuilder(input).reverse().toString();
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -54,16 +66,12 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
fun reverseString(input: String): String {
|
||||
val backwards = StringBuilder(input.length)
|
||||
for (i in 0 until input.length) {
|
||||
backwards.append(input[input.length - 1 - i])
|
||||
}
|
||||
return backwards.toString()
|
||||
return StringBuilder(input).reverse().toString()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
You can then register and use the preceding method, as the following example shows:
|
||||
You can register and use the preceding method, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -75,8 +83,9 @@ Java::
|
||||
|
||||
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
|
||||
context.setVariable("reverseString",
|
||||
StringUtils.class.getDeclaredMethod("reverseString", String.class));
|
||||
StringUtils.class.getMethod("reverseString", String.class));
|
||||
|
||||
// evaluates to "olleh"
|
||||
String helloWorldReversed = parser.parseExpression(
|
||||
"#reverseString('hello')").getValue(context, String.class);
|
||||
----
|
||||
@@ -88,16 +97,18 @@ Kotlin::
|
||||
val parser = SpelExpressionParser()
|
||||
|
||||
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
|
||||
context.setVariable("reverseString", ::reverseString::javaMethod)
|
||||
context.setVariable("reverseString", ::reverseString.javaMethod)
|
||||
|
||||
// evaluates to "olleh"
|
||||
val helloWorldReversed = parser.parseExpression(
|
||||
"#reverseString('hello')").getValue(context, String::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
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.
|
||||
A function can also be registered as a `java.lang.invoke.MethodHandle`. This enables
|
||||
potentially more efficient use cases if the `MethodHandle` target and parameters have
|
||||
been fully bound prior to registration; however, 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.
|
||||
@@ -118,9 +129,9 @@ Java::
|
||||
MethodType.methodType(String.class, Object[].class));
|
||||
context.setVariable("message", mh);
|
||||
|
||||
// evaluates to "Simple message: <Hello World>"
|
||||
String message = parser.parseExpression("#message('Simple message: <%s>', 'Hello World', 'ignored')")
|
||||
.getValue(context, String.class);
|
||||
//returns "Simple message: <Hello World>"
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
@@ -134,6 +145,7 @@ Kotlin::
|
||||
MethodType.methodType(String::class.java, Array<Any>::class.java))
|
||||
context.setVariable("message", mh)
|
||||
|
||||
// evaluates to "Simple message: <Hello World>"
|
||||
val message = parser.parseExpression("#message('Simple message: <%s>', 'Hello World', 'ignored')")
|
||||
.getValue(context, String::class.java)
|
||||
----
|
||||
@@ -161,9 +173,9 @@ Java::
|
||||
.bindTo(varargs); //here we have to provide arguments in a single array binding
|
||||
context.setVariable("message", mh);
|
||||
|
||||
// evaluates to "This is a prerecorded message with 3 words: <Oh Hello World!>"
|
||||
String message = parser.parseExpression("#message()")
|
||||
.getValue(context, String.class);
|
||||
//returns "This is a prerecorded message with 3 words: <Oh Hello World!>"
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
@@ -182,6 +194,7 @@ Kotlin::
|
||||
.bindTo(varargs) //here we have to provide arguments in a single array binding
|
||||
context.setVariable("message", mh)
|
||||
|
||||
// evaluates to "This is a prerecorded message with 3 words: <Oh Hello World!>"
|
||||
val message = parser.parseExpression("#message()")
|
||||
.getValue(context, String::class.java)
|
||||
----
|
||||
|
||||
@@ -3,20 +3,46 @@
|
||||
|
||||
SpEL supports the following types of literal expressions.
|
||||
|
||||
- strings
|
||||
- numeric values: integer (`int` or `long`), hexadecimal (`int` or `long`), real (`float`
|
||||
or `double`)
|
||||
- boolean values: `true` or `false`
|
||||
- null
|
||||
String ::
|
||||
Strings can be delimited by single quotation marks (`'`) or double quotation marks
|
||||
(`"`). To include a single quotation mark within a string literal enclosed in single
|
||||
quotation marks, use two adjacent single quotation mark characters. Similarly, to
|
||||
include a double quotation mark within a string literal enclosed in double quotation
|
||||
marks, use two adjacent double quotation mark characters.
|
||||
Number ::
|
||||
Numbers support the use of the negative sign, exponential notation, and decimal points.
|
||||
* Integer: `int` or `long`
|
||||
* Hexadecimal: `int` or `long`
|
||||
* Real: `float` or `double`
|
||||
** By default, real numbers are parsed using `Double.parseDouble()`.
|
||||
Boolean ::
|
||||
`true` or `false`
|
||||
Null ::
|
||||
`null`
|
||||
|
||||
Strings can delimited by single quotation marks (`'`) or double quotation marks (`"`). To
|
||||
include a single quotation mark within a string literal enclosed in single quotation
|
||||
marks, use two adjacent single quotation mark characters. Similarly, to include a double
|
||||
quotation mark within a string literal enclosed in double quotation marks, use two
|
||||
adjacent double quotation mark characters.
|
||||
[NOTE]
|
||||
====
|
||||
Due to the design and implementation of the Spring Expression Language, literal numbers
|
||||
are always stored internally as positive numbers.
|
||||
|
||||
Numbers support the use of the negative sign, exponential notation, and decimal points.
|
||||
By default, real numbers are parsed by using `Double.parseDouble()`.
|
||||
For example, `-2` is stored internally as a positive `2` which is then negated while
|
||||
evaluating the expression (by calculating the value of `0 - 2`).
|
||||
|
||||
This means that it is not possible to represent a negative literal number equal to the
|
||||
minimum value of that type of number in Java. For example, the minimum supported value
|
||||
for an `int` in Java is `Integer.MIN_VALUE` which has a value of `-2147483648`. However,
|
||||
if you include `-2147483648` in a SpEL expression, an exception will be thrown informing
|
||||
you that the value `2147483648` cannot be parsed as an `int` (because it exceeds the
|
||||
value of `Integer.MAX_VALUE` which is `2147483647`).
|
||||
|
||||
If you need to use the minimum value for a particular type of number within a SpEL
|
||||
expression, you can either reference the `MIN_VALUE` constant for the respective wrapper
|
||||
type (such as `Integer.MIN_VALUE`, `Long.MIN_VALUE`, etc.) or calculate the minimum
|
||||
value. For example, to use the minimum integer value:
|
||||
|
||||
- `T(Integer).MIN_VALUE` -- requires a `StandardEvaluationContext`
|
||||
- `-2^31` -- can be used with any type of `EvaluationContext`
|
||||
====
|
||||
|
||||
The following listing shows simple usage of literals. Typically, they are not used in
|
||||
isolation like this but, rather, as part of a more complex expression -- for example,
|
||||
|
||||
+326
-14
@@ -1,12 +1,27 @@
|
||||
[[expressions-operator-safe-navigation]]
|
||||
= Safe Navigation Operator
|
||||
|
||||
The safe navigation operator is used to avoid a `NullPointerException` and comes from
|
||||
the https://www.groovy-lang.org/operators.html#_safe_navigation_operator[Groovy]
|
||||
language. Typically, when you have a reference to an object, you might need to verify that
|
||||
it is not null before accessing methods or properties of the object. To avoid this, the
|
||||
safe navigation operator returns null instead of throwing an exception. The following
|
||||
example shows how to use the safe navigation operator:
|
||||
The safe navigation operator (`?`) is used to avoid a `NullPointerException` and comes
|
||||
from the https://www.groovy-lang.org/operators.html#_safe_navigation_operator[Groovy]
|
||||
language. Typically, when you have a reference to an object, you might need to verify
|
||||
that it is not `null` before accessing methods or properties of the object. To avoid
|
||||
this, the safe navigation operator returns `null` for the particular null-safe operation
|
||||
instead of throwing an exception.
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
When the safe navigation operator evaluates to `null` for a particular null-safe
|
||||
operation within a compound expression, the remainder of the compound expression will
|
||||
still be evaluated.
|
||||
|
||||
See <<expressions-operator-safe-navigation-compound-expressions>> for details.
|
||||
====
|
||||
|
||||
[[expressions-operator-safe-navigation-property-access]]
|
||||
== Safe Property and Method Access
|
||||
|
||||
The following example shows how to use the safe navigation operator for property access
|
||||
(`?.`).
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -20,13 +35,18 @@ Java::
|
||||
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
|
||||
tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan"));
|
||||
|
||||
String city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String.class);
|
||||
System.out.println(city); // Smiljan
|
||||
// evaluates to "Smiljan"
|
||||
String city = parser.parseExpression("placeOfBirth?.city") // <1>
|
||||
.getValue(context, tesla, String.class);
|
||||
|
||||
tesla.setPlaceOfBirth(null);
|
||||
city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String.class);
|
||||
System.out.println(city); // null - does not throw NullPointerException!!!
|
||||
|
||||
// evaluates to null - does not throw NullPointerException
|
||||
city = parser.parseExpression("placeOfBirth?.city") // <2>
|
||||
.getValue(context, tesla, String.class);
|
||||
----
|
||||
<1> Use safe navigation operator on non-null `placeOfBirth` property
|
||||
<2> Use safe navigation operator on null `placeOfBirth` property
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
@@ -38,14 +58,306 @@ Kotlin::
|
||||
val tesla = Inventor("Nikola Tesla", "Serbian")
|
||||
tesla.setPlaceOfBirth(PlaceOfBirth("Smiljan"))
|
||||
|
||||
var city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String::class.java)
|
||||
println(city) // Smiljan
|
||||
// evaluates to "Smiljan"
|
||||
var city = parser.parseExpression("placeOfBirth?.city") // <1>
|
||||
.getValue(context, tesla, String::class.java)
|
||||
|
||||
tesla.setPlaceOfBirth(null)
|
||||
city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String::class.java)
|
||||
println(city) // null - does not throw NullPointerException!!!
|
||||
|
||||
// evaluates to null - does not throw NullPointerException
|
||||
city = parser.parseExpression("placeOfBirth?.city") // <2>
|
||||
.getValue(context, tesla, String::class.java)
|
||||
----
|
||||
<1> Use safe navigation operator on non-null `placeOfBirth` property
|
||||
<2> Use safe navigation operator on null `placeOfBirth` property
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The safe navigation operator also applies to method invocations on an object.
|
||||
|
||||
For example, the expression `#calculator?.max(4, 2)` evaluates to `null` if the
|
||||
`#calculator` variable has not been configured in the context. Otherwise, the
|
||||
`max(int, int)` method will be invoked on the `#calculator`.
|
||||
====
|
||||
|
||||
|
||||
[[expressions-operator-safe-navigation-selection-and-projection]]
|
||||
== Safe Collection Selection and Projection
|
||||
|
||||
The Spring Expression Language supports safe navigation for
|
||||
xref:core/expressions/language-ref/collection-selection.adoc[collection selection] and
|
||||
xref:core/expressions/language-ref/collection-projection.adoc[collection projection] via
|
||||
the following operators.
|
||||
|
||||
* null-safe selection: `?.?`
|
||||
* null-safe select first: `?.^`
|
||||
* null-safe select last: `?.$`
|
||||
* null-safe projection: `?.!`
|
||||
|
||||
The following example shows how to use the safe navigation operator for collection
|
||||
selection (`?.?`).
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
IEEE society = new IEEE();
|
||||
StandardEvaluationContext context = new StandardEvaluationContext(society);
|
||||
String expression = "members?.?[nationality == 'Serbian']"; // <1>
|
||||
|
||||
// evaluates to [Inventor("Nikola Tesla")]
|
||||
List<Inventor> list = (List<Inventor>) parser.parseExpression(expression)
|
||||
.getValue(context);
|
||||
|
||||
society.members = null;
|
||||
|
||||
// evaluates to null - does not throw a NullPointerException
|
||||
list = (List<Inventor>) parser.parseExpression(expression)
|
||||
.getValue(context);
|
||||
----
|
||||
<1> Use null-safe selection operator on potentially null `members` list
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val society = IEEE()
|
||||
val context = StandardEvaluationContext(society)
|
||||
val expression = "members?.?[nationality == 'Serbian']" // <1>
|
||||
|
||||
// evaluates to [Inventor("Nikola Tesla")]
|
||||
var list = parser.parseExpression(expression)
|
||||
.getValue(context) as List<Inventor>
|
||||
|
||||
society.members = null
|
||||
|
||||
// evaluates to null - does not throw a NullPointerException
|
||||
list = parser.parseExpression(expression)
|
||||
.getValue(context) as List<Inventor>
|
||||
----
|
||||
<1> Use null-safe selection operator on potentially null `members` list
|
||||
======
|
||||
|
||||
The following example shows how to use the "null-safe select first" operator for
|
||||
collections (`?.^`).
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
IEEE society = new IEEE();
|
||||
StandardEvaluationContext context = new StandardEvaluationContext(society);
|
||||
String expression =
|
||||
"members?.^[nationality == 'Serbian' || nationality == 'Idvor']"; // <1>
|
||||
|
||||
// evaluates to Inventor("Nikola Tesla")
|
||||
Inventor inventor = parser.parseExpression(expression)
|
||||
.getValue(context, Inventor.class);
|
||||
|
||||
society.members = null;
|
||||
|
||||
// evaluates to null - does not throw a NullPointerException
|
||||
inventor = parser.parseExpression(expression)
|
||||
.getValue(context, Inventor.class);
|
||||
----
|
||||
<1> Use "null-safe select first" operator on potentially null `members` list
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val society = IEEE()
|
||||
val context = StandardEvaluationContext(society)
|
||||
val expression =
|
||||
"members?.^[nationality == 'Serbian' || nationality == 'Idvor']" // <1>
|
||||
|
||||
// evaluates to Inventor("Nikola Tesla")
|
||||
var inventor = parser.parseExpression(expression)
|
||||
.getValue(context, Inventor::class.java)
|
||||
|
||||
society.members = null
|
||||
|
||||
// evaluates to null - does not throw a NullPointerException
|
||||
inventor = parser.parseExpression(expression)
|
||||
.getValue(context, Inventor::class.java)
|
||||
----
|
||||
<1> Use "null-safe select first" operator on potentially null `members` list
|
||||
======
|
||||
|
||||
|
||||
The following example shows how to use the "null-safe select last" operator for
|
||||
collections (`?.$`).
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
IEEE society = new IEEE();
|
||||
StandardEvaluationContext context = new StandardEvaluationContext(society);
|
||||
String expression =
|
||||
"members?.$[nationality == 'Serbian' || nationality == 'Idvor']"; // <1>
|
||||
|
||||
// evaluates to Inventor("Pupin")
|
||||
Inventor inventor = parser.parseExpression(expression)
|
||||
.getValue(context, Inventor.class);
|
||||
|
||||
society.members = null;
|
||||
|
||||
// evaluates to null - does not throw a NullPointerException
|
||||
inventor = parser.parseExpression(expression)
|
||||
.getValue(context, Inventor.class);
|
||||
----
|
||||
<1> Use "null-safe select last" operator on potentially null `members` list
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val society = IEEE()
|
||||
val context = StandardEvaluationContext(society)
|
||||
val expression =
|
||||
"members?.$[nationality == 'Serbian' || nationality == 'Idvor']" // <1>
|
||||
|
||||
// evaluates to Inventor("Pupin")
|
||||
var inventor = parser.parseExpression(expression)
|
||||
.getValue(context, Inventor::class.java)
|
||||
|
||||
society.members = null
|
||||
|
||||
// evaluates to null - does not throw a NullPointerException
|
||||
inventor = parser.parseExpression(expression)
|
||||
.getValue(context, Inventor::class.java)
|
||||
----
|
||||
<1> Use "null-safe select last" operator on potentially null `members` list
|
||||
======
|
||||
|
||||
The following example shows how to use the safe navigation operator for collection
|
||||
projection (`?.!`).
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
IEEE society = new IEEE();
|
||||
StandardEvaluationContext context = new StandardEvaluationContext(society);
|
||||
|
||||
// evaluates to ["Smiljan", "Idvor"]
|
||||
List placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]") // <1>
|
||||
.getValue(context, List.class);
|
||||
|
||||
society.members = null;
|
||||
|
||||
// evaluates to null - does not throw a NullPointerException
|
||||
placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]") // <2>
|
||||
.getValue(context, List.class);
|
||||
----
|
||||
<1> Use null-safe projection operator on non-null `members` list
|
||||
<2> Use null-safe projection operator on null `members` list
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val society = IEEE()
|
||||
val context = StandardEvaluationContext(society)
|
||||
|
||||
// evaluates to ["Smiljan", "Idvor"]
|
||||
var placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]") // <1>
|
||||
.getValue(context, List::class.java)
|
||||
|
||||
society.members = null
|
||||
|
||||
// evaluates to null - does not throw a NullPointerException
|
||||
placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]") // <2>
|
||||
.getValue(context, List::class.java)
|
||||
----
|
||||
<1> Use null-safe projection operator on non-null `members` list
|
||||
<2> Use null-safe projection operator on null `members` list
|
||||
======
|
||||
|
||||
|
||||
[[expressions-operator-safe-navigation-compound-expressions]]
|
||||
== Null-safe Operations in Compound Expressions
|
||||
|
||||
As mentioned at the beginning of this section, when the safe navigation operator
|
||||
evaluates to `null` for a particular null-safe operation within a compound expression,
|
||||
the remainder of the compound expression will still be evaluated. This means that the
|
||||
safe navigation operator must be applied throughout a compound expression in order to
|
||||
avoid any unwanted `NullPointerException`.
|
||||
|
||||
Given the expression `#person?.address.city`, if `#person` is `null` the safe navigation
|
||||
operator (`?.`) ensures that no exception will be thrown when attempting to access the
|
||||
`address` property of `#person`. However, since `#person?.address` evaluates to `null`, a
|
||||
`NullPointerException` will be thrown when attempting to access the `city` property of
|
||||
`null`. To address that, you can apply null-safe navigation throughout the compound
|
||||
expression as in `#person?.address?.city`. That expression will safely evaluate to `null`
|
||||
if either `#person` or `#person?.address` evaluates to `null`.
|
||||
|
||||
The following example demonstrates how to use the "null-safe select first" operator
|
||||
(`?.^`) on a collection combined with null-safe property access (`?.`) within a compound
|
||||
expression. If `members` is `null`, the result of the "null-safe select first" operator
|
||||
(`members?.^[nationality == 'Serbian']`) evaluates to `null`, and the additional use of
|
||||
the safe navigation operator (`?.name`) ensures that the entire compound expression
|
||||
evaluates to `null` instead of throwing an exception.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
IEEE society = new IEEE();
|
||||
StandardEvaluationContext context = new StandardEvaluationContext(society);
|
||||
String expression = "members?.^[nationality == 'Serbian']?.name"; // <1>
|
||||
|
||||
// evaluates to "Nikola Tesla"
|
||||
String name = parser.parseExpression(expression)
|
||||
.getValue(context, String.class);
|
||||
|
||||
society.members = null;
|
||||
|
||||
// evaluates to null - does not throw a NullPointerException
|
||||
name = parser.parseExpression(expression)
|
||||
.getValue(context, String.class);
|
||||
----
|
||||
<1> Use "null-safe select first" and null-safe property access operators within compound expression.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val society = IEEE()
|
||||
val context = StandardEvaluationContext(society)
|
||||
val expression = "members?.^[nationality == 'Serbian']?.name" // <1>
|
||||
|
||||
// evaluates to "Nikola Tesla"
|
||||
String name = parser.parseExpression(expression)
|
||||
.getValue(context, String::class.java)
|
||||
|
||||
society.members = null
|
||||
|
||||
// evaluates to null - does not throw a NullPointerException
|
||||
name = parser.parseExpression(expression)
|
||||
.getValue(context, String::class.java)
|
||||
----
|
||||
<1> Use "null-safe select first" and null-safe property access operators within compound expression.
|
||||
======
|
||||
|
||||
@@ -5,8 +5,11 @@ The Spring Expression Language supports the following kinds of operators:
|
||||
|
||||
* xref:core/expressions/language-ref/operators.adoc#expressions-operators-relational[Relational Operators]
|
||||
* xref:core/expressions/language-ref/operators.adoc#expressions-operators-logical[Logical Operators]
|
||||
* xref:core/expressions/language-ref/operators.adoc#expressions-operators-string[String Operators]
|
||||
* xref:core/expressions/language-ref/operators.adoc#expressions-operators-mathematical[Mathematical Operators]
|
||||
* xref:core/expressions/language-ref/operators.adoc#expressions-assignment[The Assignment Operator]
|
||||
* xref:core/expressions/language-ref/operators.adoc#expressions-operators-overloaded[Overloaded Operators]
|
||||
|
||||
|
||||
|
||||
[[expressions-operators-relational]]
|
||||
@@ -15,7 +18,7 @@ The Spring Expression Language supports the following kinds of operators:
|
||||
The relational operators (equal, not equal, less than, less than or equal, greater than,
|
||||
and greater than or equal) are supported by using standard operator notation.
|
||||
These operators work on `Number` types as well as types implementing `Comparable`.
|
||||
The following listing shows a few examples of operators:
|
||||
The following listing shows a few examples of relational operators:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -65,53 +68,9 @@ If you prefer numeric comparisons instead, avoid number-based `null` comparisons
|
||||
in favor of comparisons against zero (for example, `X > 0` or `X < 0`).
|
||||
====
|
||||
|
||||
In addition to the standard relational operators, SpEL supports the `instanceof` and regular
|
||||
expression-based `matches` operator. The following listing shows examples of both:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// evaluates to false
|
||||
boolean falseValue = parser.parseExpression(
|
||||
"'xyz' instanceof T(Integer)").getValue(Boolean.class);
|
||||
|
||||
// evaluates to true
|
||||
boolean trueValue = parser.parseExpression(
|
||||
"'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
|
||||
|
||||
// evaluates to false
|
||||
boolean falseValue = parser.parseExpression(
|
||||
"'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// evaluates to false
|
||||
val falseValue = parser.parseExpression(
|
||||
"'xyz' instanceof T(Integer)").getValue(Boolean::class.java)
|
||||
|
||||
// evaluates to true
|
||||
val trueValue = parser.parseExpression(
|
||||
"'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java)
|
||||
|
||||
// evaluates to false
|
||||
val falseValue = parser.parseExpression(
|
||||
"'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
CAUTION: Be careful with primitive types, as they are immediately boxed up to their
|
||||
wrapper types. For example, `1 instanceof T(int)` evaluates to `false`, while
|
||||
`1 instanceof T(Integer)` evaluates to `true`, as expected.
|
||||
|
||||
Each symbolic operator can also be specified as a purely alphabetic equivalent. This
|
||||
avoids problems where the symbols used have special meaning for the document type in
|
||||
which the expression is embedded (such as in an XML document). The textual equivalents are:
|
||||
Each symbolic operator can also be specified as a purely textual equivalent. This avoids
|
||||
problems where the symbols used have special meaning for the document type in which the
|
||||
expression is embedded (such as in an XML document). The textual equivalents are:
|
||||
|
||||
* `lt` (`<`)
|
||||
* `gt` (`>`)
|
||||
@@ -119,22 +78,117 @@ which the expression is embedded (such as in an XML document). The textual equiv
|
||||
* `ge` (`>=`)
|
||||
* `eq` (`==`)
|
||||
* `ne` (`!=`)
|
||||
* `div` (`/`)
|
||||
* `mod` (`%`)
|
||||
* `not` (`!`).
|
||||
|
||||
All of the textual operators are case-insensitive.
|
||||
|
||||
In addition to the standard relational operators, SpEL supports the `between`,
|
||||
`instanceof`, and regular expression-based `matches` operators. The following listing
|
||||
shows examples of all three:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
boolean result;
|
||||
|
||||
// evaluates to true
|
||||
result = parser.parseExpression(
|
||||
"1 between {1, 5}").getValue(Boolean.class);
|
||||
|
||||
// evaluates to false
|
||||
result = parser.parseExpression(
|
||||
"1 between {10, 15}").getValue(Boolean.class);
|
||||
|
||||
// evaluates to true
|
||||
result = parser.parseExpression(
|
||||
"'elephant' between {'aardvark', 'zebra'}").getValue(Boolean.class);
|
||||
|
||||
// evaluates to false
|
||||
result = parser.parseExpression(
|
||||
"'elephant' between {'aardvark', 'cobra'}").getValue(Boolean.class);
|
||||
|
||||
// evaluates to true
|
||||
result = parser.parseExpression(
|
||||
"123 instanceof T(Integer)").getValue(Boolean.class);
|
||||
|
||||
// evaluates to false
|
||||
result = parser.parseExpression(
|
||||
"'xyz' instanceof T(Integer)").getValue(Boolean.class);
|
||||
|
||||
// evaluates to true
|
||||
result = parser.parseExpression(
|
||||
"'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
|
||||
|
||||
// evaluates to false
|
||||
result = parser.parseExpression(
|
||||
"'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// evaluates to true
|
||||
var result = parser.parseExpression(
|
||||
"1 between {1, 5}").getValue(Boolean::class.java)
|
||||
|
||||
// evaluates to false
|
||||
result = parser.parseExpression(
|
||||
"1 between {10, 15}").getValue(Boolean::class.java)
|
||||
|
||||
// evaluates to true
|
||||
result = parser.parseExpression(
|
||||
"'elephant' between {'aardvark', 'zebra'}").getValue(Boolean::class.java)
|
||||
|
||||
// evaluates to false
|
||||
result = parser.parseExpression(
|
||||
"'elephant' between {'aardvark', 'cobra'}").getValue(Boolean::class.java)
|
||||
|
||||
// evaluates to true
|
||||
result = parser.parseExpression(
|
||||
"123 instanceof T(Integer)").getValue(Boolean::class.java)
|
||||
|
||||
// evaluates to false
|
||||
result = parser.parseExpression(
|
||||
"'xyz' instanceof T(Integer)").getValue(Boolean::class.java)
|
||||
|
||||
// evaluates to true
|
||||
result = parser.parseExpression(
|
||||
"'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java)
|
||||
|
||||
// evaluates to false
|
||||
result = parser.parseExpression(
|
||||
"'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
[CAUTION]
|
||||
====
|
||||
The syntax for the `between` operator is `<input> between {<range_begin>, <range_end>}`,
|
||||
which is effectively a shortcut for `<input> >= <range_begin> && <input> \<= <range_end>}`.
|
||||
|
||||
Consequently, `1 between {1, 5}` evaluates to `true`, while `1 between {5, 1}` evaluates
|
||||
to `false`.
|
||||
====
|
||||
|
||||
CAUTION: Be careful with primitive types, as they are immediately boxed up to their
|
||||
wrapper types. For example, `1 instanceof T(int)` evaluates to `false`, while
|
||||
`1 instanceof T(Integer)` evaluates to `true`.
|
||||
|
||||
|
||||
[[expressions-operators-logical]]
|
||||
== Logical Operators
|
||||
|
||||
SpEL supports the following logical operators:
|
||||
SpEL supports the following logical (`boolean`) operators:
|
||||
|
||||
* `and` (`&&`)
|
||||
* `or` (`||`)
|
||||
* `not` (`!`)
|
||||
|
||||
All of the textual operators are case-insensitive.
|
||||
|
||||
The following example shows how to use the logical operators:
|
||||
|
||||
[tabs]
|
||||
@@ -167,6 +221,7 @@ Java::
|
||||
boolean falseValue = parser.parseExpression("!true").getValue(Boolean.class);
|
||||
|
||||
// -- AND and NOT --
|
||||
|
||||
String expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')";
|
||||
boolean falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
|
||||
----
|
||||
@@ -199,20 +254,24 @@ Kotlin::
|
||||
val falseValue = parser.parseExpression("!true").getValue(Boolean::class.java)
|
||||
|
||||
// -- AND and NOT --
|
||||
|
||||
val expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')"
|
||||
val falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[expressions-operators-mathematical]]
|
||||
== Mathematical Operators
|
||||
[[expressions-operators-string]]
|
||||
== String Operators
|
||||
|
||||
You can use the addition operator (`+`) on both numbers and strings. You can use the
|
||||
subtraction (`-`), multiplication (`*`), and division (`/`) operators only on numbers.
|
||||
You can also use the modulus (`%`) and exponential power (`^`) operators on numbers.
|
||||
Standard operator precedence is enforced. The following example shows the mathematical
|
||||
operators in use:
|
||||
You can use the following operators on strings.
|
||||
|
||||
* concatenation (`+`)
|
||||
* subtraction (`-`)
|
||||
- for use with a string containing a single character
|
||||
* repeat (`*`)
|
||||
|
||||
The following example shows the `String` operators in use:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -220,67 +279,212 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// Addition
|
||||
int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2
|
||||
// -- Concatenation --
|
||||
|
||||
String testString = parser.parseExpression(
|
||||
"'test' + ' ' + 'string'").getValue(String.class); // 'test string'
|
||||
// evaluates to "hello world"
|
||||
String helloWorld = parser.parseExpression("'hello' + ' ' + 'world'")
|
||||
.getValue(String.class);
|
||||
|
||||
// Subtraction
|
||||
int four = parser.parseExpression("1 - -3").getValue(Integer.class); // 4
|
||||
// -- Character Subtraction --
|
||||
|
||||
double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class); // -9000
|
||||
// evaluates to 'a'
|
||||
char ch = parser.parseExpression("'d' - 3")
|
||||
.getValue(char.class);
|
||||
|
||||
// Multiplication
|
||||
int six = parser.parseExpression("-2 * -3").getValue(Integer.class); // 6
|
||||
// -- Repeat --
|
||||
|
||||
double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); // 24.0
|
||||
|
||||
// Division
|
||||
int minusTwo = parser.parseExpression("6 / -3").getValue(Integer.class); // -2
|
||||
|
||||
double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class); // 1.0
|
||||
|
||||
// Modulus
|
||||
int three = parser.parseExpression("7 % 4").getValue(Integer.class); // 3
|
||||
|
||||
int one = parser.parseExpression("8 / 5 % 2").getValue(Integer.class); // 1
|
||||
|
||||
// Operator precedence
|
||||
int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class); // -21
|
||||
// evaluates to "abcabc"
|
||||
String repeated = parser.parseExpression("'abc' * 2")
|
||||
.getValue(String.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// Addition
|
||||
val two = parser.parseExpression("1 + 1").getValue(Int::class.java) // 2
|
||||
// -- Concatenation --
|
||||
|
||||
val testString = parser.parseExpression(
|
||||
"'test' + ' ' + 'string'").getValue(String::class.java) // 'test string'
|
||||
// evaluates to "hello world"
|
||||
val helloWorld = parser.parseExpression("'hello' + ' ' + 'world'")
|
||||
.getValue(String::class.java)
|
||||
|
||||
// -- Character Subtraction --
|
||||
|
||||
// evaluates to 'a'
|
||||
val ch = parser.parseExpression("'d' - 3")
|
||||
.getValue(Character::class.java);
|
||||
|
||||
// -- Repeat --
|
||||
|
||||
// evaluates to "abcabc"
|
||||
val repeated = parser.parseExpression("'abc' * 2")
|
||||
.getValue(String::class.java);
|
||||
----
|
||||
======
|
||||
|
||||
[[expressions-operators-mathematical]]
|
||||
== Mathematical Operators
|
||||
|
||||
You can use the following operators on numbers, and standard operator precedence is enforced.
|
||||
|
||||
* addition (`+`)
|
||||
* subtraction (`-`)
|
||||
* increment (`{pp}`)
|
||||
* decrement (`--`)
|
||||
* multiplication (`*`)
|
||||
* division (`/`)
|
||||
* modulus (`%`)
|
||||
* exponential power (`^`)
|
||||
|
||||
The division and modulus operators can also be specified as a purely textual equivalent.
|
||||
This avoids problems where the symbols used have special meaning for the document type in
|
||||
which the expression is embedded (such as in an XML document). The textual equivalents
|
||||
are:
|
||||
|
||||
* `div` (`/`)
|
||||
* `mod` (`%`)
|
||||
|
||||
All of the textual operators are case-insensitive.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The increment and decrement operators can be used with either prefix (`{pp}A`, `--A`) or
|
||||
postfix (`A{pp}`, `A--`) notation with variables or properties that can be written to.
|
||||
====
|
||||
|
||||
The following example shows the mathematical operators in use:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
Inventor inventor = new Inventor();
|
||||
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
|
||||
|
||||
// -- Addition --
|
||||
|
||||
int two = parser.parseExpression("1 + 1").getValue(int.class); // 2
|
||||
|
||||
// -- Subtraction --
|
||||
|
||||
int four = parser.parseExpression("1 - -3").getValue(int.class); // 4
|
||||
|
||||
double d = parser.parseExpression("1000.00 - 1e4").getValue(double.class); // -9000
|
||||
|
||||
// -- Increment --
|
||||
|
||||
// The counter property in Inventor has an initial value of 0.
|
||||
|
||||
// evaluates to 2; counter is now 1
|
||||
two = parser.parseExpression("counter++ + 2").getValue(context, inventor, int.class);
|
||||
|
||||
// evaluates to 5; counter is now 2
|
||||
int five = parser.parseExpression("3 + ++counter").getValue(context, inventor, int.class);
|
||||
|
||||
// -- Decrement --
|
||||
|
||||
// The counter property in Inventor has a value of 2.
|
||||
|
||||
// evaluates to 6; counter is now 1
|
||||
int six = parser.parseExpression("counter-- + 4").getValue(context, inventor, int.class);
|
||||
|
||||
// evaluates to 5; counter is now 0
|
||||
five = parser.parseExpression("5 + --counter").getValue(context, inventor, int.class);
|
||||
|
||||
// -- Multiplication --
|
||||
|
||||
six = parser.parseExpression("-2 * -3").getValue(int.class); // 6
|
||||
|
||||
double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(double.class); // 24.0
|
||||
|
||||
// -- Division --
|
||||
|
||||
int minusTwo = parser.parseExpression("6 / -3").getValue(int.class); // -2
|
||||
|
||||
double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(double.class); // 1.0
|
||||
|
||||
// -- Modulus --
|
||||
|
||||
int three = parser.parseExpression("7 % 4").getValue(int.class); // 3
|
||||
|
||||
int oneInt = parser.parseExpression("8 / 5 % 2").getValue(int.class); // 1
|
||||
|
||||
// -- Exponential power --
|
||||
|
||||
int maxInt = parser.parseExpression("(2^31) - 1").getValue(int.class); // Integer.MAX_VALUE
|
||||
|
||||
int minInt = parser.parseExpression("-2^31").getValue(int.class); // Integer.MIN_VALUE
|
||||
|
||||
// -- Operator precedence --
|
||||
|
||||
int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(int.class); // -21
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val inventor = Inventor()
|
||||
val context = SimpleEvaluationContext.forReadWriteDataBinding().build()
|
||||
|
||||
// -- Addition --
|
||||
|
||||
var two = parser.parseExpression("1 + 1").getValue(Int::class.java) // 2
|
||||
|
||||
// -- Subtraction --
|
||||
|
||||
// Subtraction
|
||||
val four = parser.parseExpression("1 - -3").getValue(Int::class.java) // 4
|
||||
|
||||
val d = parser.parseExpression("1000.00 - 1e4").getValue(Double::class.java) // -9000
|
||||
|
||||
// Multiplication
|
||||
val six = parser.parseExpression("-2 * -3").getValue(Int::class.java) // 6
|
||||
// -- Increment --
|
||||
|
||||
// The counter property in Inventor has an initial value of 0.
|
||||
|
||||
// evaluates to 2; counter is now 1
|
||||
two = parser.parseExpression("counter++ + 2").getValue(context, inventor, Int::class.java)
|
||||
|
||||
// evaluates to 5; counter is now 2
|
||||
var five = parser.parseExpression("3 + ++counter").getValue(context, inventor, Int::class.java)
|
||||
|
||||
// -- Decrement --
|
||||
|
||||
// The counter property in Inventor has a value of 2.
|
||||
|
||||
// evaluates to 6; counter is now 1
|
||||
var six = parser.parseExpression("counter-- + 4").getValue(context, inventor, Int::class.java)
|
||||
|
||||
// evaluates to 5; counter is now 0
|
||||
five = parser.parseExpression("5 + --counter").getValue(context, inventor, Int::class.java)
|
||||
|
||||
// -- Multiplication --
|
||||
|
||||
six = parser.parseExpression("-2 * -3").getValue(Int::class.java) // 6
|
||||
|
||||
val twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double::class.java) // 24.0
|
||||
|
||||
// Division
|
||||
// -- Division --
|
||||
|
||||
val minusTwo = parser.parseExpression("6 / -3").getValue(Int::class.java) // -2
|
||||
|
||||
val one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double::class.java) // 1.0
|
||||
|
||||
// Modulus
|
||||
// -- Modulus --
|
||||
|
||||
val three = parser.parseExpression("7 % 4").getValue(Int::class.java) // 3
|
||||
|
||||
val one = parser.parseExpression("8 / 5 % 2").getValue(Int::class.java) // 1
|
||||
val oneInt = parser.parseExpression("8 / 5 % 2").getValue(Int::class.java) // 1
|
||||
|
||||
// -- Exponential power --
|
||||
|
||||
val maxInt = parser.parseExpression("(2^31) - 1").getValue(Int::class.java) // Integer.MAX_VALUE
|
||||
|
||||
val minInt = parser.parseExpression("-2^31").getValue(Int::class.java) // Integer.MIN_VALUE
|
||||
|
||||
// -- Operator precedence --
|
||||
|
||||
// Operator precedence
|
||||
val minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Int::class.java) // -21
|
||||
----
|
||||
======
|
||||
@@ -325,3 +529,83 @@ Kotlin::
|
||||
======
|
||||
|
||||
|
||||
[[expressions-operators-overloaded]]
|
||||
== Overloaded Operators
|
||||
|
||||
By default, the mathematical operations defined in SpEL's `Operation` enum (`ADD`,
|
||||
`SUBTRACT`, `DIVIDE`, `MULTIPLY`, `MODULUS`, and `POWER`) support simple types like
|
||||
numbers. By providing an implementation of `OperatorOverloader`, the expression language
|
||||
can support these operations on other types.
|
||||
|
||||
For example, if we want to overload the `ADD` operator to allow two lists to be
|
||||
concatenated using the `+` sign, we can implement a custom `OperatorOverloader` as
|
||||
follows.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
pubic class ListConcatenation implements OperatorOverloader {
|
||||
|
||||
@Override
|
||||
public boolean overridesOperation(Operation operation, Object left, Object right) {
|
||||
return (operation == Operation.ADD &&
|
||||
left instanceof List && right instanceof List);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object operate(Operation operation, Object left, Object right) {
|
||||
if (operation == Operation.ADD &&
|
||||
left instanceof List list1 && right instanceof List list2) {
|
||||
|
||||
List result = new ArrayList(list1);
|
||||
result.addAll(list2);
|
||||
return result;
|
||||
}
|
||||
throw new UnsupportedOperationException(
|
||||
"No overload for operation %s and operands [%s] and [%s]"
|
||||
.formatted(operation, left, right));
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
If we register `ListConcatenation` as the `OperatorOverloader` in a
|
||||
`StandardEvaluationContext`, we can then evaluate expressions like `{1, 2, 3} + {4, 5}`
|
||||
as demonstrated in the following example.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
StandardEvaluationContext context = new StandardEvaluationContext();
|
||||
context.setOperatorOverloader(new ListConcatenation());
|
||||
|
||||
// evaluates to a new list: [1, 2, 3, 4, 5]
|
||||
parser.parseExpression("{1, 2, 3} + {2 + 2, 5}").getValue(context, List.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
StandardEvaluationContext context = StandardEvaluationContext()
|
||||
context.setOperatorOverloader(ListConcatenation())
|
||||
|
||||
// evaluates to a new list: [1, 2, 3, 4, 5]
|
||||
parser.parseExpression("{1, 2, 3} + {2 + 2, 5}").getValue(context, List::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
An `OperatorOverloader` does not change the default semantics for an operator. For
|
||||
example, `2 + 2` in the above example still evaluates to `4`.
|
||||
====
|
||||
|
||||
[CAUTION]
|
||||
====
|
||||
Any expression that uses an overloaded operator cannot be compiled. See
|
||||
xref:core/expressions/evaluation.adoc#expressions-compiler-limitations[Compiler Limitations]
|
||||
for details.
|
||||
====
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[[expressions-templating]]
|
||||
= Expression templating
|
||||
= Expression Templating
|
||||
|
||||
Expression templates allow mixing literal text with one or more evaluation blocks.
|
||||
Each evaluation block is delimited with prefix and suffix characters that you can
|
||||
@@ -32,53 +32,13 @@ Kotlin::
|
||||
======
|
||||
|
||||
The string is evaluated by concatenating the literal text `'random number is '` with the
|
||||
result of evaluating the expression inside the `#{ }` delimiter (in this case, the result
|
||||
of calling that `random()` method). The second argument to the `parseExpression()` method
|
||||
is of the type `ParserContext`. The `ParserContext` interface is used to influence how
|
||||
the expression is parsed in order to support the expression templating functionality.
|
||||
The definition of `TemplateParserContext` follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class TemplateParserContext implements ParserContext {
|
||||
|
||||
public String getExpressionPrefix() {
|
||||
return "#{";
|
||||
}
|
||||
|
||||
public String getExpressionSuffix() {
|
||||
return "}";
|
||||
}
|
||||
|
||||
public boolean isTemplate() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class TemplateParserContext : ParserContext {
|
||||
|
||||
override fun getExpressionPrefix(): String {
|
||||
return "#{"
|
||||
}
|
||||
|
||||
override fun getExpressionSuffix(): String {
|
||||
return "}"
|
||||
}
|
||||
|
||||
override fun isTemplate(): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
result of evaluating the expression inside the `#{ }` delimiters (in this case, the
|
||||
result of calling that `random()` method). The second argument to the `parseExpression()`
|
||||
method is of the type `ParserContext`. The `ParserContext` interface is used to influence
|
||||
how the expression is parsed in order to support the expression templating functionality.
|
||||
The `TemplateParserContext` used in the previous example resides in the
|
||||
`org.springframework.expression.common` package and is an implementation of the
|
||||
`ParserContext` which by default configures the prefix and suffix to `#{` and `}`,
|
||||
respectively.
|
||||
|
||||
|
||||
|
||||
@@ -6,15 +6,36 @@ are set by using the `setVariable()` method in `EvaluationContext` implementatio
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Valid variable names must be composed of one or more of the following supported
|
||||
Variable names must be begin with a letter (as defined below), an underscore, or a dollar
|
||||
sign.
|
||||
|
||||
Variable names must be composed of one or more of the following supported types of
|
||||
characters.
|
||||
|
||||
* letters: `A` to `Z` and `a` to `z`
|
||||
* digits: `0` to `9`
|
||||
* letter: any character for which `java.lang.Character.isLetter(char)` returns `true`
|
||||
- This includes letters such as `A` to `Z`, `a` to `z`, `ü`, `ñ`, and `é` as well as
|
||||
letters from other character sets such as Chinese, Japanese, Cyrillic, etc.
|
||||
* digit: `0` to `9`
|
||||
* underscore: `_`
|
||||
* dollar sign: `$`
|
||||
====
|
||||
|
||||
[TIP]
|
||||
====
|
||||
When setting a variable or root context object in the `EvaluationContext`, it is advised
|
||||
that the type of the variable or root context object be `public`.
|
||||
|
||||
Otherwise, certain types of SpEL expressions involving a variable or root context object
|
||||
with a non-public type may fail to evaluate or compile.
|
||||
====
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
Since variables share a common namespace with
|
||||
xref:core/expressions/language-ref/functions.adoc[functions] in the evaluation context,
|
||||
care must be taken to ensure that variable names and functions names do not overlap.
|
||||
====
|
||||
|
||||
The following example shows how to use variables.
|
||||
|
||||
[tabs]
|
||||
|
||||
@@ -429,7 +429,7 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
A `ConstraintViolation` on `Person.name()` is adapted to a `FieldErrro` with the following:
|
||||
A `ConstraintViolation` on `Person.name()` is adapted to a `FieldError` 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)
|
||||
|
||||
@@ -717,7 +717,7 @@ For example, with positional parameters:
|
||||
|
||||
public int countOfActorsByFirstName(String firstName) {
|
||||
return this.jdbcClient.sql("select count(*) from t_actor where first_name = ?")
|
||||
.param(firstName);
|
||||
.param(firstName)
|
||||
.query(Integer.class).single();
|
||||
}
|
||||
----
|
||||
@@ -730,7 +730,7 @@ For example, with named parameters:
|
||||
|
||||
public int countOfActorsByFirstName(String firstName) {
|
||||
return this.jdbcClient.sql("select count(*) from t_actor where first_name = :firstName")
|
||||
.param("firstName", firstName);
|
||||
.param("firstName", firstName)
|
||||
.query(Integer.class).single();
|
||||
}
|
||||
----
|
||||
@@ -759,8 +759,8 @@ 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);
|
||||
Actor actor = this.jdbcClient.sql("select first_name, last_name from t_actor where id = ?")
|
||||
.param(1212L)
|
||||
.query(Actor.class)
|
||||
.single();
|
||||
----
|
||||
@@ -769,8 +769,8 @@ 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);
|
||||
Optional<Actor> actor = this.jdbcClient.sql("select first_name, last_name from t_actor where id = ?")
|
||||
.param(1212L)
|
||||
.query(Actor.class)
|
||||
.optional();
|
||||
----
|
||||
@@ -780,7 +780,7 @@ 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");
|
||||
.param("Leonor").param("Watling")
|
||||
.update();
|
||||
----
|
||||
|
||||
@@ -789,7 +789,7 @@ 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");
|
||||
.param("firstName", "Leonor").param("lastName", "Watling")
|
||||
.update();
|
||||
----
|
||||
|
||||
@@ -800,7 +800,7 @@ provides `firstName` and `lastName` properties, such as the `Actor` class from a
|
||||
[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");
|
||||
.paramSource(new Actor("Leonor", "Watling")
|
||||
.update();
|
||||
----
|
||||
|
||||
|
||||
@@ -268,8 +268,8 @@ The actual JPA provider bootstrapping is handed off to the specified executor an
|
||||
running in parallel, to the application bootstrap thread. The exposed `EntityManagerFactory`
|
||||
proxy can be injected into other application components and is even able to respond to
|
||||
`EntityManagerFactoryInfo` configuration inspection. However, once the actual JPA provider
|
||||
is being accessed by other components (for example, calling `createEntityManager`), those calls
|
||||
block until the background bootstrapping has completed. In particular, when you use
|
||||
is being accessed by other components (for example, calling `createEntityManager`), those
|
||||
calls block until the background bootstrapping has completed. In particular, when you use
|
||||
Spring Data JPA, make sure to set up deferred bootstrapping for its repositories as well.
|
||||
|
||||
|
||||
@@ -284,9 +284,9 @@ 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:
|
||||
`@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:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
||||
@@ -440,12 +440,8 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val tuples: MutableList<Array<Any>> = ArrayList()
|
||||
tuples.add(arrayOf("John", 35))
|
||||
tuples.add(arrayOf("Ann", 50))
|
||||
|
||||
client.sql("SELECT id, name, state FROM table WHERE age IN (:ages)")
|
||||
.bind("tuples", arrayOf(35, 50))
|
||||
.bind("ages", arrayOf(35, 50))
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ As of Spring Framework 5.2, the default configuration also provides support for
|
||||
Vavr's `Try` method to trigger transaction rollbacks when it returns a 'Failure'.
|
||||
This allows you to handle functional-style errors using Try and have the transaction
|
||||
automatically rolled back in case of a failure. For more information on Vavr's Try,
|
||||
refer to the [official Vavr documentation](https://www.vavr.io/vavr-docs/#_try).
|
||||
refer to the https://docs.vavr.io/#_try[official Vavr documentation].
|
||||
|
||||
Here's an example of how to use Vavr's Try with a transactional method:
|
||||
[tabs]
|
||||
|
||||
+5
@@ -38,6 +38,11 @@ within the method.
|
||||
A reactive transaction managed by `ReactiveTransactionManager` uses the Reactor context
|
||||
instead of thread-local attributes. As a consequence, all participating data access
|
||||
operations need to execute within the same Reactor context in the same reactive pipeline.
|
||||
|
||||
When configured with a `ReactiveTransactionManager`, all transaction-demarcated methods
|
||||
are expected to return a reactive pipeline. Void methods or regular return types need
|
||||
to be associated with a regular `PlatformTransactionManager`, e.g. through the
|
||||
`transactionManager` attribute of the corresponding `@Transactional` declarations.
|
||||
====
|
||||
|
||||
The following image shows a conceptual view of calling a method on a transactional proxy:
|
||||
|
||||
@@ -18,7 +18,7 @@ WebSocket, RSocket.
|
||||
xref:integration.adoc[Integration] :: REST Clients, JMS, JCA, JMX,
|
||||
Email, Tasks, Scheduling, Caching, Observability, JVM Checkpoint Restore.
|
||||
xref:languages.adoc[Languages] :: Kotlin, Groovy, Dynamic Languages.
|
||||
xref:testing/appendix.adoc[Appendix] :: Spring properties.
|
||||
xref:appendix.adoc[Appendix] :: Spring properties.
|
||||
{spring-framework-wiki}[Wiki] :: What's New,
|
||||
Upgrade Notes, Supported Versions, additional cross-version information.
|
||||
|
||||
@@ -29,7 +29,7 @@ Brannen, Ramnivas Laddad, Arjen Poutsma, Chris Beams, Tareq Abedrabbo, Andy Clem
|
||||
Syer, Oliver Gierke, Rossen Stoyanchev, Phillip Webb, Rob Winch, Brian Clozel, Stephane
|
||||
Nicoll, Sebastien Deleuze, Jay Bryant, Mark Paluch
|
||||
|
||||
Copyright © 2002 - 2023 VMware, Inc. All Rights Reserved.
|
||||
Copyright © 2002 - 2024 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Copies of this document may be made for your own use and for distribution to others,
|
||||
provided that you do not charge any fee for such copies and further provided that each
|
||||
|
||||
+3
-2
@@ -1,5 +1,6 @@
|
||||
[[class-data-sharing]]
|
||||
= Class Data Sharing
|
||||
[[cds]]
|
||||
= CDS
|
||||
:page-aliases: integration/class-data-sharing.adoc
|
||||
|
||||
Class Data Sharing (CDS) is a https://docs.oracle.com/en/java/javase/17/vm/class-data-sharing.html[JVM feature]
|
||||
that can help reduce the startup time and memory footprint of Java applications.
|
||||
@@ -85,7 +85,7 @@ email when someone places an order:
|
||||
|
||||
// Call the collaborators to persist the order...
|
||||
|
||||
// Create a thread safe "copy" of the template message and customize it
|
||||
// Create a thread-safe "copy" of the template message and customize it
|
||||
SimpleMailMessage msg = new SimpleMailMessage(this.templateMessage);
|
||||
msg.setTo(order.getCustomer().getEmailAddress());
|
||||
msg.setText(
|
||||
|
||||
@@ -108,7 +108,7 @@ By default, the following `KeyValues` are created:
|
||||
|===
|
||||
|Name | Description
|
||||
|`code.function` _(required)_|Name of Java `Method` that is scheduled for execution.
|
||||
|`code.namespace` _(required)_|Canonical name of the class of the bean instance that holds the scheduled method.
|
||||
|`code.namespace` _(required)_|Canonical name of the class of the bean instance that holds the scheduled method, or `"ANONYMOUS"` for anonymous classes.
|
||||
|`error` _(required)_|Class name of the exception thrown during the execution, or `"none"` if no exception happened.
|
||||
|`exception` _(deprecated)_|Duplicates the `error` key and might be removed in the future.
|
||||
|`outcome` _(required)_|Outcome of the method execution. Can be `"SUCCESS"`, `"ERROR"` or `"UNKNOWN"` (if for example the operation was cancelled during execution).
|
||||
|
||||
@@ -13,12 +13,12 @@ The Spring Framework provides the following choices for making calls to REST end
|
||||
== `RestClient`
|
||||
|
||||
The `RestClient` is a synchronous HTTP client that offers a modern, fluent API.
|
||||
It offers an abstraction over HTTP libraries that allows for convenient conversion from Java object to HTTP request, and creation of objects from the HTTP response.
|
||||
It offers an abstraction over HTTP libraries that allows for convenient conversion from a Java object to an HTTP request, and the creation of objects from an HTTP response.
|
||||
|
||||
=== Creating a `RestClient`
|
||||
|
||||
The `RestClient` is created using one of the static `create` methods.
|
||||
You can also use `builder` to get a builder with further options, such as specifying which HTTP library to use (see <<rest-request-factories>>) and which message converters to use (see <<rest-message-conversion>>), setting a default URI, default path variables, a default request headers, or `uriBuilderFactory`, or registering interceptors and initializers.
|
||||
You can also use `builder()` to get a builder with further options, such as specifying which HTTP library to use (see <<rest-request-factories>>) and which message converters to use (see <<rest-message-conversion>>), setting a default URI, default path variables, default request headers, or `uriBuilderFactory`, or registering interceptors and initializers.
|
||||
|
||||
Once created (or built), the `RestClient` can be used safely by multiple threads.
|
||||
|
||||
@@ -51,9 +51,9 @@ val defaultClient = RestClient.create()
|
||||
|
||||
val customClient = RestClient.builder()
|
||||
.requestFactory(HttpComponentsClientHttpRequestFactory())
|
||||
.messageConverters(converters -> converters.add(MyCustomMessageConverter()))
|
||||
.messageConverters { converters -> converters.add(MyCustomMessageConverter()) }
|
||||
.baseUrl("https://example.com")
|
||||
.defaultUriVariables(Map.of("variable", "foo"))
|
||||
.defaultUriVariables(mapOf("variable" to "foo"))
|
||||
.defaultHeader("My-Header", "Foo")
|
||||
.requestInterceptor(myCustomInterceptor)
|
||||
.requestInitializer(myCustomInitializer)
|
||||
@@ -64,35 +64,36 @@ val customClient = RestClient.builder()
|
||||
=== Using the `RestClient`
|
||||
|
||||
When making an HTTP request with the `RestClient`, the first thing to specify is which HTTP method to use.
|
||||
This can be done with `method(HttpMethod)`, or with the convenience methods `get()`, `head()`, `post()`, and so on.
|
||||
This can be done with `method(HttpMethod)` or with the convenience methods `get()`, `head()`, `post()`, and so on.
|
||||
|
||||
==== Request URL
|
||||
|
||||
Next, the request URI can be specified with the `uri` methods.
|
||||
This step is optional, and can be skipped if the `RestClient` is configured with a default URI.
|
||||
The URL is typically specified as `String`, with optional URI template variables.
|
||||
This step is optional and can be skipped if the `RestClient` is configured with a default URI.
|
||||
The URL is typically specified as a `String`, with optional URI template variables.
|
||||
String URLs are encoded by default, but this can be changed by building a client with a custom `uriBuilderFactory`.
|
||||
|
||||
The URL can also be provided with a function, or as `java.net.URI`, both of which are not encoded.
|
||||
The URL can also be provided with a function or as a `java.net.URI`, both of which are not encoded.
|
||||
For more details on working with and encoding URIs, see xref:web/webmvc/mvc-uri-building.adoc[URI Links].
|
||||
|
||||
==== Request headers and body
|
||||
|
||||
If necessary, the HTTP request can be manipulated, by adding request headers with `header(String, String)`, `headers(Consumer<HttpHeaders>`, or with the convenience methods `accept(MediaType...)`, `acceptCharset(Charset...)` and so on.
|
||||
For HTTP request that can contain a body (`POST`, `PUT`, and `PATCH`), additional methods are available: `contentType(MediaType)`, and `contentLength(long)`.
|
||||
If necessary, the HTTP request can be manipulated by adding request headers with `header(String, String)`, `headers(Consumer<HttpHeaders>`, or with the convenience methods `accept(MediaType...)`, `acceptCharset(Charset...)` and so on.
|
||||
For HTTP requests that can contain a body (`POST`, `PUT`, and `PATCH`), additional methods are available: `contentType(MediaType)`, and `contentLength(long)`.
|
||||
|
||||
The request body itself can be set by `body(Object)`, which internally uses <<rest-message-conversion>>.
|
||||
Alternatively, the request body can be set using a `ParameterizedTypeReference`, allowing you to use generics.
|
||||
Finally, the body can be set to a callback function that writes to an `OutputStream`.
|
||||
|
||||
==== Retrieving the response
|
||||
|
||||
Once the request has been set up, the HTTP response is accessed by invoking `retrieve()`.
|
||||
The response body can be accessed by using `body(Class)`, or `body(ParameterizedTypeReference)` for parameterized types like lists.
|
||||
The `body` method converts the response contents into various types, for instance bytes can be converted into a `String`, JSON into objects using Jackson, and so on (see <<rest-message-conversion>>).
|
||||
The response body can be accessed by using `body(Class)` or `body(ParameterizedTypeReference)` for parameterized types like lists.
|
||||
The `body` method converts the response contents into various types – for instance, bytes can be converted into a `String`, JSON can be converted into objects using Jackson, and so on (see <<rest-message-conversion>>).
|
||||
|
||||
The response can also be converted into a `ResponseEntity`, giving access to the response headers as well as the body.
|
||||
|
||||
This sample shows how `RestClient` can be used to perform a simple GET request.
|
||||
This sample shows how `RestClient` can be used to perform a simple `GET` request.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -171,7 +172,7 @@ println("Contents: " + result.body) <3>
|
||||
======
|
||||
|
||||
`RestClient` can convert JSON to objects, using the Jackson library.
|
||||
Note the usage of uri variables in this sample, and that the `Accept` header is set to JSON.
|
||||
Note the usage of URI variables in this sample and that the `Accept` header is set to JSON.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -248,8 +249,9 @@ val response = restClient.post() <2>
|
||||
======
|
||||
|
||||
==== Error handling
|
||||
|
||||
By default, `RestClient` throws a subclass of `RestClientException` when retrieving a response with a 4xx or 5xx status code.
|
||||
This behavior can be overriden using `onStatus`.
|
||||
This behavior can be overridden using `onStatus`.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -286,8 +288,9 @@ val result = restClient.get() <1>
|
||||
======
|
||||
|
||||
==== Exchange
|
||||
For more advanced scenarios, the `RestClient` gives access to the underlying HTTP request and response through the `exchange` method, which can be used instead of `retrieve()`.
|
||||
Status handlers are not applied when you exchange, because the exchange function already provides access to the full response, allowing you to perform any error handling necessary.
|
||||
|
||||
For more advanced scenarios, the `RestClient` gives access to the underlying HTTP request and response through the `exchange()` method, which can be used instead of `retrieve()`.
|
||||
Status handlers are not applied when use `exchange()`, because the exchange function already provides access to the full response, allowing you to perform any error handling necessary.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -336,6 +339,7 @@ val result = restClient.get()
|
||||
|
||||
[[rest-message-conversion]]
|
||||
=== HTTP Message Conversion
|
||||
|
||||
[.small]#xref:web/webflux/reactive-spring.adoc#webflux-codecs[See equivalent in the Reactive stack]#
|
||||
|
||||
The `spring-web` module contains the `HttpMessageConverter` interface for reading and writing the body of HTTP requests and responses through `InputStream` and `OutputStream`.
|
||||
@@ -397,7 +401,7 @@ By default, this converter supports `text/xml` and `application/xml`.
|
||||
|===
|
||||
|
||||
By default, `RestClient` and `RestTemplate` register all built-in message converters, depending on the availability of underlying libraries on the classpath.
|
||||
You can also set the message converters to use explicitly, by using `messageConverters` on the `RestClient` builder, or via the `messageConverters` property of `RestTemplate`.
|
||||
You can also set the message converters to use explicitly, by using the `messageConverters()` method on the `RestClient` builder, or via the `messageConverters` property of `RestTemplate`.
|
||||
|
||||
==== Jackson JSON Views
|
||||
|
||||
@@ -437,13 +441,13 @@ parts.add("xmlPart", new HttpEntity<>(myBean, headers));
|
||||
----
|
||||
|
||||
In most cases, you do not have to specify the `Content-Type` for each part.
|
||||
The content type is determined automatically based on the `HttpMessageConverter` chosen to serialize it or, in the case of a `Resource` based on the file extension.
|
||||
The content type is determined automatically based on the `HttpMessageConverter` chosen to serialize it or, in the case of a `Resource`, based on the file extension.
|
||||
If necessary, you can explicitly provide the `MediaType` with an `HttpEntity` wrapper.
|
||||
|
||||
Once the `MultiValueMap` is ready, you can use it as the body of a POST request, using `RestClient.post().body(parts)` (or `RestTemplate.postForObject`).
|
||||
Once the `MultiValueMap` is ready, you can use it as the body of a `POST` request, using `RestClient.post().body(parts)` (or `RestTemplate.postForObject`).
|
||||
|
||||
If the `MultiValueMap` contains at least one non-`String` value, the `Content-Type` is set to `multipart/form-data` by the `FormHttpMessageConverter`.
|
||||
If the `MultiValueMap` has `String` values the `Content-Type` defaults to `application/x-www-form-urlencoded`.
|
||||
If the `MultiValueMap` has `String` values, the `Content-Type` defaults to `application/x-www-form-urlencoded`.
|
||||
If necessary the `Content-Type` may also be set explicitly.
|
||||
|
||||
[[rest-request-factories]]
|
||||
@@ -453,11 +457,11 @@ To execute the HTTP request, `RestClient` uses a client HTTP library.
|
||||
These libraries are adapted via the `ClientRequestFactory` interface.
|
||||
Various implementations are available:
|
||||
|
||||
* `JdkClientHttpRequestFactory` for Java's `HttpClient`,
|
||||
* `HttpComponentsClientHttpRequestFactory` for use with Apache HTTP Components `HttpClient`,
|
||||
* `JettyClientHttpRequestFactory` for Jetty's `HttpClient`,
|
||||
* `ReactorNettyClientRequestFactory` for Reactor Netty's `HttpClient`,
|
||||
* `SimpleClientHttpRequestFactory` as a simple default.
|
||||
* `JdkClientHttpRequestFactory` for Java's `HttpClient`
|
||||
* `HttpComponentsClientHttpRequestFactory` for use with Apache HTTP Components `HttpClient`
|
||||
* `JettyClientHttpRequestFactory` for Jetty's `HttpClient`
|
||||
* `ReactorNettyClientRequestFactory` for Reactor Netty's `HttpClient`
|
||||
* `SimpleClientHttpRequestFactory` as a simple default
|
||||
|
||||
|
||||
If no request factory is specified when the `RestClient` was built, it will use the Apache or Jetty `HttpClient` if they are available on the classpath.
|
||||
@@ -473,12 +477,12 @@ synchronous, asynchronous, and streaming scenarios.
|
||||
|
||||
`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.
|
||||
* 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.
|
||||
|
||||
@@ -848,17 +852,17 @@ It can be used to migrate from the latter to the former.
|
||||
.toEntity(ParameterizedTypeReference)` footnote:request-entity[]
|
||||
|
||||
|
||||
| `execute(String, HttpMethod method, RequestCallback, ResponseExtractor, Object...)`
|
||||
| `execute(String, HttpMethod, RequestCallback, ResponseExtractor, Object...)`
|
||||
| `method(HttpMethod)
|
||||
.uri(String, Object...)
|
||||
.exchange(ExchangeFunction)`
|
||||
|
||||
| `execute(String, HttpMethod method, RequestCallback, ResponseExtractor, Map)`
|
||||
| `execute(String, HttpMethod, RequestCallback, ResponseExtractor, Map)`
|
||||
| `method(HttpMethod)
|
||||
.uri(String, Map)
|
||||
.exchange(ExchangeFunction)`
|
||||
|
||||
| `execute(URI, HttpMethod method, RequestCallback, ResponseExtractor)`
|
||||
| `execute(URI, HttpMethod, RequestCallback, ResponseExtractor)`
|
||||
| `method(HttpMethod)
|
||||
.uri(URI)
|
||||
.exchange(ExchangeFunction)`
|
||||
@@ -906,8 +910,8 @@ For `WebClient`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
WebClient client = WebClient.builder().baseUrl("https://api.github.com/").build();
|
||||
WebClientAdapter adapter = WebClientAdapter.forClient(webClient)
|
||||
WebClient webClient = WebClient.builder().baseUrl("https://api.github.com/").build();
|
||||
WebClientAdapter adapter = WebClientAdapter.create(webClient);
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
|
||||
|
||||
RepositoryService service = factory.createClient(RepositoryService.class);
|
||||
@@ -973,6 +977,9 @@ method parameters:
|
||||
`Map<String, ?>` with multiple variables, or an individual value. Type conversion
|
||||
is supported for non-String values.
|
||||
|
||||
| `@RequestAttribute`
|
||||
| Provide an `Object` to add as a request attribute. Only supported by `WebClient`.
|
||||
|
||||
| `@RequestBody`
|
||||
| Provide the body of the request either as an Object to be serialized, or a
|
||||
Reactive Streams `Publisher` such as `Mono`, `Flux`, or any other async type supported
|
||||
@@ -1078,11 +1085,34 @@ underlying HTTP client, which operates at a lower level and provides more contro
|
||||
|
||||
|
||||
[[rest-http-interface-exceptions]]
|
||||
=== Exception Handling
|
||||
=== Error Handling
|
||||
|
||||
By default, `WebClient` raises `WebClientResponseException` for 4xx and 5xx HTTP status
|
||||
codes. To customize this, you can register a response status handler that applies to all
|
||||
responses performed through the client:
|
||||
To customize error response handling, you need to configure the underlying HTTP client.
|
||||
|
||||
For `RestClient`:
|
||||
|
||||
By default, `RestClient` raises `RestClientException` for 4xx and 5xx HTTP status codes.
|
||||
To customize this, register a response status handler that applies to all responses
|
||||
performed through the client:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
RestClient restClient = RestClient.builder()
|
||||
.defaultStatusHandler(HttpStatusCode::isError, (request, response) -> ...)
|
||||
.build();
|
||||
|
||||
RestClientAdapter adapter = RestClientAdapter.create(restClient);
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
|
||||
----
|
||||
|
||||
For more details and options, such as suppressing error status codes, see the Javadoc of
|
||||
`defaultStatusHandler` in `RestClient.Builder`.
|
||||
|
||||
For `WebClient`:
|
||||
|
||||
By default, `WebClient` raises `WebClientResponseException` for 4xx and 5xx HTTP status codes.
|
||||
To customize this, register a response status handler that applies to all responses
|
||||
performed through the client:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -1090,10 +1120,28 @@ responses performed through the client:
|
||||
.defaultStatusHandler(HttpStatusCode::isError, resp -> ...)
|
||||
.build();
|
||||
|
||||
WebClientAdapter clientAdapter = WebClientAdapter.forClient(webClient);
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory
|
||||
.builder(clientAdapter).build();
|
||||
WebClientAdapter adapter = WebClientAdapter.create(webClient);
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(adapter).build();
|
||||
----
|
||||
|
||||
For more details and options, such as suppressing error status codes, see the Javadoc of
|
||||
`defaultStatusHandler` in `WebClient.Builder`.
|
||||
|
||||
For `RestTemplate`:
|
||||
|
||||
By default, `RestTemplate` raises `RestClientException` for 4xx and 5xx HTTP status codes.
|
||||
To customize this, register an error handler that applies to all responses
|
||||
performed through the client:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
restTemplate.setErrorHandler(myErrorHandler);
|
||||
|
||||
RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate);
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
|
||||
----
|
||||
|
||||
For more details and options, see the Javadoc of `setErrorHandler` in `RestTemplate` and
|
||||
the `ResponseErrorHandler` hierarchy.
|
||||
|
||||
|
||||
@@ -252,7 +252,9 @@ application server environments, as well -- in particular on Tomcat and Jetty.
|
||||
As of 6.1, `ThreadPoolTaskScheduler` provides a pause/resume capability and graceful
|
||||
shutdown through Spring's lifecycle management. There is also a new option called
|
||||
`SimpleAsyncTaskScheduler` which is aligned with JDK 21's Virtual Threads, using a
|
||||
single scheduler thread but firing up a new thread for every scheduled task execution.
|
||||
single scheduler thread but firing up a new thread for every scheduled task execution
|
||||
(except for fixed-delay tasks which all operate on a single scheduler thread, so for
|
||||
this virtual-thread-aligned option, fixed rates and cron triggers are recommended).
|
||||
|
||||
|
||||
|
||||
@@ -477,12 +479,12 @@ the framework to invoke a suspending function as a `Publisher`.
|
||||
|
||||
The Spring Framework will obtain a `Publisher` for the annotated method once and will
|
||||
schedule a `Runnable` in which it subscribes to said `Publisher`. These inner regular
|
||||
subscriptions occur according to the corresponding `cron`/fixedDelay`/`fixedRate` configuration.
|
||||
subscriptions occur according to the corresponding `cron`/`fixedDelay`/`fixedRate` configuration.
|
||||
|
||||
If the `Publisher` emits `onNext` signal(s), these are ignored and discarded (the same way
|
||||
return values from synchronous `@Scheduled` methods are ignored).
|
||||
|
||||
In the following example, the `Flux` emits `onNext("Hello"), onNext("World")` every 5
|
||||
In the following example, the `Flux` emits `onNext("Hello")`, `onNext("World")` every 5
|
||||
seconds, but these values are unused:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
|
||||
@@ -13,7 +13,7 @@ Most of the code samples of the reference documentation are
|
||||
provided in Kotlin in addition to Java.
|
||||
|
||||
The easiest way to build a Spring application with Kotlin is to leverage Spring Boot and
|
||||
its{spring-boot-docs}/boot-features-kotlin.html[dedicated Kotlin support].
|
||||
its {spring-boot-docs}/boot-features-kotlin.html[dedicated Kotlin support].
|
||||
{spring-site-guides}/tutorials/spring-boot-kotlin/[This comprehensive tutorial]
|
||||
will teach you how to build Spring Boot applications with Kotlin using https://start.spring.io/#!language=kotlin&type=gradle-project[start.spring.io].
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ NOTE: Spring Boot is based on JavaConfig and
|
||||
{spring-boot-issues}/8115[does not yet provide specific support for functional bean definition],
|
||||
but you can experimentally use functional bean definitions through Spring Boot's `ApplicationContextInitializer` support.
|
||||
See {stackoverflow-questions}/45935931/how-to-use-functional-bean-definition-kotlin-dsl-with-spring-boot-and-spring-w/46033685#46033685[this Stack Overflow answer]
|
||||
for more details and up-to-date information. See also the experimental Kofu DSL developed in {spring-github-org}/spring-fu[Spring Fu incubator].
|
||||
for more details and up-to-date information. See also the experimental Kofu DSL developed in {spring-github-org}-experimental/spring-fu[Spring Fu incubator].
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -14,8 +14,10 @@ Spring Framework provides support for Coroutines on the following scope:
|
||||
* Suspending function support in Spring MVC and WebFlux annotated `@Controller`
|
||||
* Extensions for WebFlux {spring-framework-api-kdoc}/spring-webflux/org.springframework.web.reactive.function.client/index.html[client] and {spring-framework-api-kdoc}/spring-webflux/org.springframework.web.reactive.function.server/index.html[server] functional API.
|
||||
* WebFlux.fn {spring-framework-api-kdoc}/spring-webflux/org.springframework.web.reactive.function.server/co-router.html[coRouter { }] DSL
|
||||
* WebFlux {spring-framework-api-kdoc}/spring-web/org.springframework.web.server/-co-web-filter/index.html[`CoWebFilter`]
|
||||
* Suspending function and `Flow` support in RSocket `@MessageMapping` annotated methods
|
||||
* Extensions for {spring-framework-api-kdoc}/spring-messaging/org.springframework.messaging.rsocket/index.html[`RSocketRequester`]
|
||||
* Spring AOP
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -10,22 +10,21 @@ The easiest way to learn how to build a Spring application with Kotlin is to fol
|
||||
== `start.spring.io`
|
||||
|
||||
The easiest way to start a new Spring Framework project in Kotlin is to create a new Spring
|
||||
Boot 2 project on https://start.spring.io/#!language=kotlin&type=gradle-project[start.spring.io].
|
||||
Boot project on https://start.spring.io/#!language=kotlin&type=gradle-project-kotlin[start.spring.io].
|
||||
|
||||
|
||||
|
||||
[[choosing-the-web-flavor]]
|
||||
== Choosing the Web Flavor
|
||||
|
||||
Spring Framework now comes with two different web stacks: xref:web/webmvc.adoc#mvc[Spring MVC] and
|
||||
Spring Framework comes with two different web stacks: xref:web/webmvc.adoc#mvc[Spring MVC] and
|
||||
xref:testing/unit.adoc#mock-objects-web-reactive[Spring WebFlux].
|
||||
|
||||
Spring WebFlux is recommended if you want to create applications that will deal with latency,
|
||||
long-lived connections, streaming scenarios or if you want to use the web functional
|
||||
Kotlin DSL.
|
||||
long-lived connections or streaming scenarios.
|
||||
|
||||
For other use cases, especially if you are using blocking technologies such as JPA, Spring
|
||||
MVC and its annotation-based programming model is the recommended choice.
|
||||
MVC is the recommended choice.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,15 +2,12 @@
|
||||
= Requirements
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
Spring Framework supports Kotlin 1.3+ and requires
|
||||
Spring Framework supports Kotlin 1.7+ and requires
|
||||
https://search.maven.org/artifact/org.jetbrains.kotlin/kotlin-stdlib[`kotlin-stdlib`]
|
||||
(or one of its variants, such as https://search.maven.org/artifact/org.jetbrains.kotlin/kotlin-stdlib-jdk8[`kotlin-stdlib-jdk8`])
|
||||
and https://search.maven.org/artifact/org.jetbrains.kotlin/kotlin-reflect[`kotlin-reflect`]
|
||||
to be present on the classpath. They are provided by default if you bootstrap a Kotlin project on
|
||||
https://start.spring.io/#!language=kotlin&type=gradle-project[start.spring.io].
|
||||
|
||||
WARNING: Kotlin {kotlin-docs}/inline-classes.html[inline classes] are not yet supported.
|
||||
|
||||
NOTE: The {jackson-github-org}/jackson-module-kotlin[Jackson Kotlin module] is required
|
||||
for serializing or deserializing JSON data for Kotlin classes with Jackson, so make sure to add the
|
||||
`com.fasterxml.jackson.module:jackson-module-kotlin` dependency to your project if you have such need.
|
||||
|
||||
@@ -18,28 +18,9 @@ Kotlin and the Spring Framework:
|
||||
|
||||
The following Github projects offer examples that you can learn from and possibly even extend:
|
||||
|
||||
* https://github.com/spring-guides/tut-spring-boot-kotlin[tut-spring-boot-kotlin]: Sources of {spring-site}/guides/tutorials/spring-boot-kotlin/[the official Spring + Kotlin tutorial]
|
||||
* https://github.com/sdeleuze/spring-boot-kotlin-demo[spring-boot-kotlin-demo]: Regular Spring Boot and Spring Data JPA project
|
||||
* https://github.com/mixitconf/mixit[mixit]: Spring Boot 2, WebFlux, and Reactive Spring Data MongoDB
|
||||
* https://github.com/mixitconf/mixit[mixit]: Spring Boot, WebFlux, and Reactive Spring Data MongoDB
|
||||
* https://github.com/sdeleuze/spring-kotlin-functional[spring-kotlin-functional]: Standalone WebFlux and functional bean definition DSL
|
||||
* https://github.com/sdeleuze/spring-kotlin-fullstack[spring-kotlin-fullstack]: WebFlux Kotlin fullstack example with Kotlin2js for frontend instead of JavaScript or TypeScript
|
||||
* https://github.com/spring-petclinic/spring-petclinic-kotlin[spring-petclinic-kotlin]: Kotlin version of the Spring PetClinic Sample Application
|
||||
* https://github.com/sdeleuze/spring-kotlin-deepdive[spring-kotlin-deepdive]: A step-by-step migration guide for Boot 1.0 and Java to Boot 2.0 and Kotlin
|
||||
* https://github.com/spring-cloud/spring-cloud-gcp/tree/master/spring-cloud-gcp-kotlin-samples/spring-cloud-gcp-kotlin-app-sample[spring-cloud-gcp-kotlin-app-sample]: Spring Boot with Google Cloud Platform Integrations
|
||||
|
||||
|
||||
|
||||
[[issues]]
|
||||
== Issues
|
||||
|
||||
The following list categorizes the pending issues related to Spring and Kotlin support:
|
||||
|
||||
* Spring Framework
|
||||
** {spring-framework-issues}/20606[Unable to use WebTestClient with mock server in Kotlin]
|
||||
** {spring-framework-issues}/20496[Support null-safety at generics, varargs and array elements level]
|
||||
* Kotlin
|
||||
** {kotlin-issues}/KT-6380[Parent issue for Spring Framework support]
|
||||
** {kotlin-issues}/KT-5464[Kotlin requires type inference where Java doesn't]
|
||||
** {kotlin-issues}/KT-20283[Smart cast regression with open classes]
|
||||
** {kotlin-issues}/KT-14984[Impossible to pass not all SAM argument as function]
|
||||
** {kotlin-issues}/KT-15125[Support JSR 223 bindings directly via script variables]
|
||||
** {kotlin-issues}/KT-6653[Kotlin properties do not override Java-style getters and setters]
|
||||
|
||||
@@ -9,7 +9,7 @@ in Kotlin.
|
||||
[[final-by-default]]
|
||||
== Final by Default
|
||||
|
||||
By default, https://discuss.kotlinlang.org/t/classes-final-by-default/166[all classes in Kotlin are `final`].
|
||||
By default, https://discuss.kotlinlang.org/t/classes-final-by-default/166[all classes and member functions in Kotlin are `final`].
|
||||
The `open` modifier on a class is the opposite of Java's `final`: It allows others to inherit from this
|
||||
class. This also applies to member functions, in that they need to be marked as `open` to be overridden.
|
||||
|
||||
@@ -38,6 +38,12 @@ Meta-annotation support means that types annotated with `@Configuration`, `@Cont
|
||||
`@RestController`, `@Service`, or `@Repository` are automatically opened since these
|
||||
annotations are meta-annotated with `@Component`.
|
||||
|
||||
WARNING: Some use cases involving proxies and automatic generation of final methods by the Kotlin compiler require extra
|
||||
care. For example, a Kotlin class with properties will generate related `final` getters and setters. In order
|
||||
to be able to proxy related methods, a type level `@Component` annotation should be preferred to method level `@Bean` in
|
||||
order to have those methods opened by the `kotlin-spring` plugin. A typical use case is `@Scope` and its popular
|
||||
`@RequestScope` specialization.
|
||||
|
||||
https://start.spring.io/#!language=kotlin&type=gradle-project[start.spring.io] enables
|
||||
the `kotlin-spring` plugin by default. So, in practice, you can write your Kotlin beans
|
||||
without any additional `open` keyword, as in Java.
|
||||
@@ -97,6 +103,9 @@ does not require the `kotlin-noarg` plugin if the module uses Spring Data object
|
||||
[[injecting-dependencies]]
|
||||
== Injecting Dependencies
|
||||
|
||||
[[favor-constructor-injection]]
|
||||
=== Favor constructor injection
|
||||
|
||||
Our recommendation is to try to favor constructor injection with `val` read-only (and
|
||||
non-nullable when possible) {kotlin-docs}/properties.html[properties],
|
||||
as the following example shows:
|
||||
@@ -130,7 +139,41 @@ as the following example shows:
|
||||
}
|
||||
----
|
||||
|
||||
[[internal-functions-name-mangling]]
|
||||
=== Internal functions name mangling
|
||||
|
||||
Kotlin functions with the `internal` {kotlin-docs}/visibility-modifiers.html#class-members[visibility modifier] have
|
||||
their names mangled when compiled to JVM bytecode, which has a side effect when injecting dependencies by name.
|
||||
|
||||
For example, this Kotlin class:
|
||||
[source,kotlin,indent=0]
|
||||
----
|
||||
@Configuration
|
||||
class SampleConfiguration {
|
||||
|
||||
@Bean
|
||||
internal fun sampleBean() = SampleBean()
|
||||
}
|
||||
----
|
||||
|
||||
Translates to this Java representation of the compiled JVM bytecode:
|
||||
[source,java,indent=0]
|
||||
----
|
||||
@Configuration
|
||||
@Metadata(/* ... */)
|
||||
public class SampleConfiguration {
|
||||
|
||||
@Bean
|
||||
@NotNull
|
||||
public SampleBean sampleBean$demo_kotlin_internal_test() {
|
||||
return new SampleBean();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
As a consequence, the related bean name represented as a Kotlin string is `"sampleBean\$demo_kotlin_internal_test"`,
|
||||
instead of `"sampleBean"` for the regular `public` function use-case. Make sure to use the mangled name when injecting
|
||||
such bean by name, or add `@JvmName("sampleBean")` to disable name mangling.
|
||||
|
||||
[[injecting-configuration-properties]]
|
||||
== Injecting Configuration Properties
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
|
||||
Spring Framework comes with a Kotlin router DSL available in 3 flavors:
|
||||
|
||||
* WebMvc.fn DSL with {spring-framework-api-kdoc}/spring-webmvc/org.springframework.web.servlet.function/router.html[router { }]
|
||||
* WebFlux.fn <<web-reactive#webflux-fn, Reactive>> DSL with {spring-framework-api-kdoc}/spring-webflux/org.springframework.web.reactive.function.server/router.html[router { }]
|
||||
* WebFlux.fn <<Coroutines>> DSL with {spring-framework-api-kdoc}/spring-webflux/org.springframework.web.reactive.function.server/co-router.html[coRouter { }]
|
||||
* xref:web/webmvc-functional.adoc[WebMvc.fn DSL] with {spring-framework-api-kdoc}/spring-webmvc/org.springframework.web.servlet.function/router.html[router { }]
|
||||
* xref:web/webflux-functional.adoc[WebFlux.fn Reactive DSL] with {spring-framework-api-kdoc}/spring-webflux/org.springframework.web.reactive.function.server/router.html[router { }]
|
||||
* xref:languages/kotlin/coroutines.adoc[WebFlux.fn Coroutines DSL] with {spring-framework-api-kdoc}/spring-webflux/org.springframework.web.reactive.function.server/co-router.html[coRouter { }]
|
||||
|
||||
These DSL let you write clean and idiomatic Kotlin code to build a `RouterFunction` instance as the following example shows:
|
||||
|
||||
@@ -126,7 +126,7 @@ project for more details.
|
||||
[[kotlin-multiplatform-serialization]]
|
||||
== Kotlin multiplatform serialization
|
||||
|
||||
As of Spring Framework 5.3, {kotlin-github-org}/kotlinx.serialization[Kotlin multiplatform serialization] is
|
||||
{kotlin-github-org}/kotlinx.serialization[Kotlin multiplatform serialization] is
|
||||
supported in Spring MVC, Spring WebFlux and Spring Messaging (RSocket). The builtin support currently targets CBOR, JSON, and ProtoBuf formats.
|
||||
|
||||
To enable it, follow {kotlin-github-org}/kotlinx.serialization#setup[those instructions] to add the related dependency and plugin.
|
||||
|
||||
+1
-1
@@ -152,7 +152,7 @@ Kotlin::
|
||||
@Autowired
|
||||
lateinit var accountService: AccountService
|
||||
|
||||
lateinit mockMvc: MockMvc
|
||||
lateinit var mockMvc: MockMvc
|
||||
|
||||
@BeforeEach
|
||||
fun setup(wac: WebApplicationContext) {
|
||||
|
||||
@@ -782,6 +782,73 @@ Kotlin::
|
||||
======
|
||||
|
||||
|
||||
[[webflux-fn-serving-resources]]
|
||||
== Serving Resources
|
||||
|
||||
WebFlux.fn provides built-in support for serving resources.
|
||||
|
||||
NOTE: In addition to the capabilities described below, it is possible to implement even more flexible resource handling thanks to
|
||||
{spring-framework-api}++/web/reactive/function/server/RouterFunctions.html#resources(java.util.function.Function)++[`RouterFunctions#resource(java.util.function.Function)`].
|
||||
|
||||
[[webflux-fn-resource]]
|
||||
=== Redirecting to a resource
|
||||
|
||||
It is possible to redirect requests matching a specified predicate to a resource. This can be useful, for example,
|
||||
for handling redirects in Single Page Applications.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ClassPathResource index = new ClassPathResource("static/index.html");
|
||||
List<String> extensions = Arrays.asList("js", "css", "ico", "png", "jpg", "gif");
|
||||
RequestPredicate spaPredicate = path("/api/**").or(path("/error")).or(pathExtension(extensions::contains)).negate();
|
||||
RouterFunction<ServerResponse> redirectToIndex = route()
|
||||
.resource(spaPredicate, index)
|
||||
.build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val redirectToIndex = router {
|
||||
val index = ClassPathResource("static/index.html")
|
||||
val extensions = listOf("js", "css", "ico", "png", "jpg", "gif")
|
||||
val spaPredicate = !(path("/api/**") or path("/error") or
|
||||
pathExtension(extensions::contains))
|
||||
resource(spaPredicate, index)
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[[webflux-fn-resources]]
|
||||
=== Serving resources from a root location
|
||||
|
||||
It is also possible to route requests that match a given pattern to resources relative to a given root location.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
Resource location = new FileSystemResource("public-resources/");
|
||||
RouterFunction<ServerResponse> resources = RouterFunctions.resources("/resources/**", location);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val location = FileSystemResource("public-resources/")
|
||||
val resources = router { resources("/resources/**", location) }
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[webflux-fn-running]]
|
||||
== Running a Server
|
||||
[.small]#xref:web/webmvc-functional.adoc#webmvc-fn-running[See equivalent in the Servlet stack]#
|
||||
|
||||
@@ -81,7 +81,7 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: `@JsonView` allows an array of view classes but you can only specify only one per
|
||||
NOTE: `@JsonView` allows an array of view classes but you can specify only one per
|
||||
controller method. Use a composite interface if you need to activate multiple views.
|
||||
|
||||
|
||||
|
||||
@@ -28,6 +28,12 @@ because, arguably, most controller methods should be mapped to a specific HTTP m
|
||||
using `@RequestMapping`, which, by default, matches to all HTTP methods. At the same time, a
|
||||
`@RequestMapping` is still needed at the class level to express shared mappings.
|
||||
|
||||
NOTE: `@RequestMapping` cannot be used in conjunction with other `@RequestMapping`
|
||||
annotations that are declared on the same element (class, interface, or method). If
|
||||
multiple `@RequestMapping` annotations are detected on the same element, a warning will
|
||||
be logged, and only the first mapping will be used. This also applies to composed
|
||||
`@RequestMapping` annotations such as `@GetMapping`, `@PostMapping`, etc.
|
||||
|
||||
The following example uses type and method level mappings:
|
||||
|
||||
[tabs]
|
||||
@@ -436,8 +442,14 @@ attributes with a narrower, more specific purpose.
|
||||
`@GetMapping`, `@PostMapping`, `@PutMapping`, `@DeleteMapping`, and `@PatchMapping` are
|
||||
examples of composed annotations. They are provided, because, arguably, most
|
||||
controller methods should be mapped to a specific HTTP method versus using `@RequestMapping`,
|
||||
which, by default, matches to all HTTP methods. If you need an example of composed
|
||||
annotations, look at how those are declared.
|
||||
which, by default, matches to all HTTP methods. If you need an example of how to implement
|
||||
a composed annotation, look at how those are declared.
|
||||
|
||||
NOTE: `@RequestMapping` cannot be used in conjunction with other `@RequestMapping`
|
||||
annotations that are declared on the same element (class, interface, or method). If
|
||||
multiple `@RequestMapping` annotations are detected on the same element, a warning will
|
||||
be logged, and only the first mapping will be used. This also applies to composed
|
||||
`@RequestMapping` annotations such as `@GetMapping`, `@PostMapping`, etc.
|
||||
|
||||
Spring WebFlux also supports custom request mapping attributes with custom request matching
|
||||
logic. This is a more advanced option that requires sub-classing
|
||||
@@ -511,10 +523,17 @@ Kotlin::
|
||||
== `@HttpExchange`
|
||||
[.small]#xref:web/webmvc/mvc-controller/ann-requestmapping.adoc#mvc-ann-httpexchange-annotation[See equivalent in the Servlet stack]#
|
||||
|
||||
As an alternative to `@RequestMapping`, you can also handle requests with `@HttpExchange`
|
||||
methods. Such methods are declared on an
|
||||
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`.
|
||||
While the main purpose of `@HttpExchange` is to abstract HTTP client code with a
|
||||
generated proxy, the
|
||||
xref:integration/rest-clients.adoc#rest-http-interface[HTTP Interface] on which
|
||||
such annotations are placed is a contract neutral to client vs server use.
|
||||
In addition to simplifying client code, there are also cases where an HTTP Interface
|
||||
may be a convenient way for servers to expose their API for client access. This leads
|
||||
to increased coupling between client and server and is often not a good choice,
|
||||
especially for public API's, but may be exactly the goal for an internal API.
|
||||
It is an approach commonly used in Spring Cloud, and it is why `@HttpExchange` is
|
||||
supported as an alternative to `@RequestMapping` for server side handling in
|
||||
controller classes.
|
||||
|
||||
For example:
|
||||
|
||||
@@ -524,16 +543,23 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@RestController
|
||||
@HttpExchange("/persons")
|
||||
class PersonController {
|
||||
interface PersonService {
|
||||
|
||||
@GetExchange("/{id}")
|
||||
Person getPerson(@PathVariable Long id);
|
||||
|
||||
@PostExchange
|
||||
void add(@RequestBody Person person);
|
||||
}
|
||||
|
||||
@RestController
|
||||
class PersonController implements PersonService {
|
||||
|
||||
public Person getPerson(@PathVariable Long id) {
|
||||
// ...
|
||||
}
|
||||
|
||||
@PostExchange
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public void add(@RequestBody Person person) {
|
||||
// ...
|
||||
@@ -545,30 +571,38 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@RestController
|
||||
@HttpExchange("/persons")
|
||||
class PersonController {
|
||||
interface PersonService {
|
||||
|
||||
@GetExchange("/{id}")
|
||||
fun getPerson(@PathVariable id: Long): Person {
|
||||
fun getPerson(@PathVariable id: Long): Person
|
||||
|
||||
@PostExchange
|
||||
fun add(@RequestBody person: Person)
|
||||
}
|
||||
|
||||
@RestController
|
||||
class PersonController : PersonService {
|
||||
|
||||
override fun getPerson(@PathVariable id: Long): Person {
|
||||
// ...
|
||||
}
|
||||
|
||||
@PostExchange
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
fun add(@RequestBody person: Person) {
|
||||
override 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
|
||||
`@HttpExchange` and `@RequestMapping` have differences.
|
||||
`@RequestMapping` can map to any number of requests by path patterns, HTTP methods,
|
||||
and more, while `@HttpExchange` declares a single endpoint with a concrete HTTP method,
|
||||
path, and content types.
|
||||
|
||||
For method parameters and returns values, generally, `@HttpExchange` supports a
|
||||
subset of the method parameters that `@RequestMapping` does. Notably, it excludes any
|
||||
server-side specific parameter types. For details, see the list for
|
||||
xref:integration/rest-clients.adoc#rest-http-interface-method-parameters[@HttpExchange] and
|
||||
xref:web/webflux/controller/ann-methods/arguments.adoc[@RequestMapping].
|
||||
|
||||
@@ -8,22 +8,23 @@ Spring WebFlux has built-in xref:core/validation/validator.adoc[Validation] supp
|
||||
xref:core/validation/beanvalidation.adoc[Java Bean Validation].
|
||||
The validation support works on two levels.
|
||||
|
||||
First, method parameters such as
|
||||
First, resolvers for
|
||||
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.
|
||||
xref:web/webflux/controller/ann-methods/multipart-forms.adoc[@RequestPart] method
|
||||
parameters perform validation if the parameter has Jakarta's `@Valid` or Spring's
|
||||
`@Validated` annotation, and raise `MethodArgumentNotValidException` if necessary.
|
||||
Alternatively, you can handle the errors in the controller method by adding an
|
||||
`Errors` or `BindingResult` method parameter immediately after the validated one.
|
||||
|
||||
Second, if {bean-validation-site}[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.
|
||||
Second, if {bean-validation-site}[Java Bean Validation] is present _AND_ any method
|
||||
parameter has `@Constraint` annotations, then method validation is applied instead,
|
||||
raising `HandlerMethodValidationException` if necessary. For this case you can still add
|
||||
an `Errors` or `BindingResult` method parameter to handle validation errors within the
|
||||
controller method, but if other method arguments have validation errors then
|
||||
`HandlerMethodValidationException` is raised instead. Method validation can apply
|
||||
to the return value if the method is annotated with `@Valid` or with `@Constraint`
|
||||
annotations.
|
||||
|
||||
You can configure a `Validator` globally through the
|
||||
xref:web/webflux/config.adoc#webflux-config-validation[WebMvc config], or locally
|
||||
|
||||
@@ -760,6 +760,73 @@ Kotlin::
|
||||
======
|
||||
|
||||
|
||||
[[webmvc-fn-serving-resources]]
|
||||
== Serving Resources
|
||||
|
||||
WebMvc.fn provides built-in support for serving resources.
|
||||
|
||||
NOTE: In addition to the capabilities described below, it is possible to implement even more flexible resource handling thanks to
|
||||
{spring-framework-api}++/web/servlet/function/RouterFunctions.html#resources(java.util.function.Function)++[`RouterFunctions#resource(java.util.function.Function)`].
|
||||
|
||||
[[webmvc-fn-resource]]
|
||||
=== Redirecting to a resource
|
||||
|
||||
It is possible to redirect requests matching a specified predicate to a resource. This can be useful, for example,
|
||||
for handling redirects in Single Page Applications.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ClassPathResource index = new ClassPathResource("static/index.html");
|
||||
List<String> extensions = Arrays.asList("js", "css", "ico", "png", "jpg", "gif");
|
||||
RequestPredicate spaPredicate = path("/api/**").or(path("/error")).or(pathExtension(extensions::contains)).negate();
|
||||
RouterFunction<ServerResponse> redirectToIndex = route()
|
||||
.resource(spaPredicate, index)
|
||||
.build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val redirectToIndex = router {
|
||||
val index = ClassPathResource("static/index.html")
|
||||
val extensions = listOf("js", "css", "ico", "png", "jpg", "gif")
|
||||
val spaPredicate = !(path("/api/**") or path("/error") or
|
||||
pathExtension(extensions::contains))
|
||||
resource(spaPredicate, index)
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[[webmvc-fn-resources]]
|
||||
=== Serving resources from a root location
|
||||
|
||||
It is also possible to route requests that match a given pattern to resources relative to a given root location.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
Resource location = new FileSystemResource("public-resources/");
|
||||
RouterFunction<ServerResponse> resources = RouterFunctions.resources("/resources/**", location);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val location = FileSystemResource("public-resources/")
|
||||
val resources = router { resources("/resources/**", location) }
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[webmvc-fn-running]]
|
||||
== Running a Server
|
||||
[.small]#xref:web/webflux-functional.adoc#webflux-fn-running[See equivalent in the Reactive stack]#
|
||||
|
||||
@@ -36,7 +36,7 @@ to render the response content as XML. If the model contains multiple entries, y
|
||||
explicitly set the object to be serialized by using the `modelKey` bean property. If the
|
||||
model contains a single entry, it is serialized automatically.
|
||||
|
||||
You can customized XML mapping as needed by using JAXB or Jackson's provided
|
||||
You can customize XML mapping as needed by using JAXB or Jackson's provided
|
||||
annotations. When you need further control, you can inject a custom `XmlMapper`
|
||||
through the `ObjectMapper` property, for cases where custom XML
|
||||
you need to provide serializers and deserializers for specific types.
|
||||
|
||||
@@ -683,7 +683,7 @@ the HTML would be as follows:
|
||||
----
|
||||
|
||||
What if we want to display the entire list of errors for a given page? The next example
|
||||
shows that the `errors` tag also supports some basic wildcarding functionality.
|
||||
shows that the `errors` tag also supports some basic wildcard functionality.
|
||||
|
||||
* `path="{asterisk}"`: Displays all errors.
|
||||
* `path="lastName"`: Displays all errors associated with the `lastName` field.
|
||||
|
||||
@@ -78,8 +78,9 @@ it does the same, but it also compares the computed value against the `If-None-M
|
||||
request header and, if the two are equal, returns a 304 (NOT_MODIFIED).
|
||||
|
||||
This strategy saves network bandwidth but not CPU, as the full response must be computed for each request.
|
||||
State-changing HTTP methods and other HTTP conditional request headers such as `If-Match` and `If-Unmodified-Since` are outside the scope of this filter.
|
||||
Other strategies at the controller level can avoid the computation and have a broader support for HTTP conditional requests.
|
||||
State-changing HTTP methods and other HTTP conditional request headers such as `If-Match` and
|
||||
`If-Unmodified-Since` are outside the scope of this filter. Other strategies at the controller level
|
||||
can avoid the computation and have a broader support for HTTP conditional requests.
|
||||
See xref:web/webmvc/mvc-caching.adoc[HTTP Caching].
|
||||
|
||||
This filter has a `writeWeakETag` parameter that configures the filter to write weak ETags
|
||||
|
||||
@@ -500,8 +500,8 @@ The MVC configuration exposes the following options for asynchronous request pro
|
||||
|
||||
You can configure the following:
|
||||
|
||||
* Default timeout value for async requests, which if not set, depends
|
||||
on the underlying Servlet container.
|
||||
* The default timeout value for async requests depends
|
||||
on the underlying Servlet container, unless it is set explicitly.
|
||||
* `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.
|
||||
|
||||
@@ -79,9 +79,11 @@ message code arguments for the "detail" field. `ResponseEntityExceptionHandler`
|
||||
these through a xref:core/beans/context-introduction.adoc#context-functionality-messagesource[MessageSource]
|
||||
and updates the corresponding `ProblemDetail` fields accordingly.
|
||||
|
||||
The default strategy for message codes follows the pattern:
|
||||
The default strategy for message codes is as follows:
|
||||
|
||||
`problemDetail.[type|title|detail].[fully qualified exception class name]`
|
||||
* "type": `problemDetail.type.[fully qualified exception class name]`
|
||||
* "title": `problemDetail.title.[fully qualified exception class name]`
|
||||
* "detail": `problemDetail.[fully qualified exception class name][suffix]`
|
||||
|
||||
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
|
||||
@@ -136,7 +138,7 @@ Message codes and arguments for each error are also resolved via `MessageSource`
|
||||
| `MethodArgumentNotValidException`
|
||||
| (default)
|
||||
| `+{0}+` the list of global errors, `+{1}+` the list of field errors.
|
||||
Message codes and arguments for each error are also resolvedvia `MessageSource`.
|
||||
Message codes and arguments for each error are also resolved via `MessageSource`.
|
||||
|
||||
| `MissingRequestHeaderException`
|
||||
| (default)
|
||||
|
||||
+56
-27
@@ -30,6 +30,12 @@ arguably, most controller methods should be mapped to a specific HTTP method ver
|
||||
using `@RequestMapping`, which, by default, matches to all HTTP methods.
|
||||
A `@RequestMapping` is still needed at the class level to express shared mappings.
|
||||
|
||||
NOTE: `@RequestMapping` cannot be used in conjunction with other `@RequestMapping`
|
||||
annotations that are declared on the same element (class, interface, or method). If
|
||||
multiple `@RequestMapping` annotations are detected on the same element, a warning will
|
||||
be logged, and only the first mapping will be used. This also applies to composed
|
||||
`@RequestMapping` annotations such as `@GetMapping`, `@PostMapping`, etc.
|
||||
|
||||
The following example has type and method level mappings:
|
||||
|
||||
[tabs]
|
||||
@@ -462,11 +468,6 @@ transparently for request mapping. Controller methods do not need to change.
|
||||
A response wrapper, applied in `jakarta.servlet.http.HttpServlet`, ensures a `Content-Length`
|
||||
header is set to the number of bytes written (without actually writing to the response).
|
||||
|
||||
`@GetMapping` (and `@RequestMapping(method=HttpMethod.GET)`) are implicitly mapped to
|
||||
and support HTTP HEAD. An HTTP HEAD request is processed as if it were HTTP GET except
|
||||
that, instead of writing the body, the number of bytes are counted and the `Content-Length`
|
||||
header is set.
|
||||
|
||||
By default, HTTP OPTIONS is handled by setting the `Allow` response header to the list of HTTP
|
||||
methods listed in all `@RequestMapping` methods that have matching URL patterns.
|
||||
|
||||
@@ -491,8 +492,14 @@ attributes with a narrower, more specific purpose.
|
||||
`@GetMapping`, `@PostMapping`, `@PutMapping`, `@DeleteMapping`, and `@PatchMapping` are
|
||||
examples of composed annotations. They are provided because, arguably, most
|
||||
controller methods should be mapped to a specific HTTP method versus using `@RequestMapping`,
|
||||
which, by default, matches to all HTTP methods. If you need an example of composed
|
||||
annotations, look at how those are declared.
|
||||
which, by default, matches to all HTTP methods. If you need an example of how to implement
|
||||
a composed annotation, look at how those are declared.
|
||||
|
||||
NOTE: `@RequestMapping` cannot be used in conjunction with other `@RequestMapping`
|
||||
annotations that are declared on the same element (class, interface, or method). If
|
||||
multiple `@RequestMapping` annotations are detected on the same element, a warning will
|
||||
be logged, and only the first mapping will be used. This also applies to composed
|
||||
`@RequestMapping` annotations such as `@GetMapping`, `@PostMapping`, etc.
|
||||
|
||||
Spring MVC also supports custom request-mapping attributes with custom request-matching
|
||||
logic. This is a more advanced option that requires subclassing
|
||||
@@ -562,10 +569,17 @@ Kotlin::
|
||||
== `@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`.
|
||||
While the main purpose of `@HttpExchange` is to abstract HTTP client code with a
|
||||
generated proxy, the
|
||||
xref:integration/rest-clients.adoc#rest-http-interface[HTTP Interface] on which
|
||||
such annotations are placed is a contract neutral to client vs server use.
|
||||
In addition to simplifying client code, there are also cases where an HTTP Interface
|
||||
may be a convenient way for servers to expose their API for client access. This leads
|
||||
to increased coupling between client and server and is often not a good choice,
|
||||
especially for public API's, but may be exactly the goal for an internal API.
|
||||
It is an approach commonly used in Spring Cloud, and it is why `@HttpExchange` is
|
||||
supported as an alternative to `@RequestMapping` for server side handling in
|
||||
controller classes.
|
||||
|
||||
For example:
|
||||
|
||||
@@ -575,16 +589,23 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@RestController
|
||||
@HttpExchange("/persons")
|
||||
class PersonController {
|
||||
interface PersonService {
|
||||
|
||||
@GetExchange("/{id}")
|
||||
Person getPerson(@PathVariable Long id);
|
||||
|
||||
@PostExchange
|
||||
void add(@RequestBody Person person);
|
||||
}
|
||||
|
||||
@RestController
|
||||
class PersonController implements PersonService {
|
||||
|
||||
public Person getPerson(@PathVariable Long id) {
|
||||
// ...
|
||||
}
|
||||
|
||||
@PostExchange
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public void add(@RequestBody Person person) {
|
||||
// ...
|
||||
@@ -596,30 +617,38 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@RestController
|
||||
@HttpExchange("/persons")
|
||||
class PersonController {
|
||||
interface PersonService {
|
||||
|
||||
@GetExchange("/{id}")
|
||||
fun getPerson(@PathVariable id: Long): Person {
|
||||
fun getPerson(@PathVariable id: Long): Person
|
||||
|
||||
@PostExchange
|
||||
fun add(@RequestBody person: Person)
|
||||
}
|
||||
|
||||
@RestController
|
||||
class PersonController : PersonService {
|
||||
|
||||
override fun getPerson(@PathVariable id: Long): Person {
|
||||
// ...
|
||||
}
|
||||
|
||||
@PostExchange
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
fun add(@RequestBody person: Person) {
|
||||
override 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
|
||||
`@HttpExchange` and `@RequestMapping` have differences.
|
||||
`@RequestMapping` can map to any number of requests by path patterns, HTTP methods,
|
||||
and more, while `@HttpExchange` declares a single endpoint with a concrete HTTP method,
|
||||
path, and content types.
|
||||
|
||||
For method parameters and returns values, generally, `@HttpExchange` supports a
|
||||
subset of the method parameters that `@RequestMapping` does. Notably, it excludes any
|
||||
server-side specific parameter types. For details, see the list for
|
||||
xref:integration/rest-clients.adoc#rest-http-interface-method-parameters[@HttpExchange] and
|
||||
xref:web/webmvc/mvc-controller/ann-methods/arguments.adoc[@RequestMapping].
|
||||
|
||||
@@ -8,23 +8,23 @@ Spring MVC has built-in xref:core/validation/validator.adoc[Validation] support
|
||||
xref:core/validation/beanvalidation.adoc[Java Bean Validation].
|
||||
The validation support works on two levels.
|
||||
|
||||
First, method parameters such as
|
||||
First, resolvers for
|
||||
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.
|
||||
xref:web/webmvc/mvc-controller/ann-methods/multipart-forms.adoc[@RequestPart] method
|
||||
parameters perform validation if the parameter has Jakarta's `@Valid` or Spring's
|
||||
`@Validated` annotation, and raise `MethodArgumentNotValidException` if necessary.
|
||||
Alternatively, you can handle the errors in the controller method by adding an
|
||||
`Errors` or `BindingResult` method parameter immediately after the validated one.
|
||||
|
||||
Second, if {bean-validation-site}[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.
|
||||
Second, if {bean-validation-site}[Java Bean Validation] is present _AND_ any method
|
||||
parameter has `@Constraint` annotations, then method validation is applied instead,
|
||||
raising `HandlerMethodValidationException` if necessary. For this case you can still add
|
||||
an `Errors` or `BindingResult` method parameter to handle validation errors within the
|
||||
controller method, but if other method arguments have validation errors then
|
||||
`HandlerMethodValidationException` is raised instead. Method validation can apply
|
||||
to the return value if the method is annotated with `@Valid` or with `@Constraint`
|
||||
annotations.
|
||||
|
||||
You can configure a `Validator` globally through the
|
||||
xref:web/webmvc/mvc-config/validation.adoc[WebMvc config], or locally through an
|
||||
@@ -109,4 +109,4 @@ Kotlin::
|
||||
}
|
||||
})
|
||||
----
|
||||
======
|
||||
======
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ The next example uses server-side configuration to register a custom authenticat
|
||||
interceptor. Note that an interceptor needs only to authenticate and set
|
||||
the user header on the CONNECT `Message`. Spring notes and saves the authenticated
|
||||
user and associate it with subsequent STOMP messages on the same session. The following
|
||||
example shows how register a custom authentication interceptor:
|
||||
example shows how to register a custom authentication interceptor:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -24,7 +24,7 @@ import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class SpellCheckServiceTests {
|
||||
class SpellCheckServiceTests {
|
||||
|
||||
// tag::hintspredicates[]
|
||||
@Test
|
||||
|
||||
@@ -7,32 +7,33 @@ javaPlatform {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(platform("com.fasterxml.jackson:jackson-bom:2.15.2"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.12.1"))
|
||||
api(platform("io.netty:netty-bom:4.1.101.Final"))
|
||||
api(platform("com.fasterxml.jackson:jackson-bom:2.15.4"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.12.5"))
|
||||
api(platform("io.netty:netty-bom:4.1.108.Final"))
|
||||
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
|
||||
api(platform("io.projectreactor:reactor-bom:2023.0.1"))
|
||||
api(platform("io.projectreactor:reactor-bom:2023.0.5"))
|
||||
api(platform("io.rsocket:rsocket-bom:1.1.3"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.16"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.20"))
|
||||
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:12.0.4"))
|
||||
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.3"))
|
||||
api(platform("org.assertj:assertj-bom:3.25.3"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:12.0.7"))
|
||||
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.7"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.0"))
|
||||
api(platform("org.junit:junit-bom:5.10.1"))
|
||||
api(platform("org.mockito:mockito-bom:5.8.0"))
|
||||
api(platform("org.junit:junit-bom:5.10.2"))
|
||||
api(platform("org.mockito:mockito-bom:5.11.0"))
|
||||
|
||||
constraints {
|
||||
api("com.fasterxml:aalto-xml:1.3.2")
|
||||
api("com.fasterxml.woodstox:woodstox-core:6.5.1")
|
||||
api("com.fasterxml.woodstox:woodstox-core:6.6.2")
|
||||
api("com.github.ben-manes.caffeine:caffeine:3.1.8")
|
||||
api("com.github.librepdf:openpdf:1.3.33")
|
||||
api("com.github.librepdf:openpdf:1.3.43")
|
||||
api("com.google.code.findbugs:findbugs:3.0.1")
|
||||
api("com.google.code.findbugs:jsr305:3.0.2")
|
||||
api("com.google.code.gson:gson:2.10.1")
|
||||
api("com.google.protobuf:protobuf-java-util:3.25.0")
|
||||
api("com.google.protobuf:protobuf-java-util:3.25.3")
|
||||
api("com.h2database:h2:2.2.224")
|
||||
api("com.jayway.jsonpath:json-path:2.8.0")
|
||||
api("com.jayway.jsonpath:json-path:2.9.0")
|
||||
api("com.rometools:rome:1.19.0")
|
||||
api("com.squareup.okhttp3:mockwebserver:3.14.9")
|
||||
api("com.squareup.okhttp3:okhttp:3.14.9")
|
||||
@@ -41,11 +42,11 @@ dependencies {
|
||||
api("com.sun.xml.bind:jaxb-core:3.0.2")
|
||||
api("com.sun.xml.bind:jaxb-impl:3.0.2")
|
||||
api("com.sun.xml.bind:jaxb-xjc:3.0.2")
|
||||
api("com.thoughtworks.qdox:qdox:2.0.3")
|
||||
api("com.thoughtworks.qdox:qdox:2.1.0")
|
||||
api("com.thoughtworks.xstream:xstream:1.4.20")
|
||||
api("commons-io:commons-io:2.15.0")
|
||||
api("de.bechte.junit:junit-hierarchicalcontextrunner:4.12.2")
|
||||
api("io.micrometer:context-propagation:1.1.0")
|
||||
api("io.micrometer:context-propagation:1.1.1")
|
||||
api("io.mockk:mockk:1.13.4")
|
||||
api("io.projectreactor.netty:reactor-netty5-http:2.0.0-M3")
|
||||
api("io.projectreactor.tools:blockhound:1.0.8.RELEASE")
|
||||
@@ -54,9 +55,9 @@ dependencies {
|
||||
api("io.r2dbc:r2dbc-spi:1.0.0.RELEASE")
|
||||
api("io.reactivex.rxjava3:rxjava:3.1.8")
|
||||
api("io.smallrye.reactive:mutiny:1.10.0")
|
||||
api("io.undertow:undertow-core:2.3.10.Final")
|
||||
api("io.undertow:undertow-servlet:2.3.10.Final")
|
||||
api("io.undertow:undertow-websockets-jsr:2.3.10.Final")
|
||||
api("io.undertow:undertow-core:2.3.12.Final")
|
||||
api("io.undertow:undertow-servlet:2.3.12.Final")
|
||||
api("io.undertow:undertow-websockets-jsr:2.3.12.Final")
|
||||
api("io.vavr:vavr:0.10.4")
|
||||
api("jakarta.activation:jakarta.activation-api:2.0.1")
|
||||
api("jakarta.annotation:jakarta.annotation-api:2.0.0")
|
||||
@@ -99,23 +100,22 @@ dependencies {
|
||||
api("org.apache.derby:derby:10.16.1.1")
|
||||
api("org.apache.derby:derbyclient:10.16.1.1")
|
||||
api("org.apache.derby:derbytools:10.16.1.1")
|
||||
api("org.apache.httpcomponents.client5:httpclient5:5.2.1")
|
||||
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.2.3")
|
||||
api("org.apache.poi:poi-ooxml:5.2.4")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.16")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.16")
|
||||
api("org.apache.tomcat:tomcat-util:10.1.16")
|
||||
api("org.apache.tomcat:tomcat-websocket:10.1.16")
|
||||
api("org.aspectj:aspectjrt:1.9.20.1")
|
||||
api("org.aspectj:aspectjtools:1.9.20.1")
|
||||
api("org.aspectj:aspectjweaver:1.9.20.1")
|
||||
api("org.assertj:assertj-core:3.24.2")
|
||||
api("org.apache.httpcomponents.client5:httpclient5:5.3.1")
|
||||
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.2.4")
|
||||
api("org.apache.poi:poi-ooxml:5.2.5")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.20")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.20")
|
||||
api("org.apache.tomcat:tomcat-util:10.1.20")
|
||||
api("org.apache.tomcat:tomcat-websocket:10.1.20")
|
||||
api("org.aspectj:aspectjrt:1.9.22")
|
||||
api("org.aspectj:aspectjtools:1.9.22")
|
||||
api("org.aspectj:aspectjweaver:1.9.22")
|
||||
api("org.awaitility:awaitility:4.2.0")
|
||||
api("org.bouncycastle:bcpkix-jdk18on:1.72")
|
||||
api("org.codehaus.jettison:jettison:1.5.4")
|
||||
api("org.crac:crac:1.4.0")
|
||||
api("org.dom4j:dom4j:2.1.4")
|
||||
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.1")
|
||||
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.3")
|
||||
api("org.eclipse.persistence:org.eclipse.persistence.jpa:3.0.4")
|
||||
api("org.eclipse:yasson:2.0.4")
|
||||
api("org.ehcache:ehcache:3.10.8")
|
||||
@@ -130,8 +130,8 @@ dependencies {
|
||||
api("org.hibernate:hibernate-validator:7.0.5.Final")
|
||||
api("org.hsqldb:hsqldb:2.7.2")
|
||||
api("org.javamoney:moneta:1.4.2")
|
||||
api("org.jruby:jruby:9.4.5.0")
|
||||
api("org.junit.support:testng-engine:1.0.4")
|
||||
api("org.jruby:jruby:9.4.6.0")
|
||||
api("org.junit.support:testng-engine:1.0.5")
|
||||
api("org.mozilla:rhino:1.7.14")
|
||||
api("org.ogce:xpp3:1.1.6")
|
||||
api("org.python:jython-standalone:2.7.3")
|
||||
@@ -139,8 +139,8 @@ dependencies {
|
||||
api("org.seleniumhq.selenium:htmlunit-driver:2.70.0")
|
||||
api("org.seleniumhq.selenium:selenium-java:3.141.59")
|
||||
api("org.skyscreamer:jsonassert:1.5.1")
|
||||
api("org.slf4j:slf4j-api:2.0.9")
|
||||
api("org.testng:testng:7.8.0")
|
||||
api("org.slf4j:slf4j-api:2.0.12")
|
||||
api("org.testng:testng:7.9.0")
|
||||
api("org.webjars:underscorejs:1.8.3")
|
||||
api("org.webjars:webjars-locator-core:0.55")
|
||||
api("org.xmlunit:xmlunit-assertj:2.9.1")
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
version=6.1.2
|
||||
version=6.1.6
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
org.gradle.parallel=true
|
||||
|
||||
kotlinVersion=1.9.21
|
||||
kotlinVersion=1.9.22
|
||||
|
||||
kotlin.jvm.target.validation.mode=ignore
|
||||
kotlin.stdlib.default.dependency=false
|
||||
|
||||
@@ -8,8 +8,9 @@ apply plugin: 'me.champeau.jmh'
|
||||
apply from: "$rootDir/gradle/publications.gradle"
|
||||
|
||||
dependencies {
|
||||
jmh 'org.openjdk.jmh:jmh-core:1.36'
|
||||
jmh 'org.openjdk.jmh:jmh-generator-annprocess:1.36'
|
||||
jmh 'org.openjdk.jmh:jmh-core:1.37'
|
||||
jmh 'org.openjdk.jmh:jmh-generator-annprocess:1.37'
|
||||
jmh 'org.openjdk.jmh:jmh-generator-bytecode:1.37'
|
||||
jmh 'net.sf.jopt-simple:jopt-simple'
|
||||
}
|
||||
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
Vendored
+10
-10
@@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
@@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -75,7 +75,7 @@ class AopNamespaceHandlerScopeIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRequestScoping() throws Exception {
|
||||
void testRequestScoping() {
|
||||
MockHttpServletRequest oldRequest = new MockHttpServletRequest();
|
||||
MockHttpServletRequest newRequest = new MockHttpServletRequest();
|
||||
|
||||
@@ -103,7 +103,7 @@ class AopNamespaceHandlerScopeIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSessionScoping() throws Exception {
|
||||
void testSessionScoping() {
|
||||
MockHttpSession oldSession = new MockHttpSession();
|
||||
MockHttpSession newSession = new MockHttpSession();
|
||||
|
||||
|
||||
+9
-10
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.aop.framework.autoproxy;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
@@ -61,12 +60,12 @@ class AdvisorAutoProxyCreatorIntegrationTests {
|
||||
/**
|
||||
* Return a bean factory with attributes and EnterpriseServices configured.
|
||||
*/
|
||||
protected BeanFactory getBeanFactory() throws IOException {
|
||||
protected BeanFactory getBeanFactory() {
|
||||
return new ClassPathXmlApplicationContext(DEFAULT_CONTEXT, CLASS);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDefaultExclusionPrefix() throws Exception {
|
||||
void testDefaultExclusionPrefix() {
|
||||
DefaultAdvisorAutoProxyCreator aapc = (DefaultAdvisorAutoProxyCreator) getBeanFactory().getBean(ADVISOR_APC_BEAN_NAME);
|
||||
assertThat(aapc.getAdvisorBeanNamePrefix()).isEqualTo((ADVISOR_APC_BEAN_NAME + DefaultAdvisorAutoProxyCreator.SEPARATOR));
|
||||
assertThat(aapc.isUsePrefix()).isFalse();
|
||||
@@ -76,21 +75,21 @@ class AdvisorAutoProxyCreatorIntegrationTests {
|
||||
* If no pointcuts match (no attrs) there should be proxying.
|
||||
*/
|
||||
@Test
|
||||
void testNoProxy() throws Exception {
|
||||
void testNoProxy() {
|
||||
BeanFactory bf = getBeanFactory();
|
||||
Object o = bf.getBean("noSetters");
|
||||
assertThat(AopUtils.isAopProxy(o)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTxIsProxied() throws Exception {
|
||||
void testTxIsProxied() {
|
||||
BeanFactory bf = getBeanFactory();
|
||||
ITestBean test = (ITestBean) bf.getBean("test");
|
||||
assertThat(AopUtils.isAopProxy(test)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRegexpApplied() throws Exception {
|
||||
void testRegexpApplied() {
|
||||
BeanFactory bf = getBeanFactory();
|
||||
ITestBean test = (ITestBean) bf.getBean("test");
|
||||
MethodCounter counter = (MethodCounter) bf.getBean("countingAdvice");
|
||||
@@ -100,7 +99,7 @@ class AdvisorAutoProxyCreatorIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTransactionAttributeOnMethod() throws Exception {
|
||||
void testTransactionAttributeOnMethod() {
|
||||
BeanFactory bf = getBeanFactory();
|
||||
ITestBean test = (ITestBean) bf.getBean("test");
|
||||
|
||||
@@ -166,7 +165,7 @@ class AdvisorAutoProxyCreatorIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProgrammaticRollback() throws Exception {
|
||||
void testProgrammaticRollback() {
|
||||
BeanFactory bf = getBeanFactory();
|
||||
|
||||
Object bean = bf.getBean(TXMANAGER_BEAN_NAME);
|
||||
@@ -250,7 +249,7 @@ class OrderedTxCheckAdvisor extends StaticMethodMatcherPointcutAdvisor implement
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
public void afterPropertiesSet() {
|
||||
setAdvice(new TxCountingBeforeAdvice());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -46,7 +46,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
@EnabledIfRuntimeHintsAgent
|
||||
public class RuntimeHintsAgentTests {
|
||||
class RuntimeHintsAgentTests {
|
||||
|
||||
private static final ClassLoader classLoader = ClassLoader.getSystemClassLoader();
|
||||
|
||||
@@ -58,7 +58,7 @@ public class RuntimeHintsAgentTests {
|
||||
|
||||
|
||||
@BeforeAll
|
||||
public static void classSetup() throws NoSuchMethodException {
|
||||
static void classSetup() throws NoSuchMethodException {
|
||||
defaultConstructor = String.class.getConstructor();
|
||||
toStringMethod = String.class.getMethod("toString");
|
||||
privateGreetMethod = PrivateClass.class.getDeclaredMethod("greet");
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -39,7 +39,7 @@ class ComponentBeanDefinitionParserTests {
|
||||
|
||||
|
||||
@BeforeAll
|
||||
void setUp() throws Exception {
|
||||
void setUp() {
|
||||
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(
|
||||
new ClassPathResource("component-config.xml", ComponentBeanDefinitionParserTests.class));
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -34,7 +34,7 @@ public class ComponentFactoryBean implements FactoryBean<Component> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getObject() throws Exception {
|
||||
public Component getObject() {
|
||||
if (this.children != null && this.children.size() > 0) {
|
||||
for (Component child : children) {
|
||||
this.parent.addComponent(child);
|
||||
|
||||
+1
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -42,7 +42,6 @@ import static org.assertj.core.api.Assertions.assertThatException;
|
||||
* @author Chris Beams
|
||||
* @since 3.1
|
||||
*/
|
||||
@SuppressWarnings("resource")
|
||||
class EnableCachingIntegrationTests {
|
||||
|
||||
@Test
|
||||
|
||||
Vendored
+2
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -87,7 +87,6 @@ import static org.springframework.core.env.EnvironmentSystemIntegrationTests.Con
|
||||
* @author Sam Brannen
|
||||
* @see org.springframework.context.support.EnvironmentIntegrationTests
|
||||
*/
|
||||
@SuppressWarnings("resource")
|
||||
public class EnvironmentSystemIntegrationTests {
|
||||
|
||||
private final ConfigurableEnvironment prodEnv = new StandardEnvironment();
|
||||
@@ -618,7 +617,7 @@ public class EnvironmentSystemIntegrationTests {
|
||||
@Import({DevConfig.class, ProdConfig.class})
|
||||
static class Config {
|
||||
@Bean
|
||||
public EnvironmentAwareBean envAwareBean() {
|
||||
EnvironmentAwareBean envAwareBean() {
|
||||
return new EnvironmentAwareBean();
|
||||
}
|
||||
}
|
||||
|
||||
+6
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -27,6 +27,7 @@ import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.expression.TypeConverter;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Copied from Spring Integration for purposes of reproducing
|
||||
@@ -59,11 +60,9 @@ class BeanFactoryTypeConverter implements TypeConverter, BeanFactoryAware {
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
if (beanFactory instanceof ConfigurableBeanFactory) {
|
||||
Object typeConverter = ((ConfigurableBeanFactory) beanFactory).getTypeConverter();
|
||||
if (typeConverter instanceof SimpleTypeConverter) {
|
||||
delegate = (SimpleTypeConverter) typeConverter;
|
||||
}
|
||||
if (beanFactory instanceof ConfigurableBeanFactory cbf &&
|
||||
cbf.getTypeConverter() instanceof SimpleTypeConverter simpleTypeConverter) {
|
||||
this.delegate = simpleTypeConverter;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +85,6 @@ class BeanFactoryTypeConverter implements TypeConverter, BeanFactoryAware {
|
||||
if (conversionService.canConvert(sourceTypeDescriptor, targetTypeDescriptor)) {
|
||||
return true;
|
||||
}
|
||||
// TODO: what does this mean? This method is not used in SpEL so probably ignorable?
|
||||
Class<?> sourceType = sourceTypeDescriptor.getObjectType();
|
||||
Class<?> targetType = targetTypeDescriptor.getObjectType();
|
||||
return canConvert(sourceType, targetType);
|
||||
@@ -94,7 +92,7 @@ class BeanFactoryTypeConverter implements TypeConverter, BeanFactoryAware {
|
||||
|
||||
@Override
|
||||
public Object convertValue(Object value, TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
if (targetType.getType() == Void.class || targetType.getType() == Void.TYPE) {
|
||||
if (ClassUtils.isVoidType(targetType.getType())) {
|
||||
return null;
|
||||
}
|
||||
if (conversionService.canConvert(sourceType, targetType)) {
|
||||
|
||||
+1
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -51,7 +51,6 @@ import static org.springframework.core.testfixture.TestGroup.LONG_RUNNING;
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.1
|
||||
*/
|
||||
@SuppressWarnings("resource")
|
||||
@EnabledForTestGroups(LONG_RUNNING)
|
||||
class ScheduledAndTransactionalAnnotationIntegrationTests {
|
||||
|
||||
|
||||
+1
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -54,7 +54,6 @@ import static org.assertj.core.api.Assertions.assertThatException;
|
||||
* @author Sam Brannen
|
||||
* @since 3.1
|
||||
*/
|
||||
@SuppressWarnings("resource")
|
||||
class EnableTransactionManagementIntegrationTests {
|
||||
|
||||
@Test
|
||||
|
||||
+2
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -27,14 +27,13 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
/**
|
||||
* Tests proving that regardless the proxy strategy used (JDK interface-based vs. CGLIB
|
||||
* subclass-based), discovery of advice-oriented annotations is consistent.
|
||||
*
|
||||
* <p>
|
||||
* For example, Spring's @Transactional may be declared at the interface or class level,
|
||||
* and whether interface or subclass proxies are used, the @Transactional annotation must
|
||||
* be discovered in a consistent fashion.
|
||||
*
|
||||
* @author Chris Beams
|
||||
*/
|
||||
@SuppressWarnings("resource")
|
||||
class ProxyAnnotationDiscoveryTests {
|
||||
|
||||
@Test
|
||||
|
||||
+18
-2
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.aop.framework.autoproxy
|
||||
|
||||
import kotlinx.coroutines.delay
|
||||
@@ -94,12 +110,12 @@ class AspectJAutoProxyInterceptorKotlinIntegrationTests(
|
||||
open class Echo {
|
||||
|
||||
open fun echo(value: String): String {
|
||||
return value;
|
||||
return value
|
||||
}
|
||||
|
||||
open suspend fun suspendingEcho(value: String): String {
|
||||
delay(1)
|
||||
return value;
|
||||
return value
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user