mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
771 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c04290e9ab | |||
| 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 | |||
| d4406507d0 | |||
| a612518f96 | |||
| ec0ec7a0d6 | |||
| 0970b1dc7a | |||
| a01c6d57bb | |||
| 8d4deca2a6 | |||
| cd64e6676c | |||
| 2acc7c609f | |||
| 409cecfff9 | |||
| 3c2c9ca186 | |||
| 75da9c3c47 | |||
| 952223dcf9 | |||
| 125e2902be | |||
| 4d838c1092 | |||
| c75c0ae2d5 | |||
| c0683cd30b | |||
| 7471fd1dd0 | |||
| 1c58511cb2 | |||
| 8de0fadd09 | |||
| da01e0c6e2 | |||
| b87852612b | |||
| 7adc2f0779 | |||
| 33b28fe5bf | |||
| b85fcb6aec | |||
| 134bb6e31f | |||
| 240a75f313 | |||
| 6dcba4de2c | |||
| 6bb9775309 | |||
| eae53560e4 | |||
| f0abdf2264 | |||
| c7f24efc27 | |||
| 07d2571e0b | |||
| 8c51315cd6 | |||
| e8cd26bbf0 | |||
| 9aded3fcad | |||
| aabe4d0b07 | |||
| 570074259d | |||
| f962211e0a | |||
| 6077c998d1 | |||
| 57b8100a06 | |||
| 7432a96b48 | |||
| a01384068a | |||
| d75a7c3818 | |||
| e2c2268c39 | |||
| b510bc3bab | |||
| b6364e3665 | |||
| f29bfd9769 | |||
| 361dfd1ae4 | |||
| 8704ad98a7 | |||
| c57b7e8418 | |||
| 77b0382a6c | |||
| a7f9da1670 | |||
| b782747472 | |||
| 69bc4e2828 | |||
| e4e2224449 | |||
| d919930d83 | |||
| afc1f37616 | |||
| 748dd94dab | |||
| b3a3b79b44 | |||
| 91b9a75371 | |||
| b78aed99ea | |||
| 2eba3510f7 | |||
| 56afd38148 | |||
| 33e4129155 | |||
| 0717ea5ca5 | |||
| 3b4c7a8906 | |||
| 2e07f9ab33 | |||
| dd23b1d156 | |||
| 753409083d | |||
| e36d035f58 | |||
| 302cdeeee6 | |||
| 61dd9fce73 | |||
| 7a221eb581 | |||
| 2e5d1daeff | |||
| bf6cb7cd89 | |||
| 2c053b34f0 | |||
| 438c3818cc | |||
| f14b122c9c | |||
| 9f305bfaab | |||
| d410872e4f | |||
| 8fe2c780df | |||
| b53ffa3855 | |||
| 25537938d6 | |||
| 9704b809b1 | |||
| 0e6c17f518 | |||
| 1afea0b144 | |||
| 7b95bd72f7 | |||
| fdcea58a53 | |||
| ef4ffa0005 | |||
| 2e3d13331a | |||
| 448e753184 | |||
| 6b53f37030 | |||
| afcd03bddc | |||
| 7b16ef90f1 | |||
| e2852e7355 | |||
| 1a63257b12 | |||
| 66e405525b | |||
| 59815cefce | |||
| 785ad399e9 | |||
| 3f9a809c32 | |||
| c74d60b9fe | |||
| db48813181 | |||
| a596c0e226 | |||
| 462ef95904 | |||
| 52d4b83dba | |||
| aa347e5fe6 | |||
| ceba4162bb | |||
| 21560bccd3 | |||
| 1da40b84e7 | |||
| 6f11716b6f | |||
| 47fe61ef79 | |||
| 8a82da43c9 | |||
| cb60f74556 | |||
| 62b3d7a963 | |||
| b69e5acfe3 | |||
| d71853f105 | |||
| 490b5c77fc | |||
| 8ed04b5dd1 | |||
| 87d37a21aa | |||
| eee2569bff | |||
| dbec3f1fa1 | |||
| e870912fa2 | |||
| 99f50ebeb4 | |||
| cd62dfe3a9 | |||
| 7cdacf3083 | |||
| 0bec8125a4 | |||
| da32e3d39e | |||
| 47cdc7c5f0 | |||
| decb22a93d | |||
| d59b2924d3 | |||
| c05b4ce776 | |||
| 3a53446a2b | |||
| d204dd2dbe | |||
| 0dbb0f5c14 | |||
| e452c2e89c | |||
| 6ea9fdbf77 | |||
| 8ff687b68c | |||
| bf1c179b7f | |||
| 16ac495084 | |||
| a506238ef6 | |||
| 33af98b6d6 | |||
| edfe179291 | |||
| 5f9702b2a4 | |||
| c56c304536 | |||
| 8090a52f5c | |||
| 19bca03aa2 | |||
| 0e6e225fb9 | |||
| 8ca82120e0 | |||
| 9ade52dbe2 | |||
| feef98b73c | |||
| 4a6c3e8f5d | |||
| f77713b7e0 | |||
| dc5bef16b4 | |||
| f3b1f37000 | |||
| df00aafdff | |||
| 7cf124b696 | |||
| 35fcbae8c6 | |||
| c8e6315a67 | |||
| 61be452402 | |||
| 0cbbd3a0d5 | |||
| c92a0bd493 | |||
| 755fd75512 | |||
| f8a40555af | |||
| 64d5e904e8 | |||
| 4e2d357318 | |||
| 264ec517f2 | |||
| b2e3be10d4 | |||
| 246833329f | |||
| 43700302c6 | |||
| 54ecbb2bc8 | |||
| 9eb2f29d4a | |||
| 97264c77dd | |||
| b17f3bc07b | |||
| a15f472898 | |||
| 0785256b2f | |||
| 95e250d4bd | |||
| dbad9fd208 | |||
| 657b1c6455 | |||
| 23dc5696d9 | |||
| a56bf87476 | |||
| c2745de60c | |||
| 710373d286 | |||
| 0599320bd8 | |||
| 8921be18de | |||
| 6ff75f157b | |||
| 15d9d9d06a | |||
| 894dce7cd8 | |||
| 34031ebea9 | |||
| b2757d9a21 | |||
| 655b4aacb7 | |||
| fb4455b396 | |||
| 677b03c36d | |||
| 85aa4b65dc | |||
| 0b61755ea3 | |||
| 3bcd30e811 | |||
| 121e3540cc | |||
| 0d8dee2878 | |||
| 3a6d0c1d5b | |||
| 16c6376b3f | |||
| 487dbf8140 | |||
| e95f8d2922 | |||
| 52b40ffb03 |
@@ -1,36 +0,0 @@
|
||||
Juergen Hoeller <jhoeller@vmware.com>
|
||||
Juergen Hoeller <jhoeller@vmware.com> <jhoeller@pivotal.io>
|
||||
Juergen Hoeller <jhoeller@vmware.com> <jhoeller@gopivotal.com>
|
||||
Rossen Stoyanchev <rstoyanchev@vmware.com>
|
||||
Rossen Stoyanchev <rstoyanchev@vmware.com> <rstoyanchev@pivotal.io>
|
||||
Rossen Stoyanchev <rstoyanchev@vmware.com> <rstoyanchev@gopivotal.com>
|
||||
Phillip Webb <pwebb@vmware.com>
|
||||
Phillip Webb <pwebb@vmware.com> <pwebb@pivotal.io>
|
||||
Phillip Webb <pwebb@vmware.com> <pwebb@gopivotal.com>
|
||||
Chris Beams <cbeams@vmware.com>
|
||||
Chris Beams <cbeams@vmware.com> <cbeams@pivotal.io>
|
||||
Chris Beams <cbeams@vmware.com> <cbeams@gopivotal.com>
|
||||
Arjen Poutsma <poutsmaa@vmware.com>
|
||||
Arjen Poutsma <poutsmaa@vmware.com> <apoutsma@pivotal.io>
|
||||
Arjen Poutsma <poutsmaa@vmware.com> <apoutsma@gopivotal.com>
|
||||
Arjen Poutsma <poutsmaa@vmware.com> <poutsma@mac.com>
|
||||
Arjen Poutsma <poutsmaa@vmware.com> <apoutsma@vmware.com>
|
||||
Oliver Drotbohm <odrotbohm@vmware.com>
|
||||
Oliver Drotbohm <odrotbohm@vmware.com> <ogierke@vmware.com>
|
||||
Oliver Drotbohm <odrotbohm@vmware.com> <ogierke@pivotal.io>
|
||||
Oliver Drotbohm <odrotbohm@vmware.com> <ogierke@gopivotal.com>
|
||||
Dave Syer <dsyer@vmware.com>
|
||||
Dave Syer <dsyer@vmware.com> <dsyer@pivotal.io>
|
||||
Dave Syer <dsyer@vmware.com> <dsyer@gopivotal.com>
|
||||
Dave Syer <dsyer@vmware.com> <david_syer@hotmail.com>
|
||||
Andy Clement <aclement@vmware.com>
|
||||
Andy Clement <aclement@vmware.com> <aclement@pivotal.io>
|
||||
Andy Clement <aclement@vmware.com> <aclement@gopivotal.com>
|
||||
Andy Clement <aclement@vmware.com> <andrew.clement@gmail.com>
|
||||
Sam Brannen <sbrannen@vmware.com>
|
||||
Sam Brannen <sbrannen@vmware.com> <sbrannen@pivotal.io>
|
||||
Sam Brannen <sbrannen@vmware.com> <sam@sambrannen.com>
|
||||
Simon Basle <sbasle@vmware.com>
|
||||
Simon Baslé <sbasle@vmware.com>
|
||||
<dmitry.katsubo@gmail.com> <dmitry.katsubo@gmai.com>
|
||||
Nick Williams <nicholas@nicholaswilliams.net>
|
||||
@@ -1,3 +1,3 @@
|
||||
# Enable auto-env through the sdkman_auto_env config
|
||||
# Add key=value pairs of SDKs to use below
|
||||
java=17.0.8.1-librca
|
||||
java=17.0.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.0.x?groups=Build") [](https://ge.spring.io/scans?search.rootProjectNames=spring)
|
||||
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [](https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-6.1.x?groups=Build") [](https://ge.spring.io/scans?search.rootProjectNames=spring)
|
||||
|
||||
This is the home of the Spring Framework: the foundation for all [Spring projects](https://spring.io/projects). Collectively the Spring Framework and the family of Spring projects are often referred to simply as "Spring".
|
||||
|
||||
@@ -31,7 +31,7 @@ Information regarding CI builds can be found in the [Spring Framework Concourse
|
||||
|
||||
## Stay in Touch
|
||||
|
||||
Follow [@SpringCentral](https://twitter.com/springcentral), [@SpringFramework](https://twitter.com/springframework), and its [team members](https://twitter.com/springframework/lists/team/members) on Twitter. In-depth articles can be found at [The Spring Blog](https://spring.io/blog/), and releases are announced via our [releases feed](https://spring.io/blog/category/releases).
|
||||
Follow [@SpringCentral](https://twitter.com/springcentral), [@SpringFramework](https://twitter.com/springframework), and its [team members](https://twitter.com/springframework/lists/team/members) on 𝕏. In-depth articles can be found at [The Spring Blog](https://spring.io/blog/), and releases are announced via our [releases feed](https://spring.io/blog/category/releases).
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+4
-7
@@ -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
|
||||
@@ -16,6 +16,8 @@ ext {
|
||||
javaProjects = subprojects.findAll { !it.name.startsWith("framework-") }
|
||||
}
|
||||
|
||||
description = "Spring Framework"
|
||||
|
||||
configure(allprojects) { project ->
|
||||
apply plugin: "org.springframework.build.localdev"
|
||||
group = "org.springframework"
|
||||
@@ -102,7 +104,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/",
|
||||
@@ -116,8 +118,3 @@ configure([rootProject] + javaProjects) { project ->
|
||||
configure(moduleProjects) { project ->
|
||||
apply from: "${rootDir}/gradle/spring-module.gradle"
|
||||
}
|
||||
|
||||
configure(rootProject) {
|
||||
description = "Spring Framework"
|
||||
apply plugin: 'org.springframework.build.api-diff'
|
||||
}
|
||||
|
||||
@@ -22,21 +22,6 @@ but doesn't affect the classpath of dependent projects.
|
||||
This plugin does not provide a `provided` configuration, as the native `compileOnly` and `testCompileOnly`
|
||||
configurations are preferred.
|
||||
|
||||
### API Diff
|
||||
|
||||
This plugin uses the [Gradle JApiCmp](https://github.com/melix/japicmp-gradle-plugin) plugin
|
||||
to generate API Diff reports for each Spring Framework module. This plugin is applied once on the root
|
||||
project and creates tasks in each framework module. Unlike previous versions of this part of the build,
|
||||
there is no need for checking out a specific tag. The plugin will fetch the JARs we want to compare the
|
||||
current working version with. You can generate the reports for all modules or a single module:
|
||||
|
||||
```
|
||||
./gradlew apiDiff -PbaselineVersion=5.1.0.RELEASE
|
||||
./gradlew :spring-core:apiDiff -PbaselineVersion=5.1.0.RELEASE
|
||||
```
|
||||
|
||||
The reports are located under `build/reports/api-diff/$OLDVERSION_to_$NEWVERSION/`.
|
||||
|
||||
|
||||
### RuntimeHints Java Agent
|
||||
|
||||
|
||||
@@ -21,18 +21,13 @@ dependencies {
|
||||
checkstyle "io.spring.javaformat:spring-javaformat-checkstyle:${javaFormatVersion}"
|
||||
implementation "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}"
|
||||
implementation "org.jetbrains.kotlin:kotlin-compiler-embeddable:${kotlinVersion}"
|
||||
implementation "me.champeau.gradle:japicmp-gradle-plugin:0.3.0"
|
||||
implementation "org.gradle:test-retry-gradle-plugin:1.4.1"
|
||||
implementation "org.gradle:test-retry-gradle-plugin:1.5.6"
|
||||
implementation "io.spring.javaformat:spring-javaformat-gradle-plugin:${javaFormatVersion}"
|
||||
implementation "io.spring.nohttp:nohttp-gradle:0.0.11"
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
apiDiffPlugin {
|
||||
id = "org.springframework.build.api-diff"
|
||||
implementationClass = "org.springframework.build.api.ApiDiffPlugin"
|
||||
}
|
||||
conventionsPlugin {
|
||||
id = "org.springframework.build.conventions"
|
||||
implementationClass = "org.springframework.build.ConventionsPlugin"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
org.gradle.caching=true
|
||||
javaFormatVersion=0.0.39
|
||||
javaFormatVersion=0.0.41
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -50,12 +50,12 @@ public class CheckstyleConventions {
|
||||
project.getPlugins().apply(CheckstylePlugin.class);
|
||||
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g"));
|
||||
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
|
||||
checkstyle.setToolVersion("10.12.5");
|
||||
checkstyle.setToolVersion("10.14.1");
|
||||
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
|
||||
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
|
||||
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
|
||||
checkstyleDependencies
|
||||
.add(project.getDependencies().create("io.spring.javaformat:spring-javaformat-checkstyle:" + version));
|
||||
checkstyleDependencies.add(
|
||||
project.getDependencies().create("io.spring.javaformat:spring-javaformat-checkstyle:" + version));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build.api;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import me.champeau.gradle.japicmp.JapicmpPlugin;
|
||||
import me.champeau.gradle.japicmp.JapicmpTask;
|
||||
import org.gradle.api.GradleException;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.artifacts.Dependency;
|
||||
import org.gradle.api.plugins.JavaBasePlugin;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
import org.gradle.jvm.tasks.Jar;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* {@link Plugin} that applies the {@code "japicmp-gradle-plugin"}
|
||||
* and create tasks for all subprojects named {@code "spring-*"}, diffing the public API one by one
|
||||
* and creating the reports in {@code "build/reports/api-diff/$OLDVERSION_to_$NEWVERSION/"}.
|
||||
* <p>{@code "./gradlew apiDiff -PbaselineVersion=5.1.0.RELEASE"} will output the
|
||||
* reports for the API diff between the baseline version and the current one for all modules.
|
||||
* You can limit the report to a single module with
|
||||
* {@code "./gradlew :spring-core:apiDiff -PbaselineVersion=5.1.0.RELEASE"}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class ApiDiffPlugin implements Plugin<Project> {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ApiDiffPlugin.class);
|
||||
|
||||
public static final String TASK_NAME = "apiDiff";
|
||||
|
||||
private static final String BASELINE_VERSION_PROPERTY = "baselineVersion";
|
||||
|
||||
private static final List<String> PACKAGE_INCLUDES = Collections.singletonList("org.springframework.*");
|
||||
|
||||
private static final URI SPRING_MILESTONE_REPOSITORY = URI.create("https://repo.spring.io/milestone");
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
if (project.hasProperty(BASELINE_VERSION_PROPERTY) && project.equals(project.getRootProject())) {
|
||||
project.getPluginManager().apply(JapicmpPlugin.class);
|
||||
project.getPlugins().withType(JapicmpPlugin.class,
|
||||
plugin -> applyApiDiffConventions(project));
|
||||
}
|
||||
}
|
||||
|
||||
private void applyApiDiffConventions(Project project) {
|
||||
String baselineVersion = project.property(BASELINE_VERSION_PROPERTY).toString();
|
||||
project.subprojects(subProject -> {
|
||||
if (subProject.getName().startsWith("spring-")) {
|
||||
createApiDiffTask(baselineVersion, subProject);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void createApiDiffTask(String baselineVersion, Project project) {
|
||||
if (isProjectEligible(project)) {
|
||||
// Add Spring Milestone repository for generating diffs against previous milestones
|
||||
project.getRootProject()
|
||||
.getRepositories()
|
||||
.maven(mavenArtifactRepository -> mavenArtifactRepository.setUrl(SPRING_MILESTONE_REPOSITORY));
|
||||
JapicmpTask apiDiff = project.getTasks().create(TASK_NAME, JapicmpTask.class);
|
||||
apiDiff.setDescription("Generates an API diff report with japicmp");
|
||||
apiDiff.setGroup(JavaBasePlugin.DOCUMENTATION_GROUP);
|
||||
|
||||
apiDiff.setOldClasspath(createBaselineConfiguration(baselineVersion, project));
|
||||
TaskProvider<Jar> jar = project.getTasks().withType(Jar.class).named("jar");
|
||||
apiDiff.setNewArchives(project.getLayout().files(jar.get().getArchiveFile().get().getAsFile()));
|
||||
apiDiff.setNewClasspath(getRuntimeClassPath(project));
|
||||
apiDiff.setPackageIncludes(PACKAGE_INCLUDES);
|
||||
apiDiff.setOnlyModified(true);
|
||||
apiDiff.setIgnoreMissingClasses(true);
|
||||
// Ignore Kotlin metadata annotations since they contain
|
||||
// illegal HTML characters and fail the report generation
|
||||
apiDiff.setAnnotationExcludes(Collections.singletonList("@kotlin.Metadata"));
|
||||
|
||||
apiDiff.setHtmlOutputFile(getOutputFile(baselineVersion, project));
|
||||
|
||||
apiDiff.dependsOn(project.getTasks().getByName("jar"));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isProjectEligible(Project project) {
|
||||
return project.getPlugins().hasPlugin(JavaPlugin.class)
|
||||
&& project.getPlugins().hasPlugin(MavenPublishPlugin.class);
|
||||
}
|
||||
|
||||
private Configuration createBaselineConfiguration(String baselineVersion, Project project) {
|
||||
String baseline = String.join(":",
|
||||
project.getGroup().toString(), project.getName(), baselineVersion);
|
||||
Dependency baselineDependency = project.getDependencies().create(baseline + "@jar");
|
||||
Configuration baselineConfiguration = project.getRootProject().getConfigurations().detachedConfiguration(baselineDependency);
|
||||
try {
|
||||
// eagerly resolve the baseline configuration to check whether this is a new Spring module
|
||||
baselineConfiguration.resolve();
|
||||
return baselineConfiguration;
|
||||
}
|
||||
catch (GradleException exception) {
|
||||
logger.warn("Could not resolve {} - assuming this is a new Spring module.", baseline);
|
||||
}
|
||||
return project.getRootProject().getConfigurations().detachedConfiguration();
|
||||
}
|
||||
|
||||
private Configuration getRuntimeClassPath(Project project) {
|
||||
return project.getConfigurations().getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);
|
||||
}
|
||||
|
||||
private File getOutputFile(String baseLineVersion, Project project) {
|
||||
String buildDirectoryPath = project.getRootProject()
|
||||
.getLayout().getBuildDirectory().getAsFile().get().getAbsolutePath();
|
||||
Path outDir = Paths.get(buildDirectoryPath, "reports", "api-diff",
|
||||
baseLineVersion + "_to_" + project.getRootProject().getVersion());
|
||||
return project.file(outDir.resolve(project.getName() + ".html").toString());
|
||||
}
|
||||
|
||||
}
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
|
||||
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 +51,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-20231004
|
||||
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/19/GPL/openjdk-22-ea+19_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
-1
@@ -3,7 +3,7 @@ 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"
|
||||
|
||||
+10
-8
@@ -121,14 +121,14 @@ resources:
|
||||
access_token: ((github-ci-status-token))
|
||||
branch: ((branch))
|
||||
context: jdk21-build
|
||||
- name: repo-status-jdk22-build
|
||||
- name: repo-status-jdk23-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
|
||||
context: jdk23-build
|
||||
- name: slack-alert
|
||||
type: slack-notification
|
||||
icon: slack
|
||||
@@ -249,13 +249,15 @@ jobs:
|
||||
<<: *slack-fail-params
|
||||
- put: repo-status-jdk21-build
|
||||
params: { state: "success", commit: "git-repo" }
|
||||
- name: jdk22-build
|
||||
- name: jdk23-build
|
||||
serial: true
|
||||
public: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
- put: repo-status-jdk22-build
|
||||
- get: every-morning
|
||||
trigger: false
|
||||
- put: repo-status-jdk23-build
|
||||
params: { state: "pending", commit: "git-repo" }
|
||||
- do:
|
||||
- task: check-project
|
||||
@@ -264,16 +266,16 @@ jobs:
|
||||
privileged: true
|
||||
timeout: ((task-timeout))
|
||||
params:
|
||||
TEST_TOOLCHAIN: 22
|
||||
TEST_TOOLCHAIN: 23
|
||||
<<: *build-project-task-params
|
||||
on_failure:
|
||||
do:
|
||||
- put: repo-status-jdk22-build
|
||||
- put: repo-status-jdk23-build
|
||||
params: { state: "failure", commit: "git-repo" }
|
||||
- put: slack-alert
|
||||
params:
|
||||
<<: *slack-fail-params
|
||||
- put: repo-status-jdk22-build
|
||||
- put: repo-status-jdk23-build
|
||||
params: { state: "success", commit: "git-repo" }
|
||||
- name: stage-milestone
|
||||
serial: true
|
||||
@@ -426,7 +428,7 @@ jobs:
|
||||
|
||||
groups:
|
||||
- name: "builds"
|
||||
jobs: ["build", "jdk21-build", "jdk22-build"]
|
||||
jobs: ["build", "jdk21-build", "jdk23-build"]
|
||||
- name: "releases"
|
||||
jobs: ["stage-milestone", "stage-rc", "stage-release", "promote-milestone", "promote-rc", "promote-release", "create-github-release"]
|
||||
- name: "ci-images"
|
||||
|
||||
@@ -5,6 +5,6 @@ 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 \
|
||||
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Porg.gradle.java.installations.fromEnv=JDK17,JDK21,JDK23 \
|
||||
--no-daemon --max-workers=4 -PdeploymentRepository=${repository} build publishAllPublicationsToDeploymentRepository
|
||||
popd > /dev/null
|
||||
|
||||
@@ -4,6 +4,6 @@ set -e
|
||||
source $(dirname $0)/common.sh
|
||||
|
||||
pushd git-repo > /dev/null
|
||||
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Porg.gradle.java.installations.fromEnv=JDK17,JDK21 \
|
||||
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Porg.gradle.java.installations.fromEnv=JDK17,JDK21,JDK23 \
|
||||
-PmainToolchain=${MAIN_TOOLCHAIN} -PtestToolchain=${TEST_TOOLCHAIN} --no-daemon --max-workers=4 check antora
|
||||
popd > /dev/null
|
||||
|
||||
@@ -27,8 +27,8 @@ javadoc {
|
||||
author = true
|
||||
header = rootProject.description
|
||||
use = true
|
||||
overview = "$rootProject.rootDir/framework-docs/src/docs/api/overview.html"
|
||||
destinationDir = file("${project.buildDir}/docs/javadoc-api")
|
||||
overview = project.relativePath("$rootProject.rootDir/framework-docs/src/docs/api/overview.html")
|
||||
destinationDir = file("$project.docsDir/javadoc-api")
|
||||
splitIndex = true
|
||||
links(rootProject.ext.javadocLinks)
|
||||
addBooleanOption('Xdoclint:syntax,reference', true) // only check syntax and reference with doclint
|
||||
@@ -52,7 +52,7 @@ rootProject.tasks.dokkaHtmlMultiModule.configure {
|
||||
tasks.named("javadoc")
|
||||
}
|
||||
moduleName.set("spring-framework")
|
||||
outputDirectory.set(project.file("$buildDir/docs/kdoc-api"))
|
||||
outputDirectory.set(file("$docsDir/kdoc-api"))
|
||||
includes.from("$rootProject.rootDir/framework-docs/src/docs/api/dokka-overview.md")
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -17,8 +17,11 @@ Applying such optimizations early implies the following restrictions:
|
||||
* The beans defined in your application cannot change at runtime, meaning:
|
||||
** `@Profile`, in particular profile-specific configuration needs to be chosen at build time.
|
||||
** `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]
|
||||
@@ -156,6 +166,7 @@ Java::
|
||||
/**
|
||||
* Bean definitions for {@link DataSourceConfiguration}
|
||||
*/
|
||||
@Generated
|
||||
public class DataSourceConfiguration__BeanDefinitions {
|
||||
/**
|
||||
* Get the bean definition for 'dataSourceConfiguration'
|
||||
@@ -190,6 +201,9 @@ Java::
|
||||
|
||||
NOTE: The exact code generated may differ depending on the exact nature of your bean definitions.
|
||||
|
||||
TIP: Each generated class is annotated with `org.springframework.aot.generate.Generated` to
|
||||
identify them if they need to be excluded, for instance by static analysis tools.
|
||||
|
||||
The generated code above creates bean definitions equivalent to the `@Configuration` class, but in a direct way and without the use of reflection if at all possible.
|
||||
There is a bean definition for `dataSourceConfiguration` and one for `dataSourceBean`.
|
||||
When a `datasource` instance is required, a `BeanInstanceSupplier` is called.
|
||||
@@ -203,11 +217,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:
|
||||
@@ -258,10 +294,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
|
||||
@@ -279,7 +316,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class ClientFactoryBean<T extends AbstractClient> implements FactoryBean<T> {
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
@@ -376,7 +413,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.
|
||||
@@ -411,7 +448,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]]
|
||||
@@ -420,7 +457,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].
|
||||
|
||||
@@ -516,8 +516,8 @@ as the following example shows:
|
||||
[[beans-definition-profiles-default]]
|
||||
=== Default Profile
|
||||
|
||||
The default profile represents the profile that is enabled by default. Consider the
|
||||
following example:
|
||||
The default profile represents the profile that is enabled if no profile is active. Consider
|
||||
the following example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -558,9 +558,9 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
If no profile is active, the `dataSource` is created. You can see this
|
||||
as a way to provide a default definition for one or more beans. If any
|
||||
profile is enabled, the default profile does not apply.
|
||||
If xref:#beans-definition-profiles-enable[no profile is active], the `dataSource` is
|
||||
created. You can see this as a way to provide a default definition for one or more
|
||||
beans. If any profile is enabled, the default profile does not apply.
|
||||
|
||||
The name of the default profile is `default`. You can change the name of
|
||||
the default profile by using `setDefaultProfiles()` on the `Environment` or,
|
||||
|
||||
@@ -592,6 +592,40 @@ Kotlin::
|
||||
|
||||
|
||||
|
||||
[[beans-factory-thread-safety]]
|
||||
=== Thread Safety and Visibility
|
||||
|
||||
The Spring core container publishes created singleton instances in a thread-safe manner,
|
||||
guarding access through a singleton lock and guaranteeing visibility in other threads.
|
||||
|
||||
As a consequence, application-provided bean classes do not have to be concerned about the
|
||||
visibility of their initialization state. Regular configuration fields do not have to be
|
||||
marked as `volatile` as long as they are only mutated during the initialization phase,
|
||||
providing visibility guarantees similar to `final` even for setter-based configuration
|
||||
state that is mutable during that initial phase. If such fields get changed after the
|
||||
bean creation phase and its subsequent initial publication, they need to be declared as
|
||||
`volatile` or guarded by a common lock whenever accessed.
|
||||
|
||||
Note that concurrent access to such configuration state in singleton bean instances,
|
||||
e.g. for controller instances or repository instances, is perfectly thread-safe after
|
||||
such safe initial publication from the container side. This includes common singleton
|
||||
`FactoryBean` instances which are processed within the general singleton lock as well.
|
||||
|
||||
For destruction callbacks, the configuration state remains thread-safe but any runtime
|
||||
state accumulated between initialization and destruction should be kept in thread-safe
|
||||
structures (or in `volatile` fields for simple cases) as per common Java guidelines.
|
||||
|
||||
Deeper `Lifecycle` integration as shown above involves runtime-mutable state such as
|
||||
a `runnable` field which will have to be declared as `volatile`. While the common
|
||||
lifecycle callbacks follow a certain order, e.g. a start callback is guaranteed to
|
||||
only happen after full initialization and a stop callback only after an initial start,
|
||||
there is a special case with the common stop before destroy arrangement: It is strongly
|
||||
recommended that the internal state in any such bean also allows for an immediate
|
||||
destroy callback without a preceding stop since this may happen during an extraordinary
|
||||
shutdown after a cancelled bootstrap or in case of a stop timeout caused by another bean.
|
||||
|
||||
|
||||
|
||||
[[beans-factory-aware]]
|
||||
== `ApplicationContextAware` and `BeanNameAware`
|
||||
|
||||
|
||||
@@ -324,7 +324,6 @@ Kotlin::
|
||||
|
||||
|
||||
|
||||
|
||||
[[beans-factory-scopes-application]]
|
||||
=== Application Scope
|
||||
|
||||
@@ -374,7 +373,6 @@ Kotlin::
|
||||
|
||||
|
||||
|
||||
|
||||
[[beans-factory-scopes-websocket]]
|
||||
=== WebSocket Scope
|
||||
|
||||
@@ -384,7 +382,6 @@ xref:web/websocket/stomp/scope.adoc[WebSocket scope] for more details.
|
||||
|
||||
|
||||
|
||||
|
||||
[[beans-factory-scopes-other-injection]]
|
||||
=== Scoped Beans as Dependencies
|
||||
|
||||
@@ -544,6 +541,19 @@ see xref:core/aop/proxying.adoc[Proxying Mechanisms].
|
||||
|
||||
|
||||
|
||||
[[beans-factory-scopes-injection]]
|
||||
=== Injecting Request/Session References Directly
|
||||
|
||||
As an alternative to factory scopes, a Spring `WebApplicationContext` also supports
|
||||
the injection of `HttpServletRequest`, `HttpServletResponse`, `HttpSession`,
|
||||
`WebRequest` and (if JSF is present) `FacesContext` and `ExternalContext` into
|
||||
Spring-managed beans, simply through type-based autowiring next to regular injection
|
||||
points for other beans. Spring generally injects proxies for such request and session
|
||||
objects which has the advantage of working in singleton beans and serializable beans
|
||||
as well, similar to scoped proxies for factory-scoped beans.
|
||||
|
||||
|
||||
|
||||
[[beans-factory-scopes-custom]]
|
||||
== Custom Scopes
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
[[beans-introduction]]
|
||||
= Introduction to the Spring IoC Container and Beans
|
||||
|
||||
This chapter covers the Spring Framework implementation of the Inversion of Control
|
||||
(IoC) principle. IoC is also known as dependency injection (DI). It is a process whereby
|
||||
objects define their dependencies (that is, the other objects they work with) only through
|
||||
constructor arguments, arguments to a factory method, or properties that are set on the
|
||||
object instance after it is constructed or returned from a factory method. The container
|
||||
This chapter covers the Spring Framework implementation of the Inversion of Control (IoC)
|
||||
principle. Dependency injection (DI) is a specialized form of IoC, whereby objects define
|
||||
their dependencies (that is, the other objects they work with) only through constructor
|
||||
arguments, arguments to a factory method, or properties that are set on the object
|
||||
instance after it is constructed or returned from a factory method. The IoC container
|
||||
then injects those dependencies when it creates the bean. This process is fundamentally
|
||||
the inverse (hence the name, Inversion of Control) of the bean itself
|
||||
controlling the instantiation or location of its dependencies by using direct
|
||||
construction of classes or a mechanism such as the Service Locator pattern.
|
||||
the inverse (hence the name, Inversion of Control) of the bean itself controlling the
|
||||
instantiation or location of its dependencies by using direct construction of classes or
|
||||
a mechanism such as the Service Locator pattern.
|
||||
|
||||
The `org.springframework.beans` and `org.springframework.context` packages are the basis
|
||||
for Spring Framework's IoC container. The
|
||||
|
||||
@@ -3,17 +3,5 @@
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
This section covers how to use annotations in your Java code to configure the Spring
|
||||
container. It includes the following topics:
|
||||
|
||||
* xref:core/beans/java/basic-concepts.adoc[Basic Concepts: `@Bean` and `@Configuration`]
|
||||
* xref:core/beans/java/instantiating-container.adoc[Instantiating the Spring Container by Using `AnnotationConfigApplicationContext`]
|
||||
* xref:core/beans/java/bean-annotation.adoc[Using the `@Bean` Annotation]
|
||||
* xref:core/beans/java/configuration-annotation.adoc[Using the `@Configuration` annotation]
|
||||
* xref:core/beans/java/composing-configuration-classes.adoc[Composing Java-based Configurations]
|
||||
* xref:core/beans/environment.adoc#beans-definition-profiles[Bean Definition Profiles]
|
||||
* xref:core/beans/environment.adoc#beans-property-source-abstraction[`PropertySource` Abstraction]
|
||||
* xref:core/beans/environment.adoc#beans-using-propertysource[Using `@PropertySource`]
|
||||
* xref:core/beans/environment.adoc#beans-placeholder-resolution-in-statements[Placeholder Resolution in Statements]
|
||||
|
||||
|
||||
container.
|
||||
|
||||
|
||||
@@ -1,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"]
|
||||
----
|
||||
|
||||
@@ -2,24 +2,4 @@
|
||||
= Language Reference
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
This section describes how the Spring Expression Language works. It covers the following
|
||||
topics:
|
||||
|
||||
* xref:core/expressions/language-ref/literal.adoc[Literal Expressions]
|
||||
* xref:core/expressions/language-ref/properties-arrays.adoc[Properties, Arrays, Lists, Maps, and Indexers]
|
||||
* xref:core/expressions/language-ref/inline-lists.adoc[Inline Lists]
|
||||
* xref:core/expressions/language-ref/inline-maps.adoc[Inline Maps]
|
||||
* xref:core/expressions/language-ref/array-construction.adoc[Array Construction]
|
||||
* xref:core/expressions/language-ref/methods.adoc[Methods]
|
||||
* xref:core/expressions/language-ref/operators.adoc[Operators]
|
||||
* xref:core/expressions/language-ref/types.adoc[Types]
|
||||
* xref:core/expressions/language-ref/constructors.adoc[Constructors]
|
||||
* xref:core/expressions/language-ref/variables.adoc[Variables]
|
||||
* xref:core/expressions/language-ref/functions.adoc[User-Defined Functions]
|
||||
* xref:core/expressions/language-ref/bean-references.adoc[Bean References]
|
||||
* xref:core/expressions/language-ref/operator-ternary.adoc[Ternary Operator (If-Then-Else)]
|
||||
* xref:core/expressions/language-ref/operator-elvis.adoc[The Elvis Operator]
|
||||
* xref:core/expressions/language-ref/operator-safe-navigation.adoc[Safe Navigation Operator]
|
||||
|
||||
|
||||
|
||||
This section describes how the Spring Expression Language works.
|
||||
|
||||
+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.
|
||||
|
||||
|
||||
|
||||
@@ -1,20 +1,41 @@
|
||||
[[expressions-ref-variables]]
|
||||
= Variables
|
||||
|
||||
You can reference variables in the expression by using the `#variableName` syntax. Variables
|
||||
are set by using the `setVariable` method on `EvaluationContext` implementations.
|
||||
You can reference variables in an expression by using the `#variableName` syntax. Variables
|
||||
are set by using the `setVariable()` method in `EvaluationContext` implementations.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
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]
|
||||
@@ -29,7 +50,7 @@ Java::
|
||||
context.setVariable("newName", "Mike Tesla");
|
||||
|
||||
parser.parseExpression("name = #newName").getValue(context, tesla);
|
||||
System.out.println(tesla.getName()) // "Mike Tesla"
|
||||
System.out.println(tesla.getName()); // "Mike Tesla"
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
@@ -53,8 +74,10 @@ Kotlin::
|
||||
The `#this` variable is always defined and refers to the current evaluation object
|
||||
(against which unqualified references are resolved). The `#root` variable is always
|
||||
defined and refers to the root context object. Although `#this` may vary as components of
|
||||
an expression are evaluated, `#root` always refers to the root. The following examples
|
||||
show how to use the `#this` and `#root` variables:
|
||||
an expression are evaluated, `#root` always refers to the root.
|
||||
|
||||
The following example shows how to use the `#this` variable in conjunction with
|
||||
xref:core/expressions/language-ref/collection-selection.adoc[collection selection].
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -62,40 +85,95 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// create an array of integers
|
||||
List<Integer> primes = new ArrayList<>();
|
||||
primes.addAll(Arrays.asList(2,3,5,7,11,13,17));
|
||||
// Create a list of prime integers.
|
||||
List<Integer> primes = List.of(2, 3, 5, 7, 11, 13, 17);
|
||||
|
||||
// create parser and set variable 'primes' as the array of integers
|
||||
// Create parser and set variable 'primes' as the list of integers.
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataAccess();
|
||||
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
|
||||
context.setVariable("primes", primes);
|
||||
|
||||
// all prime numbers > 10 from the list (using selection ?{...})
|
||||
// evaluates to [11, 13, 17]
|
||||
List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression(
|
||||
"#primes.?[#this>10]").getValue(context);
|
||||
// Select all prime numbers > 10 from the list (using selection ?{...}).
|
||||
String expression = "#primes.?[#this > 10]";
|
||||
|
||||
// Evaluates to a list containing [11, 13, 17].
|
||||
List<Integer> primesGreaterThanTen =
|
||||
parser.parseExpression(expression).getValue(context, List.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// create an array of integers
|
||||
val primes = ArrayList<Int>()
|
||||
primes.addAll(listOf(2, 3, 5, 7, 11, 13, 17))
|
||||
// Create a list of prime integers.
|
||||
val primes = listOf(2, 3, 5, 7, 11, 13, 17)
|
||||
|
||||
// create parser and set variable 'primes' as the array of integers
|
||||
// Create parser and set variable 'primes' as the list of integers.
|
||||
val parser = SpelExpressionParser()
|
||||
val context = SimpleEvaluationContext.forReadOnlyDataAccess()
|
||||
val context = SimpleEvaluationContext.forReadWriteDataBinding().build()
|
||||
context.setVariable("primes", primes)
|
||||
|
||||
// all prime numbers > 10 from the list (using selection ?{...})
|
||||
// evaluates to [11, 13, 17]
|
||||
val primesGreaterThanTen = parser.parseExpression(
|
||||
"#primes.?[#this>10]").getValue(context) as List<Int>
|
||||
// Select all prime numbers > 10 from the list (using selection ?{...}).
|
||||
val expression = "#primes.?[#this > 10]"
|
||||
|
||||
// Evaluates to a list containing [11, 13, 17].
|
||||
val primesGreaterThanTen = parser.parseExpression(expression)
|
||||
.getValue(context) as List<Int>
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows how to use the `#this` and `#root` variables together in
|
||||
conjunction with
|
||||
xref:core/expressions/language-ref/collection-projection.adoc[collection projection].
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// Create parser and evaluation context.
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
|
||||
|
||||
// Create an inventor to use as the root context object.
|
||||
Inventor tesla = new Inventor("Nikola Tesla");
|
||||
tesla.setInventions("Telephone repeater", "Tesla coil transformer");
|
||||
|
||||
// Iterate over all inventions of the Inventor referenced as the #root
|
||||
// object, and generate a list of strings whose contents take the form
|
||||
// "<inventor's name> invented the <invention>." (using projection !{...}).
|
||||
String expression = "#root.inventions.![#root.name + ' invented the ' + #this + '.']";
|
||||
|
||||
// Evaluates to a list containing:
|
||||
// "Nikola Tesla invented the Telephone repeater."
|
||||
// "Nikola Tesla invented the Tesla coil transformer."
|
||||
List<String> results = parser.parseExpression(expression)
|
||||
.getValue(context, tesla, List.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// Create parser and evaluation context.
|
||||
val parser = SpelExpressionParser()
|
||||
val context = SimpleEvaluationContext.forReadWriteDataBinding().build()
|
||||
|
||||
// Create an inventor to use as the root context object.
|
||||
val tesla = Inventor("Nikola Tesla")
|
||||
tesla.setInventions("Telephone repeater", "Tesla coil transformer")
|
||||
|
||||
// Iterate over all inventions of the Inventor referenced as the #root
|
||||
// object, and generate a list of strings whose contents take the form
|
||||
// "<inventor's name> invented the <invention>." (using projection !{...}).
|
||||
val expression = "#root.inventions.![#root.name + ' invented the ' + #this + '.']"
|
||||
|
||||
// Evaluates to a list containing:
|
||||
// "Nikola Tesla invented the Telephone repeater."
|
||||
// "Nikola Tesla invented the Tesla coil transformer."
|
||||
val results = parser.parseExpression(expression)
|
||||
.getValue(context, tesla, List::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -230,6 +230,19 @@ provided you stick to the required connection lookup pattern. Note that JTA does
|
||||
savepoints or custom isolation levels and has a different timeout mechanism but otherwise
|
||||
exposes similar behavior in terms of JDBC resources and JDBC commit/rollback management.
|
||||
|
||||
For JTA-style lazy retrieval of actual resource connections, Spring provides a
|
||||
corresponding `DataSource` proxy class for the target connection pool: see
|
||||
{spring-framework-api}/jdbc/datasource/LazyConnectionDataSourceProxy.html[`LazyConnectionDataSourceProxy`].
|
||||
This is particularly useful for potentially empty transactions without actual statement
|
||||
execution (never fetching an actual resource in such a scenario), and also in front of
|
||||
a routing `DataSource` which means to take the transaction-synchronized read-only flag
|
||||
and/or isolation level into account (e.g. `IsolationLevelDataSourceRouter`).
|
||||
|
||||
`LazyConnectionDataSourceProxy` also provides special support for a read-only connection
|
||||
pool to use during a read-only transaction, avoiding the overhead of switching the JDBC
|
||||
Connection's read-only flag at the beginning and end of every transaction when fetching
|
||||
it from the primary connection pool (which may be costly depending on the JDBC driver).
|
||||
|
||||
NOTE: As of 5.3, Spring provides an extended `JdbcTransactionManager` variant which adds
|
||||
exception translation capabilities on commit/rollback (aligned with `JdbcTemplate`).
|
||||
Where `DataSourceTransactionManager` will only ever throw `TransactionSystemException`
|
||||
|
||||
@@ -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();
|
||||
----
|
||||
|
||||
|
||||
@@ -407,6 +407,12 @@ exposes the Hibernate transaction as a JDBC transaction if you have set up the p
|
||||
`DataSource` for which the transactions are supposed to be exposed through the
|
||||
`dataSource` property of the `HibernateTransactionManager` class.
|
||||
|
||||
For JTA-style lazy retrieval of actual resource connections, Spring provides a
|
||||
corresponding `DataSource` proxy class for the target connection pool: see
|
||||
{spring-framework-api}/jdbc/datasource/LazyConnectionDataSourceProxy.html[`LazyConnectionDataSourceProxy`].
|
||||
This is particularly useful for Hibernate read-only transactions which can often
|
||||
be processed from a local cache rather than hitting the database.
|
||||
|
||||
|
||||
[[orm-hibernate-resources]]
|
||||
== Comparing Container-managed and Locally Defined Resources
|
||||
|
||||
@@ -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]
|
||||
======
|
||||
@@ -506,13 +506,20 @@ if you have not already done so, to get more detailed coverage of Spring's decla
|
||||
The recommended strategy for JPA is local transactions through JPA's native transaction
|
||||
support. Spring's `JpaTransactionManager` provides many capabilities known from local
|
||||
JDBC transactions (such as transaction-specific isolation levels and resource-level
|
||||
read-only optimizations) against any regular JDBC connection pool (no XA requirement).
|
||||
read-only optimizations) against any regular JDBC connection pool, without requiring
|
||||
a JTA transaction coordinator and XA-capable resources.
|
||||
|
||||
Spring JPA also lets a configured `JpaTransactionManager` expose a JPA transaction
|
||||
to JDBC access code that accesses the same `DataSource`, provided that the registered
|
||||
`JpaDialect` supports retrieval of the underlying JDBC `Connection`.
|
||||
Spring provides dialects for the EclipseLink and Hibernate JPA implementations.
|
||||
See the xref:data-access/orm/jpa.adoc#orm-jpa-dialect[next section] for details on the `JpaDialect` mechanism.
|
||||
`JpaDialect` supports retrieval of the underlying JDBC `Connection`. Spring provides
|
||||
dialects for the EclipseLink and Hibernate JPA implementations. See the
|
||||
xref:data-access/orm/jpa.adoc#orm-jpa-dialect[next section] for details on `JpaDialect`.
|
||||
|
||||
For JTA-style lazy retrieval of actual resource connections, Spring provides a
|
||||
corresponding `DataSource` proxy class for the target connection pool: see
|
||||
{spring-framework-api}/jdbc/datasource/LazyConnectionDataSourceProxy.html[`LazyConnectionDataSourceProxy`].
|
||||
This is particularly useful for JPA read-only transactions which can often
|
||||
be processed from a local cache rather than hitting the database.
|
||||
|
||||
|
||||
[[orm-jpa-dialect]]
|
||||
|
||||
@@ -440,12 +440,8 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val tuples: MutableList<Array<Any>> = ArrayList()
|
||||
tuples.add(arrayOf("John", 35))
|
||||
tuples.add(arrayOf("Ann", 50))
|
||||
|
||||
client.sql("SELECT id, name, state FROM table WHERE age IN (:ages)")
|
||||
.bind("tuples", arrayOf(35, 50))
|
||||
.bind("ages", arrayOf(35, 50))
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
+5
@@ -38,6 +38,11 @@ within the method.
|
||||
A reactive transaction managed by `ReactiveTransactionManager` uses the Reactor context
|
||||
instead of thread-local attributes. As a consequence, all participating data access
|
||||
operations need to execute within the same Reactor context in the same reactive pipeline.
|
||||
|
||||
When configured with a `ReactiveTransactionManager`, all transaction-demarcated methods
|
||||
are expected to return a reactive pipeline. Void methods or regular return types need
|
||||
to be associated with a regular `PlatformTransactionManager`, e.g. through the
|
||||
`transactionManager` attribute of the corresponding `@Transactional` declarations.
|
||||
====
|
||||
|
||||
The following image shows a conceptual view of calling a method on a transactional proxy:
|
||||
|
||||
@@ -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
|
||||
|
||||
+6
-5
@@ -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.
|
||||
@@ -19,7 +20,7 @@ been published.
|
||||
|
||||
To create the archive, two additional JVM flags must be specified:
|
||||
|
||||
* `-XX:ArchiveClassesAtExit=app-cds.jsa`: creates the CDS archive on exit
|
||||
* `-XX:ArchiveClassesAtExit=application.jsa`: creates the CDS archive on exit
|
||||
* `-Dspring.context.exit=onRefresh`: starts and then immediately exits your Spring
|
||||
application as described above
|
||||
|
||||
@@ -40,8 +41,8 @@ The base CDS archive can be created by issuing the following command:
|
||||
|
||||
== Using the Archive
|
||||
|
||||
Once the archive is available, add `-XX:SharedArchiveFile=app-cds.jsa` to your startup
|
||||
script to use it, assuming a `app-cds.jsa` file in the working directory.
|
||||
Once the archive is available, add `-XX:SharedArchiveFile=application.jsa` to your startup
|
||||
script to use it, assuming an `application.jsa` file in the working directory.
|
||||
|
||||
To figure out how effective the cache is, you can enable class loading logs by adding
|
||||
an extra attribute: `-Xlog:class+load:file=cds.log`. This creates a `cds.log` with every
|
||||
@@ -15,8 +15,7 @@ Conceptually, checkpoint and restore align with the xref:core/beans/factory-natu
|
||||
|
||||
== On-demand checkpoint/restore of a running application
|
||||
|
||||
A checkpoint can be created on demand, for example using a command like `jcmd application.jar JDK.checkpoint`. Before the creation of the checkpoint, Spring Framework
|
||||
stops all the running beans, giving them a chance to close resources if needed by implementing `Lifecycle.stop`. After restore, the same beans are restarted, with `Lifecycle.start` allowing beans to reopen resources when relevant. For libraries that do not depend on Spring, custom checkpoint/restore integration can be provided by implementing `org.crac.Resource` and registering the related instance.
|
||||
A checkpoint can be created on demand, for example using a command like `jcmd application.jar JDK.checkpoint`. Before the creation of the checkpoint, Spring stops all the running beans, giving them a chance to close resources if needed by implementing `Lifecycle.stop`. After restore, the same beans are restarted, with `Lifecycle.start` allowing beans to reopen resources when relevant. For libraries that do not depend on Spring, custom checkpoint/restore integration can be provided by implementing `org.crac.Resource` and registering the related instance.
|
||||
|
||||
WARNING: Leveraging checkpoint/restore of a running application typically requires additional lifecycle management to gracefully stop and start using resources like files or sockets and stop active threads.
|
||||
|
||||
@@ -29,6 +28,11 @@ startup during the `LifecycleProcessor.onRefresh` phase. After this phase has co
|
||||
`InitializingBean#afterPropertiesSet` callbacks have been invoked; but the lifecycle has not started, and the
|
||||
`ContextRefreshedEvent` has not yet been published.
|
||||
|
||||
For testing purposes, it is also possible to leverage the `-Dspring.context.exit=onRefresh` JVM system property which
|
||||
triggers similar behavior, but instead of creating a checkpoint, it exits your Spring application at the same lifecycle
|
||||
phase without requiring the Project CraC dependency/JVM or Linux. This can be useful to check if connections to remote
|
||||
services are required when the beans are not started, and potentially refine the configuration to avoid that.
|
||||
|
||||
WARNING: As mentioned above, and especially in use cases where the CRaC files are shipped as part of a deployable artifact (a container image for example), operate with the assumption that any sensitive data "seen" by the JVM ends up in the CRaC files, and assess carefully the related security implications.
|
||||
|
||||
NOTE: Automatic checkpoint/restore is a way to "fast-forward" the startup of the application to a phase where the application context is about to start, but it does not allow to have a fully warmed-up JVM.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -45,7 +45,7 @@ xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The `@ContextHierarchy` annotation is currently not supported in AOT mode.
|
||||
The `@ContextHierarchy` annotation is not supported in AOT mode.
|
||||
====
|
||||
|
||||
To provide test-specific runtime hints for use within a GraalVM native image, you have
|
||||
|
||||
+7
-5
@@ -42,13 +42,15 @@ become cumbersome if a custom factory needs to be used across an entire test sui
|
||||
issue is addressed through support for automatic discovery of default
|
||||
`ContextCustomizerFactory` implementations through the `SpringFactoriesLoader` mechanism.
|
||||
|
||||
Specifically, the modules that make up the testing support in Spring Framework and Spring
|
||||
For example, the modules that make up the testing support in Spring Framework and Spring
|
||||
Boot declare all core default `ContextCustomizerFactory` implementations under the
|
||||
`org.springframework.test.context.ContextCustomizerFactory` key in their
|
||||
`META-INF/spring.factories` properties files. Third-party frameworks and developers can
|
||||
contribute their own `ContextCustomizerFactory` implementations to the list of default
|
||||
factories in the same manner through their own `META-INF/spring.factories` properties
|
||||
files.
|
||||
`META-INF/spring.factories` properties files. The `spring.factories` file for the
|
||||
`spring-test` module can be viewed
|
||||
{spring-framework-code}/spring-test/src/main/resources/META-INF/spring.factories[here].
|
||||
Third-party frameworks and developers can contribute their own `ContextCustomizerFactory`
|
||||
implementations to the list of default factories in the same manner through their own
|
||||
`spring.factories` files.
|
||||
|
||||
|
||||
[[testcontext-context-customizers-merging]]
|
||||
|
||||
@@ -80,12 +80,12 @@ become cumbersome if a custom listener needs to be used across an entire test su
|
||||
issue is addressed through support for automatic discovery of default
|
||||
`TestExecutionListener` implementations through the `SpringFactoriesLoader` mechanism.
|
||||
|
||||
Specifically, the `spring-test` module declares all core default `TestExecutionListener`
|
||||
For example, the `spring-test` module declares all core default `TestExecutionListener`
|
||||
implementations under the `org.springframework.test.context.TestExecutionListener` key in
|
||||
its `META-INF/spring.factories` properties file. Third-party frameworks and developers
|
||||
can contribute their own `TestExecutionListener` implementations to the list of default
|
||||
listeners in the same manner through their own `META-INF/spring.factories` properties
|
||||
file.
|
||||
its {spring-framework-code}/spring-test/src/main/resources/META-INF/spring.factories[`META-INF/spring.factories`
|
||||
properties file]. Third-party frameworks and developers can contribute their own
|
||||
`TestExecutionListener` implementations to the list of default listeners in the same
|
||||
manner through their own `spring.factories` files.
|
||||
|
||||
[[testcontext-tel-config-ordering]]
|
||||
== Ordering `TestExecutionListener` Implementations
|
||||
@@ -207,4 +207,3 @@ Kotlin::
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -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]#
|
||||
|
||||
+5
-2
@@ -198,6 +198,9 @@ controller method xref:web/webmvc/mvc-controller/ann-validation.adoc[Validation]
|
||||
TIP: Using `@ModelAttribute` is optional. By default, any argument that is not a simple
|
||||
value type as determined by
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty]
|
||||
_AND_ that is not resolved by any other argument resolver is treated as an `@ModelAttribute`.
|
||||
|
||||
_AND_ that is not resolved by any other argument resolver is treated as an implicit `@ModelAttribute`.
|
||||
|
||||
WARNING: When compiling to a native image with GraalVM, the implicit `@ModelAttribute`
|
||||
support described above does not allow proper ahead-of-time inference of related data
|
||||
binding reflection hints. As a consequence, it is recommended to explicitly annotate
|
||||
method parameters with `@ModelAttribute` for use in a GraalVM native image.
|
||||
|
||||
@@ -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
|
||||
@@ -509,12 +521,19 @@ Kotlin::
|
||||
|
||||
[[webflux-ann-httpexchange-annotation]]
|
||||
== `@HttpExchange`
|
||||
[.small]#xref:web/webmvc/mvc-controller/ann-requestmapping.adoc#mvc-ann-httpexchange-annotation[See equivalent in the Reactive stack]#
|
||||
[.small]#xref:web/webmvc/mvc-controller/ann-requestmapping.adoc#mvc-ann-httpexchange-annotation[See equivalent in the Servlet stack]#
|
||||
|
||||
As an alternative to `@RequestMapping`, you can also handle requests with `@HttpExchange`
|
||||
methods. Such methods are declared on an
|
||||
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]#
|
||||
|
||||
@@ -78,8 +78,9 @@ it does the same, but it also compares the computed value against the `If-None-M
|
||||
request header and, if the two are equal, returns a 304 (NOT_MODIFIED).
|
||||
|
||||
This strategy saves network bandwidth but not CPU, as the full response must be computed for each request.
|
||||
State-changing HTTP methods and other HTTP conditional request headers such as `If-Match` and `If-Unmodified-Since` are outside the scope of this filter.
|
||||
Other strategies at the controller level can avoid the computation and have a broader support for HTTP conditional requests.
|
||||
State-changing HTTP methods and other HTTP conditional request headers such as `If-Match` and
|
||||
`If-Unmodified-Since` are outside the scope of this filter. Other strategies at the controller level
|
||||
can avoid the computation and have a broader support for HTTP conditional requests.
|
||||
See xref:web/webmvc/mvc-caching.adoc[HTTP Caching].
|
||||
|
||||
This filter has a `writeWeakETag` parameter that configures the filter to write weak ETags
|
||||
|
||||
+6
-1
@@ -243,4 +243,9 @@ xref:web/webmvc/mvc-controller/ann-validation.adoc[Validation].
|
||||
TIP: Using `@ModelAttribute` is optional. By default, any parameter that is not a simple
|
||||
value type as determined by
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty]
|
||||
_AND_ that is not resolved by any other argument resolver is treated as an `@ModelAttribute`.
|
||||
_AND_ that is not resolved by any other argument resolver is treated as an implicit `@ModelAttribute`.
|
||||
|
||||
WARNING: When compiling to a native image with GraalVM, the implicit `@ModelAttribute`
|
||||
support described above does not allow proper ahead-of-time inference of related data
|
||||
binding reflection hints. As a consequence, it is recommended to explicitly annotate
|
||||
method parameters with `@ModelAttribute` for use in a GraalVM native image.
|
||||
|
||||
+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"]
|
||||
----
|
||||
|
||||
+3
-3
@@ -103,9 +103,9 @@ You can also use the WebSocket transport configuration shown earlier to configur
|
||||
maximum allowed size for incoming STOMP messages. In theory, a WebSocket
|
||||
message can be almost unlimited in size. In practice, WebSocket servers impose
|
||||
limits -- for example, 8K on Tomcat and 64K on Jetty. For this reason, STOMP clients
|
||||
(such as the JavaScript https://github.com/JSteunou/webstomp-client[webstomp-client]
|
||||
and others) split larger STOMP messages at 16K boundaries and send them as multiple
|
||||
WebSocket messages, which requires the server to buffer and re-assemble.
|
||||
such as https://github.com/stomp-js/stompjs[`stomp-js/stompjs`] and others split larger
|
||||
STOMP messages at 16K boundaries and send them as multiple WebSocket messages,
|
||||
which requires the server to buffer and re-assemble.
|
||||
|
||||
Spring's STOMP-over-WebSocket support does this ,so applications can configure the
|
||||
maximum size for STOMP messages irrespective of WebSocket server-specific message
|
||||
|
||||
+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.0"))
|
||||
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.4"))
|
||||
api(platform("io.netty:netty-bom:4.1.107.Final"))
|
||||
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
|
||||
api(platform("io.projectreactor:reactor-bom:2023.0.0"))
|
||||
api(platform("io.projectreactor:reactor-bom:2023.0.4"))
|
||||
api(platform("io.rsocket:rsocket-bom:1.1.3"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.15"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.19"))
|
||||
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:12.0.3"))
|
||||
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.3"))
|
||||
api(platform("org.assertj:assertj-bom:3.25.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.7.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.1")
|
||||
api("com.github.ben-manes.caffeine:caffeine:3.1.8")
|
||||
api("com.github.librepdf:openpdf:1.3.33")
|
||||
api("com.github.librepdf:openpdf:1.3.42")
|
||||
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.15")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.15")
|
||||
api("org.apache.tomcat:tomcat-util:10.1.15")
|
||||
api("org.apache.tomcat:tomcat-websocket:10.1.15")
|
||||
api("org.aspectj:aspectjrt:1.9.20.1")
|
||||
api("org.aspectj:aspectjtools:1.9.20.1")
|
||||
api("org.aspectj:aspectjweaver:1.9.20.1")
|
||||
api("org.assertj:assertj-core:3.24.2")
|
||||
api("org.apache.httpcomponents.client5:httpclient5:5.3.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.19")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.19")
|
||||
api("org.apache.tomcat:tomcat-util:10.1.19")
|
||||
api("org.apache.tomcat:tomcat-websocket:10.1.19")
|
||||
api("org.aspectj:aspectjrt:1.9.21.1")
|
||||
api("org.aspectj:aspectjtools:1.9.21.1")
|
||||
api("org.aspectj:aspectjweaver:1.9.21.1")
|
||||
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.1-SNAPSHOT
|
||||
version=6.1.5
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
org.gradle.parallel=true
|
||||
|
||||
kotlinVersion=1.9.20
|
||||
kotlinVersion=1.9.22
|
||||
|
||||
kotlin.jvm.target.validation.mode=ignore
|
||||
kotlin.stdlib.default.dependency=false
|
||||
|
||||
@@ -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.4-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user