mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
606 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 73855ec6f0 | |||
| 28398624bf | |||
| da3b59e7a9 | |||
| edb6bb717d | |||
| 48ad7b1bcd | |||
| a3b62a80bc | |||
| 6cdb34410b | |||
| 7d8279afec | |||
| d625b3de27 | |||
| 531da015e1 | |||
| 60c5f44e1d | |||
| c69ab069b7 | |||
| 7c534eeeeb | |||
| 716e7de841 | |||
| f7a6a7b814 | |||
| a86612a254 | |||
| fc07946e60 | |||
| 061c13d367 | |||
| 8fe4493a7d | |||
| 12727a2c4f | |||
| 29bb7b907c | |||
| 9516f87e5e | |||
| 376d7839e0 | |||
| ec4f3ea6d0 | |||
| cd56b47302 | |||
| 3547491590 | |||
| 80ec951fcf | |||
| 10e3d3b434 | |||
| 2c9ed4608f | |||
| 3ada9a0c79 | |||
| 5cb4985234 | |||
| 19792f9670 | |||
| f629fc9dc5 | |||
| 589b6dbba0 | |||
| b3005fcdde | |||
| b2d43f54e0 | |||
| 797f586f9a | |||
| 8ac39d0062 | |||
| 7985ab33f4 | |||
| a02861f7db | |||
| 8137cc9566 | |||
| 7b16988ec9 | |||
| 8d90817ba3 | |||
| 36295e664a | |||
| 3ce79c3fbf | |||
| cb48077698 | |||
| 2d942a6e91 | |||
| 168276aaab | |||
| bcecce7aac | |||
| c8967de065 | |||
| 5567d14700 | |||
| 24cc77655f | |||
| 645556a28c | |||
| ac0136b75c | |||
| 3048cfe9d9 | |||
| d9ca263065 | |||
| 39889744b0 | |||
| 12272d6e41 | |||
| 93dcbc41b0 | |||
| 371d1a02f2 | |||
| 04944a1f56 | |||
| c6459b40e4 | |||
| bdc4ecd599 | |||
| 92f54da628 | |||
| 8bca7cd8e7 | |||
| 4df80a8d09 | |||
| 12b285add3 | |||
| b0a2f269cb | |||
| 8de7c64637 | |||
| 305ebca56d | |||
| 09ca4cdc70 | |||
| 7ccd4ce886 | |||
| 8f3b7484e2 | |||
| d708343a05 | |||
| 1ffd0dcf06 | |||
| 759ec52fd2 | |||
| a8430878ca | |||
| 3897ea78bb | |||
| d5664ba01a | |||
| 3f3995f846 | |||
| 02b3801f1a | |||
| 69eaf8f828 | |||
| 649a537891 | |||
| 153d1bc923 | |||
| 4e6591e1a9 | |||
| 37dd47c6fc | |||
| 57dd2f46db | |||
| 512ff7ce3c | |||
| a3afe51c9f | |||
| f1a1190700 | |||
| 35c183d634 | |||
| b232aefdeb | |||
| 2b62897dc5 | |||
| ad0c488767 | |||
| 109d985f89 | |||
| a7ceedf2bb | |||
| f85c4e1ea7 | |||
| 4972d18dd6 | |||
| 2350c8a8f5 | |||
| dfc053ad65 | |||
| 87855e2d34 | |||
| 331bdb066e | |||
| 9492d88270 | |||
| 9b85c93b6b | |||
| 65f118bdec | |||
| fc3dddac9c | |||
| a662f5e719 | |||
| 3002813598 | |||
| 3cfa4ed4f7 | |||
| 27d2200058 | |||
| 0b5800ae39 | |||
| 25fd5659cb | |||
| de9ce800bf | |||
| 8f579b3144 | |||
| 14689256c4 | |||
| a3d3bc0a1f | |||
| a986374da7 | |||
| 4e6325a277 | |||
| aaf33100d9 | |||
| 1e4275a0f9 | |||
| 33fbd7141d | |||
| 461d7a82f6 | |||
| 00da781c44 | |||
| 0402ea13c0 | |||
| 6682d75481 | |||
| 75a5409c97 | |||
| 3991cae875 | |||
| a77895bd90 | |||
| ed188f82d8 | |||
| df41bb1ae9 | |||
| 6c5ef9715b | |||
| 3f8a10c19f | |||
| adc7f73170 | |||
| af4a986256 | |||
| 4e1b8f9be3 | |||
| ca6d987c56 | |||
| 8124491249 | |||
| c21090ad31 | |||
| 9f46dd7d86 | |||
| 6a1ec0ed73 | |||
| b965336467 | |||
| c80bf57834 | |||
| 80fb8ea813 | |||
| 1eed71bb1d | |||
| c8090fe0b4 | |||
| 9eab7bb11d | |||
| a01f7cefae | |||
| ccf9ba86a0 | |||
| 0893cf948d | |||
| 62a1b5558f | |||
| aa05a6a3b3 | |||
| a985c0bb35 | |||
| e42c5ca52b | |||
| f96d0a6c4a | |||
| 0bc447c304 | |||
| 00c7002354 | |||
| ec055da7c3 | |||
| c03f798dad | |||
| af23cc233e | |||
| f21e05a9e4 | |||
| 8727d723f3 | |||
| ce8758b83c | |||
| 43b5c81dad | |||
| 69cb2a347a | |||
| 0d4ef4eafe | |||
| 051fd2ae4f | |||
| 02ee5de470 | |||
| afbce96fb7 | |||
| 3f34a13436 | |||
| fe83003324 | |||
| 5012843b12 | |||
| b13963ee81 | |||
| 43cc18a5a1 | |||
| 4d5536db21 | |||
| d99113661e | |||
| 54dab45151 | |||
| 2e3a923225 | |||
| 77c6f160fe | |||
| 2d33aac350 | |||
| 711ddd1ac6 | |||
| 35a0d24dc3 | |||
| ae3dc0d4b1 | |||
| 1c9cff668c | |||
| 15511890bd | |||
| 22bfe7da5a | |||
| 5c6b82a947 | |||
| b7c3833732 | |||
| d91277095a | |||
| 50a0000ed7 | |||
| d6e9562aeb | |||
| f6089afd0e | |||
| 561583842f | |||
| bed3689001 | |||
| 3d85ec2354 | |||
| 330dcf15bc | |||
| 82b51479d5 | |||
| 2d408685ce | |||
| cc7815145f | |||
| a1586ff0a0 | |||
| 0637b699cd | |||
| c3aa0cd815 | |||
| a9fffa844f | |||
| bb04c6b1f3 | |||
| cb8b7a455a | |||
| 9dbb269bb9 | |||
| f3aa61c5bb | |||
| dc242df533 | |||
| c05b4bcd71 | |||
| 246f291f67 | |||
| 4eb93da31d | |||
| cabd1f53df | |||
| a471aa6a9a | |||
| 515295e205 | |||
| 1455a9fe22 | |||
| 36636b000b | |||
| 94050b3638 | |||
| 62db2680f5 | |||
| 5c8043dcea | |||
| 13df59cef4 | |||
| 78f08ed1dd | |||
| 03dd8e20e2 | |||
| dcab1f2237 | |||
| 3e93ca02cc | |||
| 583dc04986 | |||
| 749d7671a0 | |||
| 08bc959c9b | |||
| 4e6532f639 | |||
| 0c42874629 | |||
| 5253af1a04 | |||
| e3281a527c | |||
| a96f22d5e2 | |||
| 7a74e45946 | |||
| 8af3eb1aea | |||
| d26265420a | |||
| bede619e7d | |||
| bf2a72b043 | |||
| 67edcde0a2 | |||
| 81bc586e15 | |||
| 6f95af978e | |||
| d360def92e | |||
| 86b87d7d85 | |||
| 186e70cd04 | |||
| 91bb7d8daf | |||
| db1010f9c9 | |||
| cf87441a26 | |||
| dc982d0d51 | |||
| 996e66abdb | |||
| 96d9081190 | |||
| bf5b22d64f | |||
| 8b51b36729 | |||
| 2fea3d7921 | |||
| f47352ff04 | |||
| c1ed504ac1 | |||
| 5b660da52d | |||
| 3b4f8dbb8e | |||
| 4b0a048570 | |||
| 57632f9f08 | |||
| 7c009ccc1f | |||
| f421f38e24 | |||
| 88e26a9570 | |||
| 2fc78dfb69 | |||
| e52ee01ec8 | |||
| 2b1eb488fc | |||
| 5bec072dcb | |||
| 8736ca038a | |||
| aa9f0b3cb2 | |||
| 56b14dc54a | |||
| 2a74fe5ddb | |||
| 218a148898 | |||
| 38c473fd05 | |||
| d2bd0d5716 | |||
| 4d433174eb | |||
| 9f4d46fe33 | |||
| 2a1abb5553 | |||
| 7edd4e8e22 | |||
| 05b15812bb | |||
| 0e7aba4179 | |||
| cf8810042a | |||
| 88bc3f270e | |||
| b695dbc2bf | |||
| cf4ac96688 | |||
| 0a50854e1a | |||
| 637aa9f71f | |||
| b1b9ee0ccf | |||
| a30c06b883 | |||
| 458c30cb63 | |||
| 5698191ba0 | |||
| 7d197972e0 | |||
| 1732844137 | |||
| 7f40b49f4d | |||
| 2f070e59e6 | |||
| f648fd7c3b | |||
| 1ccd5512c5 | |||
| 5a86ab99df | |||
| 29d307e211 | |||
| 4c7735016b | |||
| 0a715bcab3 | |||
| 8d23ec4747 | |||
| 14bc0d6469 | |||
| 3bd342ee7d | |||
| 871860798e | |||
| e58ea0d945 | |||
| 8f56b4a8ae | |||
| 2ca10e20ca | |||
| 0ef5a4090b | |||
| 6df27644eb | |||
| 2f2c4188e5 | |||
| ccebaa6b0c | |||
| ae5cff1705 | |||
| 3e102e5d8e | |||
| 9cd4d32195 | |||
| 73a1c2d509 | |||
| 2b56ca08d4 | |||
| b89cf2b16a | |||
| 99e1b0d117 | |||
| 89d563097f | |||
| 10e8039de8 | |||
| 2086cda296 | |||
| 66235fa8c8 | |||
| cf31d088e2 | |||
| 4a74e1fc2d | |||
| e7d7cb8641 | |||
| 555d4a6004 | |||
| 1cdbcc58f3 | |||
| b46e528922 | |||
| e97ae434fb | |||
| 76f45c4289 | |||
| 97ebc43ea9 | |||
| 214a54dd6f | |||
| 875037c431 | |||
| 35e8ae2a84 | |||
| 26cab0616e | |||
| e1b1435a00 | |||
| 8e2134c6a7 | |||
| 7c7a59bdd2 | |||
| acd69d38e9 | |||
| 4a3daa7812 | |||
| 27d5213176 | |||
| 19dfc781f5 | |||
| 1d2b55e670 | |||
| 7f7c0d59f6 | |||
| 7d4db434a5 | |||
| 986c4fd926 | |||
| d6422d368a | |||
| 21ed8aad74 | |||
| 7211db9262 | |||
| 3b87c87a33 | |||
| 460ffbc0f6 | |||
| 8a67018e61 | |||
| 68ae1e5700 | |||
| 4b732d62c2 | |||
| 670fc9bb4e | |||
| 7a65aad09c | |||
| b1bf8c5242 | |||
| 6767f7010c | |||
| 8af1d8e842 | |||
| 052b6357c8 | |||
| 282ee02419 | |||
| 667e30f580 | |||
| f9883d8bd6 | |||
| 73ee86c666 | |||
| 76d00d78db | |||
| bf014ef18b | |||
| 13679bb906 | |||
| 6fc1f72704 | |||
| 04e69bdb26 | |||
| 3e48031601 | |||
| f285971cb3 | |||
| 89f6e8ec49 | |||
| 53ed15e4a7 | |||
| 9af4f5cf17 | |||
| 92d1ebefbb | |||
| 71245f9655 | |||
| f30a67c9d0 | |||
| 0435e9c49e | |||
| 38c831f15f | |||
| c4e0f96ef7 | |||
| 5ee11fb1b3 | |||
| 6a8f0d6d7d | |||
| 246e4977a2 | |||
| 4e5723ca9e | |||
| ffb1caaff4 | |||
| 918c5f1f33 | |||
| b431594021 | |||
| fc885224f0 | |||
| 5dc1190930 | |||
| 6f5d3a4b12 | |||
| 4c246b7c96 | |||
| 5345a13918 | |||
| 7fa2a28970 | |||
| 8172d7adfe | |||
| 6b64e9992a | |||
| c188f22378 | |||
| e53ed3e3c6 | |||
| f4c1ad7ae6 | |||
| 20be9e150c | |||
| 65d77624d1 | |||
| 107f47cfcf | |||
| fad544e077 | |||
| 19fec0633f | |||
| 729dc0b671 | |||
| 38d5c0fed6 | |||
| 3577e3b758 | |||
| e1bbdf0913 | |||
| 90867e7e62 | |||
| 5cac2ca11a | |||
| db3c7a157e | |||
| d2e55a2038 | |||
| ae6c64abc5 | |||
| c1bd9243ee | |||
| e66d965fed | |||
| aa4282d7f8 | |||
| 34f0c27b5e | |||
| 722199fe6d | |||
| 9cb8766977 | |||
| 8d24e22e33 | |||
| c79436f832 | |||
| 1ffffef85e | |||
| f0039833c8 | |||
| ac1176af53 | |||
| dcbc2ef134 | |||
| 4ec4e93ece | |||
| 7f0ab22c47 | |||
| 92f4e883b3 | |||
| dedc6a7e9b | |||
| c9e85ec297 | |||
| 1a8d64f9d8 | |||
| 13221ac0ef | |||
| 40da093f58 | |||
| b32a2cadfb | |||
| d87465f9e9 | |||
| 14a461e795 | |||
| 6461eec582 | |||
| be136d79ee | |||
| 9eea768205 | |||
| 19b5f11734 | |||
| 6f7f032ced | |||
| 7d4c8a403e | |||
| eb01cc0d9d | |||
| 30e75e4a09 | |||
| 1fa6ac30b5 | |||
| ddab971fca | |||
| b44c31e997 | |||
| 1ea593e777 | |||
| 70a545e13a | |||
| c29c67839b | |||
| f6dfbac582 | |||
| 6d9aba88d0 | |||
| db826551a6 | |||
| 646bd7f893 | |||
| 1cb2dfa459 | |||
| 79c54098ab | |||
| dc6c96de0a | |||
| b5ca646431 | |||
| 138e7a0e07 | |||
| 4ce2559980 | |||
| 1c5bb732b9 | |||
| 3d7ef3ebfc | |||
| 219004ef13 | |||
| 0758c8b14c | |||
| 4a6dbb17f4 | |||
| a29c84f663 | |||
| 390fe0fe78 | |||
| ac1a030c35 | |||
| fdbefad59c | |||
| bdcd10e228 | |||
| 861ef88d9f | |||
| 193424c465 | |||
| c76d681dc4 | |||
| 4149d5cb2b | |||
| 89a10d5c9b | |||
| 4ac521607e | |||
| 57c10a1fac | |||
| d81619addd | |||
| 246ebd24bf | |||
| 9a1e5eb8d7 | |||
| 4ff0f86ae2 | |||
| e68487a481 | |||
| cfb29db278 | |||
| 570c5b34e6 | |||
| fbc265b72b | |||
| 5acee7b22e | |||
| 7c5bcbcba4 | |||
| 3477738bed | |||
| fa5d246a1b | |||
| aeb77cf4e1 | |||
| 18dc7d73d6 | |||
| 17b2087198 | |||
| 9e7c64270b | |||
| 69dabe9df5 | |||
| 4d206f79d0 | |||
| 5c589833d7 | |||
| 9198774f13 | |||
| 430824038e | |||
| 260404b7f2 | |||
| 567547b63c | |||
| 98efa1df5a | |||
| 87ea3bd31a | |||
| f59c4023e9 | |||
| 58b8330e8d | |||
| c2d2e99c2f | |||
| eab1a3dc6b | |||
| 4b5e96578d | |||
| b0d08fe2d4 | |||
| 122372c580 | |||
| 9c610d9a70 | |||
| 5ae6c0e8ca | |||
| eb8492d597 | |||
| c98bebd6d3 | |||
| d0ffc16efc | |||
| 4bd1485ce4 | |||
| 4339c8eac2 | |||
| 233b59f144 | |||
| 3c00637c88 | |||
| 524588ef93 | |||
| bed4d684e6 | |||
| 229214a9b0 | |||
| 906109e129 | |||
| 00edba07ff | |||
| aee03c5201 | |||
| 89d746ddf8 | |||
| 3ddc512108 | |||
| 76eb5b8c19 | |||
| 491ebb4ec4 | |||
| f811f0dc18 | |||
| f5397d6426 | |||
| 2a1b30dbed | |||
| 734fc476ee | |||
| 5a2b127a21 | |||
| bfed6a3bc5 | |||
| 644887e094 | |||
| b9c304b890 | |||
| e1a32d4ba9 | |||
| 6383a0d7ca | |||
| 3fb170058f | |||
| f9fe8efb2e | |||
| ff9c7141c5 | |||
| 871f705bca | |||
| 22b41c33ba | |||
| 266953195c | |||
| 889c4e0ff5 | |||
| ad2e95be4b | |||
| 63ca8d5d17 | |||
| bc01e3116f | |||
| 480051a21c | |||
| c077805761 | |||
| ceb7afdca2 | |||
| 70004e9ad0 | |||
| 969d0bd08b | |||
| a8fb16b47c | |||
| bc2257aaff | |||
| 218957f0e8 | |||
| a001319f1f | |||
| 874e61a0c6 | |||
| 4a02893c31 | |||
| 902e5707a8 | |||
| f529386ce2 | |||
| b8715811f8 | |||
| 08998376cc | |||
| 6adda31328 | |||
| c6e6a3e44d | |||
| bc2895a30b | |||
| d86af57e0f | |||
| d4cde29f75 | |||
| 5f1e25a61f | |||
| 0ddfe3977a | |||
| c6146ea2db | |||
| eefdee7983 | |||
| ab48ac36e9 | |||
| c1f0faade7 | |||
| cd2a3fdd8d | |||
| 71dfebbfe5 | |||
| a85bf3185e | |||
| 6d5bf6d9b3 | |||
| 6b482df6fa | |||
| ea4e7df9ca | |||
| 7c07c43201 | |||
| 6791ea94a0 | |||
| 7ee8e66c7f | |||
| 34372ee32b | |||
| b6df5a677e | |||
| fc9a118406 | |||
| 87e6d1b7da | |||
| ea3573176a | |||
| bcf235cc66 | |||
| 7ad197e8e1 | |||
| 7e67da8a26 | |||
| d4e8daaede | |||
| e788aeb25b | |||
| 4b2e40153a | |||
| aef4b21f19 | |||
| 5bd1c1fddb | |||
| 5d22aa9770 | |||
| e3aa5b6b11 | |||
| 00e05e603d | |||
| 0fbfecd392 | |||
| b4131ce131 | |||
| 0f707706f1 | |||
| f526b23fd7 | |||
| 2fc8b13dd5 | |||
| e73bbd4ad3 | |||
| 9f8038963f | |||
| 4f8eca1350 | |||
| 18ea43c905 | |||
| bcccba50c1 | |||
| 8288db496d |
@@ -1,20 +0,0 @@
|
||||
name: Await HTTP Resource
|
||||
description: 'Waits for an HTTP resource to be available (a HEAD request succeeds)'
|
||||
inputs:
|
||||
url:
|
||||
description: 'URL of the resource to await'
|
||||
required: true
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Await HTTP resource
|
||||
shell: bash
|
||||
run: |
|
||||
url=${{ inputs.url }}
|
||||
echo "Waiting for $url"
|
||||
until curl --fail --head --silent ${{ inputs.url }} > /dev/null
|
||||
do
|
||||
echo "."
|
||||
sleep 60
|
||||
done
|
||||
echo "$url is available"
|
||||
@@ -1,61 +0,0 @@
|
||||
name: 'Build'
|
||||
description: 'Builds the project, optionally publishing it to a local deployment repository'
|
||||
inputs:
|
||||
develocity-access-key:
|
||||
description: 'Access key for authentication with ge.spring.io'
|
||||
required: false
|
||||
java-distribution:
|
||||
description: 'Java distribution to use'
|
||||
required: false
|
||||
default: 'liberica'
|
||||
java-early-access:
|
||||
description: 'Whether the Java version is in early access'
|
||||
required: false
|
||||
default: 'false'
|
||||
java-toolchain:
|
||||
description: 'Whether a Java toolchain should be used'
|
||||
required: false
|
||||
default: 'false'
|
||||
java-version:
|
||||
description: 'Java version to compile and test with'
|
||||
required: false
|
||||
default: '17'
|
||||
publish:
|
||||
description: 'Whether to publish artifacts ready for deployment to Artifactory'
|
||||
required: false
|
||||
default: 'false'
|
||||
outputs:
|
||||
build-scan-url:
|
||||
description: 'URL, if any, of the build scan produced by the build'
|
||||
value: ${{ (inputs.publish == 'true' && steps.publish.outputs.build-scan-url) || steps.build.outputs.build-scan-url }}
|
||||
version:
|
||||
description: 'Version that was built'
|
||||
value: ${{ steps.read-version.outputs.version }}
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Prepare Gradle Build
|
||||
uses: ./.github/actions/prepare-gradle-build
|
||||
with:
|
||||
develocity-access-key: ${{ inputs.develocity-access-key }}
|
||||
java-distribution: ${{ inputs.java-distribution }}
|
||||
java-early-access: ${{ inputs.java-early-access }}
|
||||
java-toolchain: ${{ inputs.java-toolchain }}
|
||||
java-version: ${{ inputs.java-version }}
|
||||
- name: Build
|
||||
id: build
|
||||
if: ${{ inputs.publish == 'false' }}
|
||||
shell: bash
|
||||
run: ./gradlew check antora
|
||||
- name: Publish
|
||||
id: publish
|
||||
if: ${{ inputs.publish == 'true' }}
|
||||
shell: bash
|
||||
run: ./gradlew -PdeploymentRepository=$(pwd)/deployment-repository build publishAllPublicationsToDeploymentRepository
|
||||
- name: Read Version From gradle.properties
|
||||
id: read-version
|
||||
shell: bash
|
||||
run: |
|
||||
version=$(sed -n 's/version=\(.*\)/\1/p' gradle.properties)
|
||||
echo "Version is $version"
|
||||
echo "version=$version" >> $GITHUB_OUTPUT
|
||||
@@ -1,23 +0,0 @@
|
||||
name: Create GitHub Release
|
||||
description: 'Create the release on GitHub with a changelog'
|
||||
inputs:
|
||||
milestone:
|
||||
description: 'Name of the GitHub milestone for which a release will be created'
|
||||
required: true
|
||||
token:
|
||||
description: 'Token to use for authentication with GitHub'
|
||||
required: true
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Generate Changelog
|
||||
uses: spring-io/github-changelog-generator@185319ad7eaa75b0e8e72e4b6db19c8b2cb8c4c1 #v0.0.11
|
||||
with:
|
||||
config-file: .github/actions/create-github-release/changelog-generator.yml
|
||||
milestone: ${{ inputs.milestone }}
|
||||
token: ${{ inputs.token }}
|
||||
- name: Create GitHub Release
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ inputs.token }}
|
||||
run: gh release create ${{ format('v{0}', inputs.milestone) }} --notes-file changelog.md
|
||||
@@ -1,53 +0,0 @@
|
||||
name: Prepare Gradle Build
|
||||
description: 'Prepares a Gradle build. Sets up Java and Gradle and configures Gradle properties'
|
||||
inputs:
|
||||
develocity-access-key:
|
||||
description: 'Access key for authentication with ge.spring.io'
|
||||
required: false
|
||||
java-distribution:
|
||||
description: 'Java distribution to use'
|
||||
required: false
|
||||
default: 'liberica'
|
||||
java-early-access:
|
||||
description: 'Whether the Java version is in early access. When true, forces java-distribution to temurin'
|
||||
required: false
|
||||
default: 'false'
|
||||
java-toolchain:
|
||||
description: 'Whether a Java toolchain should be used'
|
||||
required: false
|
||||
default: 'false'
|
||||
java-version:
|
||||
description: 'Java version to use for the build'
|
||||
required: false
|
||||
default: '17'
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Set Up Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: ${{ inputs.java-early-access == 'true' && 'temurin' || (inputs.java-distribution || 'liberica') }}
|
||||
java-version: |
|
||||
${{ inputs.java-early-access == 'true' && format('{0}-ea', inputs.java-version) || inputs.java-version }}
|
||||
${{ inputs.java-toolchain == 'true' && '17' || '' }}
|
||||
- name: Set Up Gradle
|
||||
uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
|
||||
with:
|
||||
cache-read-only: false
|
||||
develocity-access-key: ${{ inputs.develocity-access-key }}
|
||||
- name: Configure Gradle Properties
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p $HOME/.gradle
|
||||
echo 'systemProp.user.name=spring-builds+github' >> $HOME/.gradle/gradle.properties
|
||||
echo 'systemProp.org.gradle.internal.launcher.welcomeMessageEnabled=false' >> $HOME/.gradle/gradle.properties
|
||||
echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties
|
||||
echo 'org.gradle.daemon=4' >> $HOME/.gradle/gradle.properties
|
||||
- name: Configure Toolchain Properties
|
||||
if: ${{ inputs.java-toolchain == 'true' }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo toolchainVersion=${{ inputs.java-version }} >> $HOME/.gradle/gradle.properties
|
||||
echo systemProp.org.gradle.java.installations.auto-detect=false >> $HOME/.gradle/gradle.properties
|
||||
echo systemProp.org.gradle.java.installations.auto-download=false >> $HOME/.gradle/gradle.properties
|
||||
echo systemProp.org.gradle.java.installations.paths=${{ format('$JAVA_HOME_{0}_X64', inputs.java-version) }} >> $HOME/.gradle/gradle.properties
|
||||
@@ -1,5 +1,5 @@
|
||||
name: Print JVM thread dumps
|
||||
description: 'Prints a thread dump for all running JVMs'
|
||||
description: Prints a thread dump for all running JVMs
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
@@ -7,7 +7,7 @@ runs:
|
||||
shell: bash
|
||||
run: |
|
||||
for jvm_pid in $(jps -q -J-XX:+PerfDisableSharedMem); do
|
||||
jcmd $jvm_pid Thread.print
|
||||
jcmd $java_pid Thread.print
|
||||
done
|
||||
- if: ${{ runner.os == 'Windows' }}
|
||||
shell: powershell
|
||||
|
||||
@@ -1,39 +1,33 @@
|
||||
name: Send Notification
|
||||
description: 'Sends a Google Chat message as a notification of the job''s outcome'
|
||||
name: Send notification
|
||||
description: Sends a Google Chat message as a notification of the job's outcome
|
||||
inputs:
|
||||
build-scan-url:
|
||||
description: 'URL of the build scan to include in the notification'
|
||||
required: false
|
||||
run-name:
|
||||
description: 'Name of the run to include in the notification'
|
||||
required: false
|
||||
default: ${{ format('{0} {1}', github.ref_name, github.job) }}
|
||||
status:
|
||||
description: 'Status of the job'
|
||||
required: true
|
||||
webhook-url:
|
||||
description: 'Google Chat Webhook URL'
|
||||
required: true
|
||||
status:
|
||||
description: 'Status of the job'
|
||||
required: true
|
||||
build-scan-url:
|
||||
description: 'URL of the build scan to include in the notification'
|
||||
run-name:
|
||||
description: 'Name of the run to include in the notification'
|
||||
default: ${{ format('{0} {1}', github.ref_name, github.job) }}
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Prepare Variables
|
||||
shell: bash
|
||||
- shell: bash
|
||||
run: |
|
||||
echo "BUILD_SCAN=${{ inputs.build-scan-url == '' && ' [build scan unavailable]' || format(' [<{0}|Build Scan>]', inputs.build-scan-url) }}" >> "$GITHUB_ENV"
|
||||
echo "RUN_URL=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" >> "$GITHUB_ENV"
|
||||
- name: Success Notification
|
||||
- shell: bash
|
||||
if: ${{ inputs.status == 'success' }}
|
||||
shell: bash
|
||||
run: |
|
||||
curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: "<${{ env.RUN_URL }}|${{ inputs.run-name }}> was successful ${{ env.BUILD_SCAN }}"}' || true
|
||||
- name: Failure Notification
|
||||
- shell: bash
|
||||
if: ${{ inputs.status == 'failure' }}
|
||||
shell: bash
|
||||
run: |
|
||||
curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: "<users/all> *<${{ env.RUN_URL }}|${{ inputs.run-name }}> failed* ${{ env.BUILD_SCAN }}"}' || true
|
||||
- name: Cancel Notification
|
||||
- shell: bash
|
||||
if: ${{ inputs.status == 'cancelled' }}
|
||||
shell: bash
|
||||
run: |
|
||||
curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: "<${{ env.RUN_URL }}|${{ inputs.run-name }}> was cancelled"}' || true
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
name: Sync to Maven Central
|
||||
description: 'Syncs a release to Maven Central and waits for it to be available for use'
|
||||
inputs:
|
||||
jfrog-cli-config-token:
|
||||
description: 'Config token for the JFrog CLI'
|
||||
required: true
|
||||
ossrh-s01-staging-profile:
|
||||
description: 'Staging profile to use when syncing to Central'
|
||||
required: true
|
||||
ossrh-s01-token-password:
|
||||
description: 'Password for authentication with s01.oss.sonatype.org'
|
||||
required: true
|
||||
ossrh-s01-token-username:
|
||||
description: 'Username for authentication with s01.oss.sonatype.org'
|
||||
required: true
|
||||
spring-framework-version:
|
||||
description: 'Version of Spring Framework that is being synced to Central'
|
||||
required: true
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Set Up JFrog CLI
|
||||
uses: jfrog/setup-jfrog-cli@9fe0f98bd45b19e6e931d457f4e98f8f84461fb5 # v4.4.1
|
||||
env:
|
||||
JF_ENV_SPRING: ${{ inputs.jfrog-cli-config-token }}
|
||||
- name: Download Release Artifacts
|
||||
shell: bash
|
||||
run: jf rt download --spec ${{ format('{0}/artifacts.spec', github.action_path) }} --spec-vars 'buildName=${{ format('spring-framework-{0}', inputs.spring-framework-version) }};buildNumber=${{ github.run_number }}'
|
||||
- name: Sync
|
||||
uses: spring-io/nexus-sync-action@42477a2230a2f694f9eaa4643fa9e76b99b7ab84 # v0.0.1
|
||||
with:
|
||||
close: true
|
||||
create: true
|
||||
generate-checksums: true
|
||||
password: ${{ inputs.ossrh-s01-token-password }}
|
||||
release: true
|
||||
staging-profile-name: ${{ inputs.ossrh-s01-staging-profile }}
|
||||
upload: true
|
||||
username: ${{ inputs.ossrh-s01-token-username }}
|
||||
- name: Await
|
||||
uses: ./.github/actions/await-http-resource
|
||||
with:
|
||||
url: ${{ format('https://repo.maven.apache.org/maven2/org/springframework/spring-context/{0}/spring-context-{0}.jar', inputs.spring-framework-version) }}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"aql": {
|
||||
"items.find": {
|
||||
"$and": [
|
||||
{
|
||||
"@build.name": "${buildName}",
|
||||
"@build.number": "${buildNumber}",
|
||||
"path": {
|
||||
"$nmatch": "org/springframework/framework-api/*"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"target": "nexus/"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
name: Backport Bot
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [labeled]
|
||||
@@ -28,6 +29,6 @@ jobs:
|
||||
run: wget https://github.com/spring-io/backport-bot/releases/download/latest/backport-bot-0.0.1-SNAPSHOT.jar
|
||||
- name: Backport
|
||||
env:
|
||||
GITHUB_EVENT: ${{ toJSON(github.event) }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_EVENT: ${{ toJSON(github.event) }}
|
||||
run: java -jar backport-bot-0.0.1-SNAPSHOT.jar --github.accessToken="$GITHUB_TOKEN" --github.event_name "$GITHUB_EVENT_NAME" --github.event "$GITHUB_EVENT"
|
||||
|
||||
@@ -1,58 +1,62 @@
|
||||
name: Build and Deploy Snapshot
|
||||
name: Build and deploy snapshot
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 6.1.x
|
||||
- main
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
build-and-deploy-snapshot:
|
||||
name: Build and Deploy Snapshot
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
name: Build and deploy snapshot
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'liberica'
|
||||
java-version: 17
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
- name: Build and Publish
|
||||
id: build-and-publish
|
||||
uses: ./.github/actions/build
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5
|
||||
with:
|
||||
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
|
||||
publish: true
|
||||
cache-read-only: false
|
||||
- name: Configure Gradle properties
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p $HOME/.gradle
|
||||
echo 'systemProp.user.name=spring-builds+github' >> $HOME/.gradle/gradle.properties
|
||||
echo 'systemProp.org.gradle.internal.launcher.welcomeMessageEnabled=false' >> $HOME/.gradle/gradle.properties
|
||||
echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties
|
||||
echo 'org.gradle.daemon=4' >> $HOME/.gradle/gradle.properties
|
||||
- name: Build and publish
|
||||
id: build
|
||||
env:
|
||||
CI: 'true'
|
||||
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
|
||||
DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
|
||||
run: ./gradlew -PdeploymentRepository=$(pwd)/deployment-repository build publishAllPublicationsToDeploymentRepository
|
||||
- name: Deploy
|
||||
uses: spring-io/artifactory-deploy-action@26bbe925a75f4f863e1e529e85be2d0093cac116 # v0.0.1
|
||||
uses: spring-io/artifactory-deploy-action@v0.0.1
|
||||
with:
|
||||
uri: 'https://repo.spring.io'
|
||||
username: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
build-name: ${{ format('spring-framework-{0}', github.ref_name)}}
|
||||
repository: 'libs-snapshot-local'
|
||||
folder: 'deployment-repository'
|
||||
signing-key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
signing-passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||
artifact-properties: |
|
||||
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
|
||||
/**/framework-api-*-docs.zip::zip.type=docs
|
||||
/**/framework-api-*-schema.zip::zip.type=schema
|
||||
build-name: 'spring-framework-6.1.x'
|
||||
folder: 'deployment-repository'
|
||||
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
repository: 'libs-snapshot-local'
|
||||
signing-key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
signing-passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||
uri: 'https://repo.spring.io'
|
||||
username: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
- name: Send Notification
|
||||
if: always()
|
||||
- name: Send notification
|
||||
uses: ./.github/actions/send-notification
|
||||
if: always()
|
||||
with:
|
||||
build-scan-url: ${{ steps.build-and-publish.outputs.build-scan-url }}
|
||||
run-name: ${{ format('{0} | Linux | Java 17', github.ref_name) }}
|
||||
status: ${{ job.status }}
|
||||
webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
|
||||
outputs:
|
||||
version: ${{ steps.build-and-publish.outputs.version }}
|
||||
verify:
|
||||
name: Verify
|
||||
needs: build-and-deploy-snapshot
|
||||
uses: ./.github/workflows/verify.yml
|
||||
secrets:
|
||||
google-chat-webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
|
||||
repository-password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
repository-username: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
|
||||
with:
|
||||
version: ${{ needs.build-and-deploy-snapshot.outputs.version }}
|
||||
status: ${{ job.status }}
|
||||
build-scan-url: ${{ steps.build.outputs.build-scan-url }}
|
||||
run-name: ${{ format('{0} | Linux | Java 17', github.ref_name) }}
|
||||
@@ -1,36 +1,43 @@
|
||||
name: Build Pull Request
|
||||
on: pull_request
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Pull Request
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
name: Build pull request
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
steps:
|
||||
- name: Set Up JDK 17
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'liberica'
|
||||
java-version: '17'
|
||||
- name: Check Out
|
||||
distribution: 'liberica'
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
- name: Validate Gradle Wrapper
|
||||
uses: gradle/actions/wrapper-validation@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
|
||||
- name: Set Up Gradle
|
||||
uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
|
||||
|
||||
- name: Validate Gradle wrapper
|
||||
uses: gradle/wrapper-validation-action@699bb18358f12c5b78b37bb0111d3a0e2276e0e2
|
||||
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
CI: 'true'
|
||||
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
|
||||
run: ./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false --no-daemon --no-parallel --continue build
|
||||
- name: Print JVM Thread Dumps When Cancelled
|
||||
if: cancelled()
|
||||
|
||||
- name: Print JVM thread dumps when cancelled
|
||||
uses: ./.github/actions/print-jvm-thread-dumps
|
||||
- name: Upload Build Reports
|
||||
if: failure()
|
||||
if: cancelled()
|
||||
|
||||
- name: Upload build reports
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: build-reports
|
||||
path: '**/build/reports/'
|
||||
|
||||
+42
-23
@@ -1,16 +1,12 @@
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 6.1.x
|
||||
schedule:
|
||||
- cron: '30 9 * * *'
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
ci:
|
||||
name: '${{ matrix.os.name}} | Java ${{ matrix.java.version}}'
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
runs-on: ${{ matrix.os.id }}
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
@@ -21,38 +17,61 @@ jobs:
|
||||
toolchain: false
|
||||
- version: 21
|
||||
toolchain: true
|
||||
- version: 22
|
||||
toolchain: true
|
||||
- version: 23
|
||||
toolchain: true
|
||||
exclude:
|
||||
- os:
|
||||
name: Linux
|
||||
java:
|
||||
version: 17
|
||||
name: '${{ matrix.os.name}} | Java ${{ matrix.java.version}}'
|
||||
runs-on: ${{ matrix.os.id }}
|
||||
steps:
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'liberica'
|
||||
java-version: |
|
||||
${{ matrix.java.version }}
|
||||
${{ matrix.java.toolchain && '17' || '' }}
|
||||
- name: Prepare Windows runner
|
||||
if: ${{ runner.os == 'Windows' }}
|
||||
run: |
|
||||
git config --global core.autocrlf true
|
||||
git config --global core.longPaths true
|
||||
Stop-Service -name Docker
|
||||
- name: Check Out Code
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5
|
||||
with:
|
||||
cache-read-only: false
|
||||
- name: Configure Gradle properties
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p $HOME/.gradle
|
||||
echo 'systemProp.user.name=spring-builds+github' >> $HOME/.gradle/gradle.properties
|
||||
echo 'systemProp.org.gradle.internal.launcher.welcomeMessageEnabled=false' >> $HOME/.gradle/gradle.properties
|
||||
echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties
|
||||
echo 'org.gradle.daemon=4' >> $HOME/.gradle/gradle.properties
|
||||
- name: Configure toolchain properties
|
||||
if: ${{ matrix.java.toolchain }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo toolchainVersion=${{ matrix.java.version }} >> $HOME/.gradle/gradle.properties
|
||||
echo systemProp.org.gradle.java.installations.auto-detect=false >> $HOME/.gradle/gradle.properties
|
||||
echo systemProp.org.gradle.java.installations.auto-download=false >> $HOME/.gradle/gradle.properties
|
||||
echo systemProp.org.gradle.java.installations.paths=${{ format('$JAVA_HOME_{0}_X64', matrix.java.version) }} >> $HOME/.gradle/gradle.properties
|
||||
- name: Build
|
||||
id: build
|
||||
uses: ./.github/actions/build
|
||||
with:
|
||||
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
|
||||
java-early-access: ${{ matrix.java.early-access || 'false' }}
|
||||
java-distribution: ${{ matrix.java.distribution }}
|
||||
java-toolchain: ${{ matrix.java.toolchain }}
|
||||
java-version: ${{ matrix.java.version }}
|
||||
- name: Send Notification
|
||||
if: always()
|
||||
env:
|
||||
CI: 'true'
|
||||
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
|
||||
DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
|
||||
run: ./gradlew check antora
|
||||
- name: Send notification
|
||||
uses: ./.github/actions/send-notification
|
||||
if: always()
|
||||
with:
|
||||
build-scan-url: ${{ steps.build.outputs.build-scan-url }}
|
||||
run-name: ${{ format('{0} | {1} | Java {2}', github.ref_name, matrix.os.name, matrix.java.version) }}
|
||||
status: ${{ job.status }}
|
||||
webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
|
||||
status: ${{ job.status }}
|
||||
build-scan-url: ${{ steps.build.outputs.build-scan-url }}
|
||||
run-name: ${{ format('{0} | {1} | Java {2}', github.ref_name, matrix.os.name, matrix.java.version) }}
|
||||
@@ -14,15 +14,14 @@ permissions:
|
||||
actions: write
|
||||
jobs:
|
||||
build:
|
||||
name: Dispatch docs deployment
|
||||
if: github.repository_owner == 'spring-projects'
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'spring-projects'
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
ref: docs-build
|
||||
fetch-depth: 1
|
||||
- name: Dispatch (partial build)
|
||||
if: github.ref_type == 'branch'
|
||||
env:
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
name: "Validate Gradle Wrapper"
|
||||
on: [push, pull_request]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
validation:
|
||||
name: "Validation"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: gradle/wrapper-validation-action@v2
|
||||
@@ -1,94 +0,0 @@
|
||||
name: Release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v6.1.[0-9]+
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
build-and-stage-release:
|
||||
name: Build and Stage Release
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v4
|
||||
- name: Build and Publish
|
||||
id: build-and-publish
|
||||
uses: ./.github/actions/build
|
||||
with:
|
||||
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
|
||||
publish: true
|
||||
- name: Stage Release
|
||||
uses: spring-io/artifactory-deploy-action@26bbe925a75f4f863e1e529e85be2d0093cac116 # v0.0.1
|
||||
with:
|
||||
artifact-properties: |
|
||||
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
|
||||
/**/framework-api-*-docs.zip::zip.type=docs
|
||||
/**/framework-api-*-schema.zip::zip.type=schema
|
||||
build-name: ${{ format('spring-framework-{0}', steps.build-and-publish.outputs.version)}}
|
||||
folder: 'deployment-repository'
|
||||
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
repository: 'libs-staging-local'
|
||||
signing-key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
signing-passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||
uri: 'https://repo.spring.io'
|
||||
username: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
outputs:
|
||||
version: ${{ steps.build-and-publish.outputs.version }}
|
||||
verify:
|
||||
name: Verify
|
||||
needs: build-and-stage-release
|
||||
uses: ./.github/workflows/verify.yml
|
||||
secrets:
|
||||
google-chat-webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
|
||||
repository-password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
repository-username: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
|
||||
with:
|
||||
staging: true
|
||||
version: ${{ needs.build-and-stage-release.outputs.version }}
|
||||
sync-to-maven-central:
|
||||
name: Sync to Maven Central
|
||||
needs:
|
||||
- build-and-stage-release
|
||||
- verify
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Sync to Maven Central
|
||||
uses: ./.github/actions/sync-to-maven-central
|
||||
with:
|
||||
jfrog-cli-config-token: ${{ secrets.JF_ARTIFACTORY_SPRING }}
|
||||
ossrh-s01-staging-profile: ${{ secrets.OSSRH_S01_STAGING_PROFILE }}
|
||||
ossrh-s01-token-password: ${{ secrets.OSSRH_S01_TOKEN_PASSWORD }}
|
||||
ossrh-s01-token-username: ${{ secrets.OSSRH_S01_TOKEN_USERNAME }}
|
||||
spring-framework-version: ${{ needs.build-and-stage-release.outputs.version }}
|
||||
promote-release:
|
||||
name: Promote Release
|
||||
needs:
|
||||
- build-and-stage-release
|
||||
- sync-to-maven-central
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up JFrog CLI
|
||||
uses: jfrog/setup-jfrog-cli@9fe0f98bd45b19e6e931d457f4e98f8f84461fb5 # v4.4.1
|
||||
env:
|
||||
JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }}
|
||||
- name: Promote build
|
||||
run: jfrog rt build-promote ${{ format('spring-framework-{0}', needs.build-and-stage-release.outputs.version)}} ${{ github.run_number }} libs-release-local
|
||||
create-github-release:
|
||||
name: Create GitHub Release
|
||||
needs:
|
||||
- build-and-stage-release
|
||||
- promote-release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Create GitHub Release
|
||||
uses: ./.github/actions/create-github-release
|
||||
with:
|
||||
milestone: ${{ needs.build-and-stage-release.outputs.version }}
|
||||
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
|
||||
@@ -1,77 +0,0 @@
|
||||
name: Verify
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
staging:
|
||||
description: 'Whether the release to verify is in the staging repository'
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
version:
|
||||
description: 'Version to verify'
|
||||
required: true
|
||||
type: string
|
||||
secrets:
|
||||
google-chat-webhook-url:
|
||||
description: 'Google Chat Webhook URL'
|
||||
required: true
|
||||
repository-password:
|
||||
description: 'Password for authentication with the repository'
|
||||
required: false
|
||||
repository-username:
|
||||
description: 'Username for authentication with the repository'
|
||||
required: false
|
||||
token:
|
||||
description: 'Token to use for authentication with GitHub'
|
||||
required: true
|
||||
jobs:
|
||||
verify:
|
||||
name: Verify
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Release Verification Tests
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: 'v0.0.2'
|
||||
repository: spring-projects/spring-framework-release-verification
|
||||
token: ${{ secrets.token }}
|
||||
- name: Check Out Send Notification Action
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: send-notification
|
||||
sparse-checkout: .github/actions/send-notification
|
||||
- name: Set Up Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'liberica'
|
||||
java-version: 17
|
||||
- name: Set Up Gradle
|
||||
uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
|
||||
with:
|
||||
cache-read-only: false
|
||||
- name: Configure Gradle Properties
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p $HOME/.gradle
|
||||
echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties
|
||||
- name: Run Release Verification Tests
|
||||
env:
|
||||
RVT_OSS_REPOSITORY_PASSWORD: ${{ secrets.repository-password }}
|
||||
RVT_OSS_REPOSITORY_USERNAME: ${{ secrets.repository-username }}
|
||||
RVT_RELEASE_TYPE: oss
|
||||
RVT_STAGING: ${{ inputs.staging }}
|
||||
RVT_VERSION: ${{ inputs.version }}
|
||||
run: ./gradlew spring-framework-release-verification-tests:test
|
||||
- name: Upload Build Reports on Failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-reports
|
||||
path: '**/build/reports/'
|
||||
- name: Send Notification
|
||||
if: failure()
|
||||
uses: ./send-notification/.github/actions/send-notification
|
||||
with:
|
||||
run-name: ${{ format('{0} | Verification | {1}', github.ref_name, inputs.version) }}
|
||||
status: ${{ job.status }}
|
||||
webhook-url: ${{ secrets.google-chat-webhook-url }}
|
||||
@@ -51,5 +51,3 @@ atlassian-ide-plugin.xml
|
||||
.vscode/
|
||||
|
||||
cached-antora-playbook.yml
|
||||
|
||||
node_modules
|
||||
|
||||
@@ -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.12-librca
|
||||
java=17.0.11-librca
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
= Contributor Code of Conduct
|
||||
|
||||
As contributors and maintainers of this project, and in the interest of fostering an open
|
||||
and welcoming community, we pledge to respect all people who contribute through reporting
|
||||
issues, posting feature requests, updating documentation, submitting pull requests or
|
||||
patches, and other activities.
|
||||
|
||||
We are committed to making participation in this project a harassment-free experience for
|
||||
everyone, regardless of level of experience, gender, gender identity and expression,
|
||||
sexual orientation, disability, personal appearance, body size, race, ethnicity, age,
|
||||
religion, or nationality.
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery
|
||||
* Personal attacks
|
||||
* Trolling or insulting/derogatory comments
|
||||
* Public or private harassment
|
||||
* Publishing other's private information, such as physical or electronic addresses,
|
||||
without explicit permission
|
||||
* Other unethical or unprofessional conduct
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments,
|
||||
commits, code, wiki edits, issues, and other contributions that are not aligned to this
|
||||
Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors
|
||||
that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
By adopting this Code of Conduct, project maintainers commit themselves to fairly and
|
||||
consistently applying these principles to every aspect of managing this project. Project
|
||||
maintainers who do not follow or enforce the Code of Conduct may be permanently removed
|
||||
from the project team.
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an
|
||||
individual is representing the project or its community.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by
|
||||
contacting a project maintainer at spring-code-of-conduct@pivotal.io . All complaints will
|
||||
be reviewed and investigated and will result in a response that is deemed necessary and
|
||||
appropriate to the circumstances. Maintainers are obligated to maintain confidentiality
|
||||
with regard to the reporter of an incident.
|
||||
|
||||
This Code of Conduct is adapted from the
|
||||
https://contributor-covenant.org[Contributor Covenant], version 1.3.0, available at
|
||||
https://contributor-covenant.org/version/1/3/0/[contributor-covenant.org/version/1/3/0/]
|
||||
+1
-1
@@ -18,7 +18,7 @@ First off, thank you for taking the time to contribute! :+1: :tada:
|
||||
|
||||
This project is governed by the [Spring Code of Conduct](CODE_OF_CONDUCT.adoc).
|
||||
By participating you are expected to uphold this code.
|
||||
Please report unacceptable behavior to spring-code-of-conduct@spring.io.
|
||||
Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
|
||||
|
||||
### How to Contribute
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [](https://github.com/spring-projects/spring-framework/actions/workflows/build-and-deploy-snapshot.yml?query=branch%3A6.1.x) [](https://ge.spring.io/scans?search.rootProjectNames=spring)
|
||||
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [](https://github.com/spring-projects/spring-framework/actions/workflows/build-and-deploy-snapshot.yml?query=branch%3Amain) [](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".
|
||||
|
||||
@@ -6,7 +6,7 @@ Spring provides everything required beyond the Java programming language for cre
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project is governed by the [Spring Code of Conduct](CODE_OF_CONDUCT.adoc). By participating, you are expected to uphold this code of conduct. Please report unacceptable behavior to spring-code-of-conduct@spring.io.
|
||||
This project is governed by the [Spring Code of Conduct](CODE_OF_CONDUCT.adoc). By participating, you are expected to uphold this code of conduct. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
|
||||
|
||||
## Access to Binaries
|
||||
|
||||
|
||||
+8
-5
@@ -2,13 +2,14 @@ plugins {
|
||||
id 'io.freefair.aspectj' version '8.4' apply false
|
||||
// kotlinVersion is managed in gradle.properties
|
||||
id 'org.jetbrains.kotlin.plugin.serialization' version "${kotlinVersion}" apply false
|
||||
id 'org.jetbrains.dokka' version '1.8.20'
|
||||
id 'org.jetbrains.dokka' version '1.9.20'
|
||||
id 'org.unbroken-dome.xjc' version '2.0.0' apply false
|
||||
id 'com.github.ben-manes.versions' version '0.51.0'
|
||||
id 'com.github.bjornvester.xjc' version '1.8.2' apply false
|
||||
id 'com.github.johnrengelman.shadow' version '8.1.1' apply false
|
||||
id 'de.undercouch.download' version '5.4.0'
|
||||
id 'io.github.goooler.shadow' version '8.1.8' apply false
|
||||
id 'me.champeau.jmh' version '0.7.2' apply false
|
||||
id 'me.champeau.mrjar' version '0.1.1'
|
||||
id "net.ltgt.errorprone" version "3.1.0" apply false
|
||||
}
|
||||
|
||||
ext {
|
||||
@@ -89,19 +90,21 @@ configure([rootProject] + javaProjects) { project ->
|
||||
ext.javadocLinks = [
|
||||
"https://docs.oracle.com/en/java/javase/17/docs/api/",
|
||||
"https://jakarta.ee/specifications/platform/9/apidocs/",
|
||||
"https://docs.oracle.com/cd/E13222_01/wls/docs90/javadocs/", // CommonJ and weblogic.* packages
|
||||
"https://docs.jboss.org/jbossas/javadoc/4.0.5/connector/", // org.jboss.resource.*
|
||||
"https://docs.jboss.org/hibernate/orm/5.6/javadocs/",
|
||||
"https://eclipse.dev/aspectj/doc/released/aspectj5rt-api",
|
||||
"https://www.quartz-scheduler.org/api/2.3.0/",
|
||||
"https://fasterxml.github.io/jackson-core/javadoc/2.14/",
|
||||
"https://fasterxml.github.io/jackson-databind/javadoc/2.14/",
|
||||
"https://fasterxml.github.io/jackson-dataformat-xml/javadoc/2.14/",
|
||||
"https://hc.apache.org/httpcomponents-client-5.4.x/current/httpclient5/apidocs/",
|
||||
"https://hc.apache.org/httpcomponents-client-5.2.x/current/httpclient5/apidocs/",
|
||||
"https://projectreactor.io/docs/test/release/api/",
|
||||
"https://junit.org/junit4/javadoc/4.13.2/",
|
||||
// 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.4/api/",
|
||||
// "https://junit.org/junit5/docs/5.10.2/api/",
|
||||
"https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/",
|
||||
//"https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/",
|
||||
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
org.gradle.caching=true
|
||||
javaFormatVersion=0.0.42
|
||||
javaFormatVersion=0.0.41
|
||||
|
||||
@@ -50,7 +50,7 @@ public class CheckstyleConventions {
|
||||
project.getPlugins().apply(CheckstylePlugin.class);
|
||||
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g"));
|
||||
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
|
||||
checkstyle.setToolVersion("10.20.1");
|
||||
checkstyle.setToolVersion("10.16.0");
|
||||
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
|
||||
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
|
||||
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
|
||||
@@ -64,7 +64,7 @@ public class CheckstyleConventions {
|
||||
NoHttpExtension noHttp = project.getExtensions().getByType(NoHttpExtension.class);
|
||||
noHttp.setAllowlistFile(project.file("src/nohttp/allowlist.lines"));
|
||||
noHttp.getSource().exclude("**/test-output/**", "**/.settings/**",
|
||||
"**/.classpath", "**/.project", "**/.gradle/**", "**/node_modules/**");
|
||||
"**/.classpath", "**/.project", "**/.gradle/**");
|
||||
List<String> buildFolders = List.of("bin", "build", "out");
|
||||
project.allprojects(subproject -> {
|
||||
Path rootPath = project.getRootDir().toPath();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-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.
|
||||
@@ -62,11 +62,9 @@ class TestConventions {
|
||||
if (project.hasProperty("testGroups")) {
|
||||
test.systemProperty("testGroups", project.getProperties().get("testGroups"));
|
||||
}
|
||||
test.jvmArgs(
|
||||
"--add-opens=java.base/java.lang=ALL-UNNAMED",
|
||||
test.jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.util=ALL-UNNAMED",
|
||||
"-Xshare:off"
|
||||
);
|
||||
"-Djava.locale.providers=COMPAT");
|
||||
}
|
||||
|
||||
private void configureTestRetryPlugin(Project project, Test test) {
|
||||
|
||||
@@ -26,9 +26,7 @@ import org.gradle.api.attributes.LibraryElements;
|
||||
import org.gradle.api.attributes.Usage;
|
||||
import org.gradle.api.attributes.java.TargetJvmVersion;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.plugins.jvm.JvmTestSuite;
|
||||
import org.gradle.api.tasks.testing.Test;
|
||||
import org.gradle.testing.base.TestingExtension;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
@@ -49,8 +47,6 @@ public class RuntimeHintsAgentPlugin implements Plugin<Project> {
|
||||
public void apply(Project project) {
|
||||
|
||||
project.getPlugins().withType(JavaPlugin.class, javaPlugin -> {
|
||||
TestingExtension testing = project.getExtensions().getByType(TestingExtension.class);
|
||||
JvmTestSuite jvmTestSuite = (JvmTestSuite) testing.getSuites().getByName("test");
|
||||
RuntimeHintsAgentExtension agentExtension = createRuntimeHintsAgentExtension(project);
|
||||
Test agentTest = project.getTasks().create(RUNTIMEHINTS_TEST_TASK, Test.class, test -> {
|
||||
test.useJUnitPlatform(options -> {
|
||||
@@ -59,8 +55,6 @@ public class RuntimeHintsAgentPlugin implements Plugin<Project> {
|
||||
test.include("**/*Tests.class", "**/*Test.class");
|
||||
test.systemProperty("java.awt.headless", "true");
|
||||
test.systemProperty("org.graalvm.nativeimage.imagecode", "runtime");
|
||||
test.setTestClassesDirs(jvmTestSuite.getSources().getOutput().getClassesDirs());
|
||||
test.setClasspath(jvmTestSuite.getSources().getRuntimeClasspath());
|
||||
test.getJvmArgumentProviders().add(createRuntimeHintsAgentArgumentProvider(project, agentExtension));
|
||||
});
|
||||
project.getTasks().getByName("check", task -> task.dependsOn(agentTest));
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
== Spring Framework Concourse pipeline
|
||||
|
||||
NOTE: CI is being migrated to GitHub Actions.
|
||||
|
||||
The Spring Framework uses https://concourse-ci.org/[Concourse] for its CI build and other automated tasks.
|
||||
The Spring team has a dedicated Concourse instance available at https://ci.spring.io with a build pipeline
|
||||
for https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-6.2.x[Spring Framework 6.2.x].
|
||||
|
||||
=== Setting up your development environment
|
||||
|
||||
If you're part of the Spring Framework project on GitHub, you can get access to CI management features.
|
||||
First, you need to go to https://ci.spring.io and install the client CLI for your platform (see bottom right of the screen).
|
||||
|
||||
You can then login with the instance using:
|
||||
|
||||
[source]
|
||||
----
|
||||
$ fly -t spring login -n spring-framework -c https://ci.spring.io
|
||||
----
|
||||
|
||||
Once logged in, you should get something like:
|
||||
|
||||
[source]
|
||||
----
|
||||
$ fly ts
|
||||
name url team expiry
|
||||
spring https://ci.spring.io spring-framework Wed, 25 Mar 2020 17:45:26 UTC
|
||||
----
|
||||
|
||||
=== Pipeline configuration and structure
|
||||
|
||||
The build pipelines are described in `pipeline.yml` file.
|
||||
|
||||
This file is listing Concourse resources, i.e. build inputs and outputs such as container images, artifact repositories, source repositories, notification services, etc.
|
||||
|
||||
It also describes jobs (a job is a sequence of inputs, tasks and outputs); jobs are organized by groups.
|
||||
|
||||
The `pipeline.yml` definition contains `((parameters))` which are loaded from the `parameters.yml` file or from our https://docs.cloudfoundry.org/credhub/[credhub instance].
|
||||
|
||||
You'll find in this folder the following resources:
|
||||
|
||||
* `pipeline.yml` the build pipeline
|
||||
* `parameters.yml` the build parameters used for the pipeline
|
||||
* `images/` holds the container images definitions used in this pipeline
|
||||
* `scripts/` holds the build scripts that ship within the CI container images
|
||||
* `tasks` contains the task definitions used in the main `pipeline.yml`
|
||||
|
||||
=== Updating the build pipeline
|
||||
|
||||
Updating files on the repository is not enough to update the build pipeline, as changes need to be applied.
|
||||
|
||||
The pipeline can be deployed using the following command:
|
||||
|
||||
[source]
|
||||
----
|
||||
$ fly -t spring set-pipeline -p spring-framework-6.2.x -c ci/pipeline.yml -l ci/parameters.yml
|
||||
----
|
||||
|
||||
NOTE: This assumes that you have credhub integration configured with the appropriate secrets.
|
||||
+1
-9
@@ -17,12 +17,4 @@ changelog:
|
||||
- "type: dependency-upgrade"
|
||||
contributors:
|
||||
exclude:
|
||||
names:
|
||||
- "bclozel"
|
||||
- "jhoeller"
|
||||
- "poutsma"
|
||||
- "rstoyanchev"
|
||||
- "sbrannen"
|
||||
- "sdeleuze"
|
||||
- "simonbasle"
|
||||
- "snicoll"
|
||||
names: ["bclozel", "jhoeller", "poutsma", "rstoyanchev", "sbrannen", "sdeleuze", "snicoll", "simonbasle"]
|
||||
@@ -0,0 +1,10 @@
|
||||
logging:
|
||||
level:
|
||||
io.spring.concourse: DEBUG
|
||||
spring:
|
||||
main:
|
||||
banner-mode: off
|
||||
sonatype:
|
||||
exclude:
|
||||
- 'build-info\.json'
|
||||
- '.*\.zip'
|
||||
@@ -0,0 +1,21 @@
|
||||
== CI Images
|
||||
|
||||
These images are used by CI to run the actual builds.
|
||||
|
||||
To build the image locally run the following from this directory:
|
||||
|
||||
----
|
||||
$ docker build --no-cache -f <image-folder>/Dockerfile .
|
||||
----
|
||||
|
||||
For example
|
||||
|
||||
----
|
||||
$ docker build --no-cache -f spring-framework-ci-image/Dockerfile .
|
||||
----
|
||||
|
||||
To test run:
|
||||
|
||||
----
|
||||
$ docker run -it --entrypoint /bin/bash <SHA>
|
||||
----
|
||||
@@ -0,0 +1,12 @@
|
||||
FROM ubuntu:jammy-20240125
|
||||
|
||||
ADD setup.sh /setup.sh
|
||||
ADD get-jdk-url.sh /get-jdk-url.sh
|
||||
RUN ./setup.sh
|
||||
|
||||
ENV JAVA_HOME /opt/openjdk/java17
|
||||
ENV JDK17 /opt/openjdk/java17
|
||||
ENV JDK21 /opt/openjdk/java21
|
||||
ENV JDK23 /opt/openjdk/java23
|
||||
|
||||
ENV PATH $JAVA_HOME/bin:$PATH
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
case "$1" in
|
||||
java17)
|
||||
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://github.com/bell-sw/Liberica/releases/download/21.0.2%2B14/bellsoft-jdk21.0.2+14-linux-amd64.tar.gz"
|
||||
;;
|
||||
java23)
|
||||
echo "https://download.java.net/java/early_access/jdk23/17/GPL/openjdk-23-ea+17_linux-x64_bin.tar.gz"
|
||||
;;
|
||||
*)
|
||||
echo $"Unknown java version"
|
||||
exit 1
|
||||
esac
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
#!/bin/bash
|
||||
set -ex
|
||||
|
||||
###########################################################
|
||||
# UTILS
|
||||
###########################################################
|
||||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update
|
||||
apt-get install --no-install-recommends -y tzdata ca-certificates net-tools libxml2-utils git curl libudev1 libxml2-utils iptables iproute2 jq fontconfig
|
||||
ln -fs /usr/share/zoneinfo/UTC /etc/localtime
|
||||
dpkg-reconfigure --frontend noninteractive tzdata
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
curl https://raw.githubusercontent.com/spring-io/concourse-java-scripts/v0.0.4/concourse-java.sh > /opt/concourse-java.sh
|
||||
|
||||
###########################################################
|
||||
# JAVA
|
||||
###########################################################
|
||||
|
||||
mkdir -p /opt/openjdk
|
||||
pushd /opt/openjdk > /dev/null
|
||||
for jdk in java17 java21 java23
|
||||
do
|
||||
JDK_URL=$( /get-jdk-url.sh $jdk )
|
||||
mkdir $jdk
|
||||
pushd $jdk > /dev/null
|
||||
curl -L ${JDK_URL} | tar zx --strip-components=1
|
||||
test -f bin/java
|
||||
test -f bin/javac
|
||||
popd > /dev/null
|
||||
done
|
||||
popd
|
||||
|
||||
###########################################################
|
||||
# GRADLE ENTERPRISE
|
||||
###########################################################
|
||||
cd /
|
||||
mkdir ~/.gradle
|
||||
echo 'systemProp.user.name=concourse' > ~/.gradle/gradle.properties
|
||||
@@ -0,0 +1,10 @@
|
||||
github-repo: "https://github.com/spring-projects/spring-framework.git"
|
||||
github-repo-name: "spring-projects/spring-framework"
|
||||
sonatype-staging-profile: "org.springframework"
|
||||
docker-hub-organization: "springci"
|
||||
artifactory-server: "https://repo.spring.io"
|
||||
branch: "main"
|
||||
milestone: "6.2.x"
|
||||
build-name: "spring-framework"
|
||||
pipeline-name: "spring-framework"
|
||||
concourse-url: "https://ci.spring.io"
|
||||
+293
@@ -0,0 +1,293 @@
|
||||
anchors:
|
||||
git-repo-resource-source: &git-repo-resource-source
|
||||
uri: ((github-repo))
|
||||
username: ((github-username))
|
||||
password: ((github-ci-release-token))
|
||||
branch: ((branch))
|
||||
gradle-enterprise-task-params: &gradle-enterprise-task-params
|
||||
DEVELOCITY_ACCESS_KEY: ((gradle_enterprise_secret_access_key))
|
||||
sonatype-task-params: &sonatype-task-params
|
||||
SONATYPE_USERNAME: ((sonatype-username))
|
||||
SONATYPE_PASSWORD: ((sonatype-password))
|
||||
SONATYPE_URL: ((sonatype-url))
|
||||
SONATYPE_STAGING_PROFILE: ((sonatype-staging-profile))
|
||||
artifactory-task-params: &artifactory-task-params
|
||||
ARTIFACTORY_SERVER: ((artifactory-server))
|
||||
ARTIFACTORY_USERNAME: ((artifactory-username))
|
||||
ARTIFACTORY_PASSWORD: ((artifactory-password))
|
||||
build-project-task-params: &build-project-task-params
|
||||
BRANCH: ((branch))
|
||||
<<: *gradle-enterprise-task-params
|
||||
docker-resource-source: &docker-resource-source
|
||||
username: ((docker-hub-username))
|
||||
password: ((docker-hub-password))
|
||||
changelog-task-params: &changelog-task-params
|
||||
name: generated-changelog/tag
|
||||
tag: generated-changelog/tag
|
||||
body: generated-changelog/changelog.md
|
||||
github-task-params: &github-task-params
|
||||
GITHUB_USERNAME: ((github-username))
|
||||
GITHUB_TOKEN: ((github-ci-release-token))
|
||||
|
||||
resource_types:
|
||||
- name: registry-image
|
||||
type: registry-image
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: concourse/registry-image-resource
|
||||
tag: 1.8.0
|
||||
- name: artifactory-resource
|
||||
type: registry-image
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: springio/artifactory-resource
|
||||
tag: 0.0.18
|
||||
- name: github-release
|
||||
type: registry-image
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: concourse/github-release-resource
|
||||
tag: 1.8.0
|
||||
- name: github-status-resource
|
||||
type: registry-image
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: dpb587/github-status-resource
|
||||
tag: master
|
||||
resources:
|
||||
- name: git-repo
|
||||
type: git
|
||||
icon: github
|
||||
source:
|
||||
<<: *git-repo-resource-source
|
||||
- name: ci-images-git-repo
|
||||
type: git
|
||||
icon: github
|
||||
source:
|
||||
uri: ((github-repo))
|
||||
branch: ((branch))
|
||||
paths: ["ci/images/*"]
|
||||
- name: ci-image
|
||||
type: registry-image
|
||||
icon: docker
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: ((docker-hub-organization))/spring-framework-ci
|
||||
tag: ((milestone))
|
||||
- name: artifactory-repo
|
||||
type: artifactory-resource
|
||||
icon: package-variant
|
||||
source:
|
||||
uri: ((artifactory-server))
|
||||
username: ((artifactory-username))
|
||||
password: ((artifactory-password))
|
||||
build_name: ((build-name))
|
||||
- name: github-pre-release
|
||||
type: github-release
|
||||
icon: briefcase-download-outline
|
||||
source:
|
||||
owner: spring-projects
|
||||
repository: spring-framework
|
||||
access_token: ((github-ci-release-token))
|
||||
pre_release: true
|
||||
release: false
|
||||
- name: github-release
|
||||
type: github-release
|
||||
icon: briefcase-download
|
||||
source:
|
||||
owner: spring-projects
|
||||
repository: spring-framework
|
||||
access_token: ((github-ci-release-token))
|
||||
pre_release: false
|
||||
jobs:
|
||||
- name: build-ci-images
|
||||
plan:
|
||||
- get: git-repo
|
||||
- get: ci-images-git-repo
|
||||
trigger: true
|
||||
- task: build-ci-image
|
||||
privileged: true
|
||||
file: git-repo/ci/tasks/build-ci-image.yml
|
||||
output_mapping:
|
||||
image: ci-image
|
||||
vars:
|
||||
ci-image-name: ci-image
|
||||
<<: *docker-resource-source
|
||||
- put: ci-image
|
||||
params:
|
||||
image: ci-image/image.tar
|
||||
- name: stage-milestone
|
||||
serial: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
trigger: false
|
||||
- task: stage
|
||||
image: ci-image
|
||||
file: git-repo/ci/tasks/stage-version.yml
|
||||
params:
|
||||
RELEASE_TYPE: M
|
||||
<<: *gradle-enterprise-task-params
|
||||
- put: artifactory-repo
|
||||
params: &artifactory-params
|
||||
signing_key: ((signing-key))
|
||||
signing_passphrase: ((signing-passphrase))
|
||||
repo: libs-staging-local
|
||||
folder: distribution-repository
|
||||
build_uri: "https://ci.spring.io/teams/${BUILD_TEAM_NAME}/pipelines/${BUILD_PIPELINE_NAME}/jobs/${BUILD_JOB_NAME}/builds/${BUILD_NAME}"
|
||||
build_number: "${BUILD_PIPELINE_NAME}-${BUILD_JOB_NAME}-${BUILD_NAME}"
|
||||
disable_checksum_uploads: true
|
||||
threads: 8
|
||||
artifact_set:
|
||||
- include:
|
||||
- "/**/framework-api-*.zip"
|
||||
properties:
|
||||
"zip.name": "spring-framework"
|
||||
"zip.displayname": "Spring Framework"
|
||||
"zip.deployed": "false"
|
||||
- include:
|
||||
- "/**/framework-api-*-docs.zip"
|
||||
properties:
|
||||
"zip.type": "docs"
|
||||
- include:
|
||||
- "/**/framework-api-*-schema.zip"
|
||||
properties:
|
||||
"zip.type": "schema"
|
||||
get_params:
|
||||
threads: 8
|
||||
- put: git-repo
|
||||
params:
|
||||
repository: stage-git-repo
|
||||
- name: promote-milestone
|
||||
serial: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
trigger: false
|
||||
- get: artifactory-repo
|
||||
trigger: false
|
||||
passed: [stage-milestone]
|
||||
params:
|
||||
download_artifacts: false
|
||||
save_build_info: true
|
||||
- task: promote
|
||||
file: git-repo/ci/tasks/promote-version.yml
|
||||
params:
|
||||
RELEASE_TYPE: M
|
||||
<<: *artifactory-task-params
|
||||
- task: generate-changelog
|
||||
file: git-repo/ci/tasks/generate-changelog.yml
|
||||
params:
|
||||
RELEASE_TYPE: M
|
||||
<<: *github-task-params
|
||||
<<: *docker-resource-source
|
||||
- put: github-pre-release
|
||||
params:
|
||||
<<: *changelog-task-params
|
||||
- name: stage-rc
|
||||
serial: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
trigger: false
|
||||
- task: stage
|
||||
image: ci-image
|
||||
file: git-repo/ci/tasks/stage-version.yml
|
||||
params:
|
||||
RELEASE_TYPE: RC
|
||||
<<: *gradle-enterprise-task-params
|
||||
- put: artifactory-repo
|
||||
params:
|
||||
<<: *artifactory-params
|
||||
- put: git-repo
|
||||
params:
|
||||
repository: stage-git-repo
|
||||
- name: promote-rc
|
||||
serial: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
trigger: false
|
||||
- get: artifactory-repo
|
||||
trigger: false
|
||||
passed: [stage-rc]
|
||||
params:
|
||||
download_artifacts: false
|
||||
save_build_info: true
|
||||
- task: promote
|
||||
file: git-repo/ci/tasks/promote-version.yml
|
||||
params:
|
||||
RELEASE_TYPE: RC
|
||||
<<: *docker-resource-source
|
||||
<<: *artifactory-task-params
|
||||
- task: generate-changelog
|
||||
file: git-repo/ci/tasks/generate-changelog.yml
|
||||
params:
|
||||
RELEASE_TYPE: RC
|
||||
<<: *github-task-params
|
||||
- put: github-pre-release
|
||||
params:
|
||||
<<: *changelog-task-params
|
||||
- name: stage-release
|
||||
serial: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
trigger: false
|
||||
- task: stage
|
||||
image: ci-image
|
||||
file: git-repo/ci/tasks/stage-version.yml
|
||||
params:
|
||||
RELEASE_TYPE: RELEASE
|
||||
<<: *gradle-enterprise-task-params
|
||||
- put: artifactory-repo
|
||||
params:
|
||||
<<: *artifactory-params
|
||||
- put: git-repo
|
||||
params:
|
||||
repository: stage-git-repo
|
||||
- name: promote-release
|
||||
serial: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
trigger: false
|
||||
- get: artifactory-repo
|
||||
trigger: false
|
||||
passed: [stage-release]
|
||||
params:
|
||||
download_artifacts: true
|
||||
save_build_info: true
|
||||
- task: promote
|
||||
file: git-repo/ci/tasks/promote-version.yml
|
||||
params:
|
||||
RELEASE_TYPE: RELEASE
|
||||
<<: *docker-resource-source
|
||||
<<: *artifactory-task-params
|
||||
<<: *sonatype-task-params
|
||||
- name: create-github-release
|
||||
serial: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
- get: artifactory-repo
|
||||
trigger: true
|
||||
passed: [promote-release]
|
||||
params:
|
||||
download_artifacts: false
|
||||
save_build_info: true
|
||||
- task: generate-changelog
|
||||
file: git-repo/ci/tasks/generate-changelog.yml
|
||||
params:
|
||||
RELEASE_TYPE: RELEASE
|
||||
<<: *docker-resource-source
|
||||
<<: *github-task-params
|
||||
- put: github-release
|
||||
params:
|
||||
<<: *changelog-task-params
|
||||
|
||||
groups:
|
||||
- name: "releases"
|
||||
jobs: ["stage-milestone", "stage-rc", "stage-release", "promote-milestone", "promote-rc", "promote-release", "create-github-release"]
|
||||
- name: "ci-images"
|
||||
jobs: ["build-ci-images"]
|
||||
@@ -0,0 +1,2 @@
|
||||
source /opt/concourse-java.sh
|
||||
setup_symlinks
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
CONFIG_DIR=git-repo/ci/config
|
||||
version=$( cat artifactory-repo/build-info.json | jq -r '.buildInfo.modules[0].id' | sed 's/.*:.*:\(.*\)/\1/' )
|
||||
|
||||
java -jar /github-changelog-generator.jar \
|
||||
--spring.config.location=${CONFIG_DIR}/changelog-generator.yml \
|
||||
${version} generated-changelog/changelog.md
|
||||
|
||||
echo ${version} > generated-changelog/version
|
||||
echo v${version} > generated-changelog/tag
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
|
||||
CONFIG_DIR=git-repo/ci/config
|
||||
|
||||
version=$( cat artifactory-repo/build-info.json | jq -r '.buildInfo.modules[0].id' | sed 's/.*:.*:\(.*\)/\1/' )
|
||||
export BUILD_INFO_LOCATION=$(pwd)/artifactory-repo/build-info.json
|
||||
|
||||
java -jar /concourse-release-scripts.jar \
|
||||
--spring.config.location=${CONFIG_DIR}/release-scripts.yml \
|
||||
publishToCentral $RELEASE_TYPE $BUILD_INFO_LOCATION artifactory-repo || { exit 1; }
|
||||
|
||||
java -jar /concourse-release-scripts.jar \
|
||||
--spring.config.location=${CONFIG_DIR}/release-scripts.yml \
|
||||
promote $RELEASE_TYPE $BUILD_INFO_LOCATION || { exit 1; }
|
||||
|
||||
echo "Promotion complete"
|
||||
echo $version > version/version
|
||||
Executable
+51
@@ -0,0 +1,51 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
source $(dirname $0)/common.sh
|
||||
repository=$(pwd)/distribution-repository
|
||||
|
||||
pushd git-repo > /dev/null
|
||||
git fetch --tags --all > /dev/null
|
||||
popd > /dev/null
|
||||
|
||||
git clone git-repo stage-git-repo > /dev/null
|
||||
|
||||
pushd stage-git-repo > /dev/null
|
||||
|
||||
snapshotVersion=$( awk -F '=' '$1 == "version" { print $2 }' gradle.properties )
|
||||
if [[ $RELEASE_TYPE = "M" ]]; then
|
||||
stageVersion=$( get_next_milestone_release $snapshotVersion)
|
||||
nextVersion=$snapshotVersion
|
||||
elif [[ $RELEASE_TYPE = "RC" ]]; then
|
||||
stageVersion=$( get_next_rc_release $snapshotVersion)
|
||||
nextVersion=$snapshotVersion
|
||||
elif [[ $RELEASE_TYPE = "RELEASE" ]]; then
|
||||
stageVersion=$( get_next_release $snapshotVersion)
|
||||
nextVersion=$( bump_version_number $snapshotVersion)
|
||||
else
|
||||
echo "Unknown release type $RELEASE_TYPE" >&2; exit 1;
|
||||
fi
|
||||
|
||||
echo "Staging $stageVersion (next version will be $nextVersion)"
|
||||
sed -i "s/version=$snapshotVersion/version=$stageVersion/" gradle.properties
|
||||
|
||||
git config user.name "Spring Builds" > /dev/null
|
||||
git config user.email "spring-builds@users.noreply.github.com" > /dev/null
|
||||
git add gradle.properties > /dev/null
|
||||
git commit -m"Release v$stageVersion" > /dev/null
|
||||
git tag -a "v$stageVersion" -m"Release v$stageVersion" > /dev/null
|
||||
|
||||
./gradlew --no-daemon --max-workers=4 -PdeploymentRepository=${repository} -Porg.gradle.java.installations.fromEnv=JDK17,JDK21 \
|
||||
build publishAllPublicationsToDeploymentRepository
|
||||
|
||||
git reset --hard HEAD^ > /dev/null
|
||||
if [[ $nextVersion != $snapshotVersion ]]; then
|
||||
echo "Setting next development version (v$nextVersion)"
|
||||
sed -i "s/version=$snapshotVersion/version=$nextVersion/" gradle.properties
|
||||
git add gradle.properties > /dev/null
|
||||
git commit -m"Next development version (v$nextVersion)" > /dev/null
|
||||
fi;
|
||||
|
||||
echo "Staging Complete"
|
||||
|
||||
popd > /dev/null
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
platform: linux
|
||||
image_resource:
|
||||
type: registry-image
|
||||
source:
|
||||
repository: concourse/oci-build-task
|
||||
tag: 0.10.0
|
||||
username: ((docker-hub-username))
|
||||
password: ((docker-hub-password))
|
||||
inputs:
|
||||
- name: ci-images-git-repo
|
||||
outputs:
|
||||
- name: image
|
||||
caches:
|
||||
- path: ci-image-cache
|
||||
params:
|
||||
CONTEXT: ci-images-git-repo/ci/images
|
||||
DOCKERFILE: ci-images-git-repo/ci/images/ci-image/Dockerfile
|
||||
DOCKER_HUB_AUTH: ((docker-hub-auth))
|
||||
run:
|
||||
path: /bin/sh
|
||||
args:
|
||||
- "-c"
|
||||
- |
|
||||
mkdir -p /root/.docker
|
||||
cat > /root/.docker/config.json <<EOF
|
||||
{ "auths": { "https://index.docker.io/v1/": { "auth": "$DOCKER_HUB_AUTH" }}}
|
||||
EOF
|
||||
build
|
||||
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
---
|
||||
platform: linux
|
||||
image_resource:
|
||||
type: registry-image
|
||||
source:
|
||||
repository: springio/github-changelog-generator
|
||||
tag: '0.0.8'
|
||||
username: ((docker-hub-username))
|
||||
password: ((docker-hub-password))
|
||||
inputs:
|
||||
- name: git-repo
|
||||
- name: artifactory-repo
|
||||
outputs:
|
||||
- name: generated-changelog
|
||||
params:
|
||||
GITHUB_ORGANIZATION:
|
||||
GITHUB_REPO:
|
||||
GITHUB_USERNAME:
|
||||
GITHUB_TOKEN:
|
||||
RELEASE_TYPE:
|
||||
run:
|
||||
path: git-repo/ci/scripts/generate-changelog.sh
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
platform: linux
|
||||
image_resource:
|
||||
type: registry-image
|
||||
source:
|
||||
repository: springio/concourse-release-scripts
|
||||
tag: '0.4.0'
|
||||
username: ((docker-hub-username))
|
||||
password: ((docker-hub-password))
|
||||
inputs:
|
||||
- name: git-repo
|
||||
- name: artifactory-repo
|
||||
outputs:
|
||||
- name: version
|
||||
params:
|
||||
RELEASE_TYPE:
|
||||
ARTIFACTORY_SERVER:
|
||||
ARTIFACTORY_USERNAME:
|
||||
ARTIFACTORY_PASSWORD:
|
||||
SONATYPE_USER:
|
||||
SONATYPE_PASSWORD:
|
||||
SONATYPE_URL:
|
||||
SONATYPE_STAGING_PROFILE:
|
||||
run:
|
||||
path: git-repo/ci/scripts/promote-version.sh
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
platform: linux
|
||||
inputs:
|
||||
- name: git-repo
|
||||
outputs:
|
||||
- name: stage-git-repo
|
||||
- name: distribution-repository
|
||||
params:
|
||||
RELEASE_TYPE:
|
||||
CI: true
|
||||
GRADLE_ENTERPRISE_CACHE_USERNAME:
|
||||
GRADLE_ENTERPRISE_CACHE_PASSWORD:
|
||||
GRADLE_ENTERPRISE_URL: https://ge.spring.io
|
||||
caches:
|
||||
- path: gradle
|
||||
run:
|
||||
path: git-repo/ci/scripts/stage-version.sh
|
||||
@@ -28,7 +28,7 @@ javadoc {
|
||||
header = rootProject.description
|
||||
use = true
|
||||
overview = project.relativePath("$rootProject.rootDir/framework-docs/src/docs/api/overview.html")
|
||||
destinationDir = project.java.docsDir.dir("javadoc-api").get().asFile
|
||||
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.java.docsDir.dir("kdoc-api").get().asFile)
|
||||
outputDirectory.set(file("$docsDir/kdoc-api"))
|
||||
includes.from("$rootProject.rootDir/framework-docs/src/docs/api/dokka-overview.md")
|
||||
}
|
||||
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
antora:
|
||||
extensions:
|
||||
- require: '@springio/antora-extensions'
|
||||
root_component_name: 'framework'
|
||||
site:
|
||||
title: Spring Framework
|
||||
url: https://docs.spring.io/spring-framework/reference
|
||||
robots: allow
|
||||
git:
|
||||
ensure_git_suffix: false
|
||||
content:
|
||||
sources:
|
||||
- url: https://github.com/spring-projects/spring-framework
|
||||
# Refname matching:
|
||||
# https://docs.antora.org/antora/latest/playbook/content-refname-matching/
|
||||
branches: ['main', '{6..9}.+({1..9}).x']
|
||||
tags: ['v{6..9}.+({0..9}).+({0..9})?(-{RC,M}*)', '!(v6.0.{0..8})', '!(v6.0.0-{RC,M}{0..9})']
|
||||
start_path: framework-docs
|
||||
asciidoc:
|
||||
extensions:
|
||||
- '@asciidoctor/tabs'
|
||||
- '@springio/asciidoctor-extensions'
|
||||
- '@springio/asciidoctor-extensions/include-code-extension'
|
||||
attributes:
|
||||
page-stackoverflow-url: https://stackoverflow.com/questions/tagged/spring
|
||||
page-pagination: ''
|
||||
hide-uri-scheme: '@'
|
||||
tabs-sync-option: '@'
|
||||
include-java: 'example$docs-src/main/java/org/springframework/docs'
|
||||
urls:
|
||||
latest_version_segment_strategy: redirect:to
|
||||
latest_version_segment: ''
|
||||
redirect_facility: httpd
|
||||
runtime:
|
||||
log:
|
||||
failure_level: warn
|
||||
ui:
|
||||
bundle:
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.17/ui-bundle.zip
|
||||
@@ -21,6 +21,7 @@ asciidoc:
|
||||
table-stripes: 'odd'
|
||||
include-java: 'example$docs-src/main/java/org/springframework/docs'
|
||||
include-kotlin: 'example$docs-src/main/kotlin/org/springframework/docs'
|
||||
include-xml: 'example$docs-src/main/resources/org/springframework/docs'
|
||||
spring-site: 'https://spring.io'
|
||||
spring-site-blog: '{spring-site}/blog'
|
||||
spring-site-cve: "{spring-site}/security"
|
||||
@@ -71,9 +72,6 @@ asciidoc:
|
||||
kotlin-coroutines-api: '{kotlin-site}/api/kotlinx.coroutines'
|
||||
kotlin-github-org: 'https://github.com/Kotlin'
|
||||
kotlin-issues: 'https://youtrack.jetbrains.com/issue'
|
||||
micrometer-docs: 'https://docs.micrometer.io/micrometer/reference'
|
||||
micrometer-context-propagation-docs: 'https://docs.micrometer.io/context-propagation/reference'
|
||||
petclinic-github-org: 'https://github.com/spring-petclinic'
|
||||
reactive-streams-site: 'https://www.reactive-streams.org'
|
||||
reactive-streams-spec: 'https://github.com/reactive-streams/reactive-streams-jvm/blob/master/README.md#specification'
|
||||
reactor-github-org: 'https://github.com/reactor'
|
||||
@@ -91,5 +89,4 @@ asciidoc:
|
||||
stackoverflow-questions: '{stackoverflow-site}/questions'
|
||||
stackoverflow-spring-tag: "{stackoverflow-questions}/tagged/spring"
|
||||
stackoverflow-spring-kotlin-tags: "{stackoverflow-spring-tag}+kotlin"
|
||||
testcontainers-site: 'https://www.testcontainers.org'
|
||||
vavr-docs: 'https://vavr-io.github.io/vavr-docs'
|
||||
testcontainers-site: 'https://www.testcontainers.org'
|
||||
@@ -10,10 +10,27 @@ apply from: "${rootDir}/gradle/ide.gradle"
|
||||
apply from: "${rootDir}/gradle/publications.gradle"
|
||||
|
||||
antora {
|
||||
options = [clean: true, fetch: !project.gradle.startParameter.offline, stacktrace: true]
|
||||
version = '3.2.0-alpha.2'
|
||||
playbook = 'cached-antora-playbook.yml'
|
||||
playbookProvider {
|
||||
repository = 'spring-projects/spring-framework'
|
||||
branch = 'docs-build'
|
||||
path = 'lib/antora/templates/per-branch-antora-playbook.yml'
|
||||
checkLocalBranch = true
|
||||
}
|
||||
options = ['--clean', '--stacktrace']
|
||||
environment = [
|
||||
'BUILD_REFNAME': 'HEAD',
|
||||
'BUILD_VERSION': project.version,
|
||||
'ALGOLIA_API_KEY': '82c7ead946afbac3cf98c32446154691',
|
||||
'ALGOLIA_APP_ID': '244V8V9FGG',
|
||||
'ALGOLIA_INDEX_NAME': 'framework-docs'
|
||||
]
|
||||
dependencies = [
|
||||
'@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',
|
||||
'@springio/antora-extensions': '1.8.2',
|
||||
'@springio/asciidoctor-extensions': '1.0.0-alpha.10'
|
||||
]
|
||||
}
|
||||
|
||||
@@ -46,12 +63,25 @@ dependencies {
|
||||
api(project(":spring-jdbc"))
|
||||
api(project(":spring-jms"))
|
||||
api(project(":spring-web"))
|
||||
api(project(":spring-webflux"))
|
||||
api(project(":spring-webmvc"))
|
||||
api(project(":spring-context-support"))
|
||||
api(project(":spring-aspects"))
|
||||
api(project(":spring-websocket"))
|
||||
|
||||
api("com.oracle.database.jdbc:ojdbc11")
|
||||
api("org.jetbrains.kotlin:kotlin-stdlib")
|
||||
api("jakarta.jms:jakarta.jms-api")
|
||||
api("jakarta.servlet:jakarta.servlet-api")
|
||||
api("org.jetbrains.kotlin:kotlin-stdlib")
|
||||
api("org.apache.commons:commons-dbcp2:2.11.0")
|
||||
api("com.mchange:c3p0:0.9.5.5")
|
||||
api("com.fasterxml.jackson.core:jackson-databind")
|
||||
api("com.fasterxml.jackson.module:jackson-module-parameter-names")
|
||||
api("jakarta.validation:jakarta.validation-api")
|
||||
api("org.aspectj:aspectjweaver")
|
||||
api("io.projectreactor.netty:reactor-netty-http")
|
||||
api("org.eclipse.jetty.websocket:jetty-websocket-jetty-api")
|
||||
api("javax.cache:cache-api")
|
||||
api("jakarta.resource:jakarta.resource-api")
|
||||
api("org.apache.activemq:activemq-ra:6.1.2")
|
||||
|
||||
implementation(project(":spring-core-test"))
|
||||
implementation("org.assertj:assertj-core")
|
||||
|
||||
@@ -134,6 +134,7 @@
|
||||
**** xref:testing/testcontext-framework/ctx-management/failure-threshold.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/hierarchies.adoc[]
|
||||
*** xref:testing/testcontext-framework/fixture-di.adoc[]
|
||||
*** xref:testing/testcontext-framework/bean-overriding.adoc[]
|
||||
*** xref:testing/testcontext-framework/web-scoped-beans.adoc[]
|
||||
*** xref:testing/testcontext-framework/tx.adoc[]
|
||||
*** xref:testing/testcontext-framework/executing-sql.adoc[]
|
||||
@@ -171,6 +172,8 @@
|
||||
***** xref:testing/annotations/integration-spring/annotation-activeprofiles.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-testpropertysource.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-dynamicpropertysource.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-testbean.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-mockitobean.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-dirtiescontext.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-testexecutionlisteners.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-recordapplicationevents.adoc[]
|
||||
|
||||
@@ -23,12 +23,6 @@ The following table lists all currently supported Spring properties.
|
||||
|===
|
||||
| Name | Description
|
||||
|
||||
| `spring.aop.ajc.ignore`
|
||||
| Instructs Spring to ignore ajc-compiled aspects for Spring AOP proxying, restoring traditional
|
||||
Spring behavior for scenarios where both weaving and AspectJ auto-proxying are enabled. See
|
||||
{spring-framework-api}++/aop/aspectj/annotation/AbstractAspectJAdvisorFactory.html#IGNORE_AJC_PROPERTY_NAME++[`AbstractAspectJAdvisorFactory`]
|
||||
for details.
|
||||
|
||||
| `spring.aot.enabled`
|
||||
| Indicates the application should run with AOT generated artifacts. See
|
||||
xref:core/aot.adoc[Ahead of Time Optimizations] and
|
||||
@@ -38,7 +32,7 @@ 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++[`StandardBeanInfoFactory`]
|
||||
{spring-framework-api}++/beans/StandardBeanInfoFactory.html#IGNORE_BEANINFO_PROPERTY_NAME++[`CachedIntrospectionResults`]
|
||||
for details.
|
||||
|
||||
| `spring.cache.reactivestreams.ignore`
|
||||
@@ -55,13 +49,15 @@ 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
|
||||
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
|
||||
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.
|
||||
|
||||
|
||||
@@ -205,7 +205,7 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val person = factory.getBean("person") as Person;
|
||||
val person = factory.getBean("person") as Person
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -128,18 +128,7 @@ the resulting pointcut is effectively the union of the specified patterns.)
|
||||
|
||||
The following example shows how to use `JdkRegexpMethodPointcut`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<bean id="settersAndAbsquatulatePointcut"
|
||||
class="org.springframework.aop.support.JdkRegexpMethodPointcut">
|
||||
<property name="patterns">
|
||||
<list>
|
||||
<value>.*set.*</value>
|
||||
<value>.*absquatulate</value>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
include-code::./JdkRegexpConfiguration[tag=snippet,indent=0]
|
||||
|
||||
Spring provides a convenience class named `RegexpMethodPointcutAdvisor`, which lets us
|
||||
also reference an `Advice` (remember that an `Advice` can be an interceptor, before advice,
|
||||
@@ -147,21 +136,7 @@ throws advice, and others). Behind the scenes, Spring uses a `JdkRegexpMethodPoi
|
||||
Using `RegexpMethodPointcutAdvisor` simplifies wiring, as the one bean encapsulates both
|
||||
pointcut and advice, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<bean id="settersAndAbsquatulateAdvisor"
|
||||
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
|
||||
<property name="advice">
|
||||
<ref bean="beanNameOfAopAllianceInterceptor"/>
|
||||
</property>
|
||||
<property name="patterns">
|
||||
<list>
|
||||
<value>.*set.*</value>
|
||||
<value>.*absquatulate</value>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
include-code::./RegexpConfiguration[tag=snippet,indent=0]
|
||||
|
||||
You can use `RegexpMethodPointcutAdvisor` with any `Advice` type.
|
||||
|
||||
|
||||
@@ -221,8 +221,8 @@ NOTE: `ThreadLocal` instances come with serious issues (potentially resulting in
|
||||
incorrectly using them in multi-threaded and multi-classloader environments. You
|
||||
should always consider wrapping a `ThreadLocal` in some other class and never directly use
|
||||
the `ThreadLocal` itself (except in the wrapper class). Also, you should
|
||||
always remember to correctly set and unset (where the latter involves a call to
|
||||
`ThreadLocal.remove()`) the resource local to the thread. Unsetting should be done in
|
||||
always remember to correctly set and unset (where the latter simply involves a call to
|
||||
`ThreadLocal.set(null)`) the resource local to the thread. Unsetting should be done in
|
||||
any case, since not unsetting it might result in problematic behavior. Spring's
|
||||
`ThreadLocal` support does this for you and should always be considered in favor of using
|
||||
`ThreadLocal` instances without other proper handling code.
|
||||
|
||||
@@ -8,54 +8,8 @@ determines that a bean is advised by one or more aspects, it automatically gener
|
||||
a proxy for that bean to intercept method invocations and ensures that advice is run
|
||||
as needed.
|
||||
|
||||
The @AspectJ support can be enabled with XML- or Java-style configuration. In either
|
||||
case, you also need to ensure that AspectJ's `aspectjweaver.jar` library is on the
|
||||
classpath of your application (version 1.9 or later). This library is available in the
|
||||
`lib` directory of an AspectJ distribution or from the Maven Central repository.
|
||||
|
||||
|
||||
[[aop-enable-aspectj-java]]
|
||||
== Enabling @AspectJ Support with Java Configuration
|
||||
|
||||
To enable @AspectJ support with Java `@Configuration`, add the `@EnableAspectJAutoProxy`
|
||||
annotation, as the following example shows:
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableAspectJAutoProxy
|
||||
public class AppConfig {
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableAspectJAutoProxy
|
||||
class AppConfig
|
||||
----
|
||||
======
|
||||
|
||||
[[aop-enable-aspectj-xml]]
|
||||
== Enabling @AspectJ Support with XML Configuration
|
||||
|
||||
To enable @AspectJ support with XML-based configuration, use the `aop:aspectj-autoproxy`
|
||||
element, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:aspectj-autoproxy/>
|
||||
----
|
||||
|
||||
This assumes that you use schema support as described in
|
||||
xref:core/appendix/xsd-schemas.adoc[XML Schema-based configuration].
|
||||
See xref:core/appendix/xsd-schemas.adoc#aop[the AOP schema] for how to
|
||||
import the tags in the `aop` namespace.
|
||||
|
||||
|
||||
The @AspectJ support can be enabled with programmatic or XML configuration. In either
|
||||
case, you also need to ensure that AspectJ's `org.aspectj:aspectjweaver` library is on the
|
||||
classpath of your application (version 1.9 or later).
|
||||
|
||||
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
|
||||
|
||||
@@ -9,43 +9,12 @@ minimal steps required for a not-very-useful aspect.
|
||||
The first of the two examples shows a regular bean definition in the application context
|
||||
that points to a bean class that is annotated with `@Aspect`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<bean id="myAspect" class="com.xyz.NotVeryUsefulAspect">
|
||||
<!-- configure properties of the aspect here -->
|
||||
</bean>
|
||||
----
|
||||
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
|
||||
|
||||
The second of the two examples shows the `NotVeryUsefulAspect` class definition, which is
|
||||
annotated with `@Aspect`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary",chomp="-packages",fold="none"]
|
||||
----
|
||||
package com.xyz;
|
||||
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
|
||||
@Aspect
|
||||
public class NotVeryUsefulAspect {
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages",fold="none"]
|
||||
----
|
||||
package com.xyz
|
||||
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
|
||||
@Aspect
|
||||
class NotVeryUsefulAspect
|
||||
----
|
||||
======
|
||||
include-code::./NotVeryUsefulAspect[tag=snippet,indent=0]
|
||||
|
||||
Aspects (classes annotated with `@Aspect`) can have methods and fields, the same as any
|
||||
other class. They can also contain pointcut, advice, and introduction (inter-type)
|
||||
|
||||
@@ -16,93 +16,9 @@ aspect.
|
||||
Because we want to retry the operation, we need to use around advice so that we can
|
||||
call `proceed` multiple times. The following listing shows the basic aspect implementation:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Aspect
|
||||
public class ConcurrentOperationExecutor implements Ordered {
|
||||
include-code::./ConcurrentOperationExecutor[tag=snippet,indent=0]
|
||||
|
||||
private static final int DEFAULT_MAX_RETRIES = 2;
|
||||
|
||||
private int maxRetries = DEFAULT_MAX_RETRIES;
|
||||
private int order = 1;
|
||||
|
||||
public void setMaxRetries(int maxRetries) {
|
||||
this.maxRetries = maxRetries;
|
||||
}
|
||||
|
||||
public int getOrder() {
|
||||
return this.order;
|
||||
}
|
||||
|
||||
public void setOrder(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
@Around("com.xyz.CommonPointcuts.businessService()") // <1>
|
||||
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
|
||||
int numAttempts = 0;
|
||||
PessimisticLockingFailureException lockFailureException;
|
||||
do {
|
||||
numAttempts++;
|
||||
try {
|
||||
return pjp.proceed();
|
||||
}
|
||||
catch(PessimisticLockingFailureException ex) {
|
||||
lockFailureException = ex;
|
||||
}
|
||||
} while(numAttempts <= this.maxRetries);
|
||||
throw lockFailureException;
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> References the `businessService` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[Sharing Named Pointcut Definitions].
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Aspect
|
||||
class ConcurrentOperationExecutor : Ordered {
|
||||
|
||||
private val DEFAULT_MAX_RETRIES = 2
|
||||
private var maxRetries = DEFAULT_MAX_RETRIES
|
||||
private var order = 1
|
||||
|
||||
fun setMaxRetries(maxRetries: Int) {
|
||||
this.maxRetries = maxRetries
|
||||
}
|
||||
|
||||
override fun getOrder(): Int {
|
||||
return this.order
|
||||
}
|
||||
|
||||
fun setOrder(order: Int) {
|
||||
this.order = order
|
||||
}
|
||||
|
||||
@Around("com.xyz.CommonPointcuts.businessService()") // <1>
|
||||
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any? {
|
||||
var numAttempts = 0
|
||||
var lockFailureException: PessimisticLockingFailureException
|
||||
do {
|
||||
numAttempts++
|
||||
try {
|
||||
return pjp.proceed()
|
||||
} catch (ex: PessimisticLockingFailureException) {
|
||||
lockFailureException = ex
|
||||
}
|
||||
|
||||
} while (numAttempts <= this.maxRetries)
|
||||
throw lockFailureException
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> References the `businessService` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[Sharing Named Pointcut Definitions].
|
||||
======
|
||||
`@Around("com.xyz.CommonPointcuts.businessService()")` references the `businessService` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[Sharing Named Pointcut Definitions].
|
||||
|
||||
Note that the aspect implements the `Ordered` interface so that we can set the precedence of
|
||||
the aspect higher than the transaction advice (we want a fresh transaction each time we
|
||||
@@ -114,70 +30,15 @@ we have exhausted all of our retry attempts.
|
||||
|
||||
The corresponding Spring configuration follows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:aspectj-autoproxy/>
|
||||
|
||||
<bean id="concurrentOperationExecutor"
|
||||
class="com.xyz.service.impl.ConcurrentOperationExecutor">
|
||||
<property name="maxRetries" value="3"/>
|
||||
<property name="order" value="100"/>
|
||||
</bean>
|
||||
----
|
||||
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
|
||||
|
||||
To refine the aspect so that it retries only idempotent operations, we might define the following
|
||||
`Idempotent` annotation:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
// marker annotation
|
||||
public @interface Idempotent {
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
// marker annotation
|
||||
annotation class Idempotent
|
||||
----
|
||||
======
|
||||
include-code::./service/Idempotent[tag=snippet,indent=0]
|
||||
|
||||
We can then use the annotation to annotate the implementation of service operations. The change
|
||||
to the aspect to retry only idempotent operations involves refining the pointcut
|
||||
expression so that only `@Idempotent` operations match, as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Around("execution(* com.xyz..service.*.*(..)) && " +
|
||||
"@annotation(com.xyz.service.Idempotent)")
|
||||
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Around("execution(* com.xyz..service.*.*(..)) && " +
|
||||
"@annotation(com.xyz.service.Idempotent)")
|
||||
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any? {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
include-code::./service/SampleService[tag=snippet,indent=0]
|
||||
|
||||
@@ -72,7 +72,7 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
val usageTracked = context.getBean("myService", UsageTracked.class)
|
||||
val usageTracked = context.getBean<UsageTracked>("myService")
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ Kotlin::
|
||||
When used as a marker interface in this way, Spring configures new instances of the
|
||||
annotated type (`Account`, in this case) by using a bean definition (typically
|
||||
prototype-scoped) with the same name as the fully-qualified type name
|
||||
(`com.xyz.domain.Account`). Since the default name for a bean is the
|
||||
(`com.xyz.domain.Account`). Since the default name for a bean defined via XML is the
|
||||
fully-qualified name of its type, a convenient way to declare the prototype definition
|
||||
is to omit the `id` attribute, as the following example shows:
|
||||
|
||||
@@ -177,41 +177,10 @@ either use a build-time Ant or Maven task to do this (see, for example, the
|
||||
{aspectj-docs-devguide}/antTasks.html[AspectJ Development
|
||||
Environment Guide]) or load-time weaving (see xref:core/aop/using-aspectj.adoc#aop-aj-ltw[Load-time Weaving with AspectJ in the Spring Framework]). The
|
||||
`AnnotationBeanConfigurerAspect` itself needs to be configured by Spring (in order to obtain
|
||||
a reference to the bean factory that is to be used to configure new objects). If you
|
||||
use Java-based configuration, you can add `@EnableSpringConfigured` to any
|
||||
`@Configuration` class, as follows:
|
||||
a reference to the bean factory that is to be used to configure new objects). You can define
|
||||
the related configuration as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableSpringConfigured
|
||||
public class AppConfig {
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableSpringConfigured
|
||||
class AppConfig {
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
If you prefer XML based configuration, the Spring
|
||||
xref:core/appendix/xsd-schemas.adoc#context[`context` namespace]
|
||||
defines a convenient `context:spring-configured` element, which you can use as follows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<context:spring-configured/>
|
||||
----
|
||||
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
|
||||
|
||||
Instances of `@Configurable` objects created before the aspect has been configured
|
||||
result in a message being issued to the debug log and no configuration of the
|
||||
@@ -420,7 +389,7 @@ who typically are in charge of the deployment configuration, such as the launch
|
||||
Now that the sales pitch is over, let us first walk through a quick example of AspectJ
|
||||
LTW that uses Spring, followed by detailed specifics about elements introduced in the
|
||||
example. For a complete example, see the
|
||||
{petclinic-github-org}/spring-framework-petclinic[Petclinic sample application based on Spring Framework].
|
||||
{spring-github-org}/spring-petclinic[Petclinic sample application].
|
||||
|
||||
|
||||
[[aop-aj-ltw-first-example]]
|
||||
@@ -783,52 +752,9 @@ adding one line. (Note that you almost certainly need to use an
|
||||
`ApplicationContext` as your Spring container -- typically, a `BeanFactory` is not
|
||||
enough because the LTW support uses `BeanFactoryPostProcessors`.)
|
||||
|
||||
To enable the Spring Framework's LTW support, you need to configure a `LoadTimeWeaver`,
|
||||
which typically is done by using the `@EnableLoadTimeWeaving` annotation, as follows:
|
||||
To enable the Spring Framework's LTW support, you need to configure a `LoadTimeWeaver` as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableLoadTimeWeaving
|
||||
public class AppConfig {
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableLoadTimeWeaving
|
||||
class AppConfig {
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Alternatively, if you prefer XML-based configuration, use the
|
||||
`<context:load-time-weaver/>` element. Note that the element is defined in the
|
||||
`context` namespace. The following example shows how to use `<context:load-time-weaver/>`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
https://www.springframework.org/schema/context/spring-context.xsd">
|
||||
|
||||
<context:load-time-weaver/>
|
||||
|
||||
</beans>
|
||||
----
|
||||
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
|
||||
|
||||
The preceding configuration automatically defines and registers a number of LTW-specific
|
||||
infrastructure beans, such as a `LoadTimeWeaver` and an `AspectJWeavingEnabler`, for you.
|
||||
@@ -864,63 +790,12 @@ Note that the table lists only the `LoadTimeWeavers` that are autodetected when
|
||||
use the `DefaultContextLoadTimeWeaver`. You can specify exactly which `LoadTimeWeaver`
|
||||
implementation to use.
|
||||
|
||||
To specify a specific `LoadTimeWeaver` with Java configuration, implement the
|
||||
`LoadTimeWeavingConfigurer` interface and override the `getLoadTimeWeaver()` method.
|
||||
To configure a specific `LoadTimeWeaver`, implement the
|
||||
`LoadTimeWeavingConfigurer` interface and override the `getLoadTimeWeaver()` method
|
||||
(or use the XML equivalent).
|
||||
The following example specifies a `ReflectiveLoadTimeWeaver`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableLoadTimeWeaving
|
||||
public class AppConfig implements LoadTimeWeavingConfigurer {
|
||||
|
||||
@Override
|
||||
public LoadTimeWeaver getLoadTimeWeaver() {
|
||||
return new ReflectiveLoadTimeWeaver();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableLoadTimeWeaving
|
||||
class AppConfig : LoadTimeWeavingConfigurer {
|
||||
|
||||
override fun getLoadTimeWeaver(): LoadTimeWeaver {
|
||||
return ReflectiveLoadTimeWeaver()
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
If you use XML-based configuration, you can specify the fully qualified class name
|
||||
as the value of the `weaver-class` attribute on the `<context:load-time-weaver/>`
|
||||
element. Again, the following example specifies a `ReflectiveLoadTimeWeaver`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
https://www.springframework.org/schema/context/spring-context.xsd">
|
||||
|
||||
<context:load-time-weaver
|
||||
weaver-class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>
|
||||
|
||||
</beans>
|
||||
----
|
||||
include-code::./CustomWeaverConfiguration[tag=snippet,indent=0]
|
||||
|
||||
The `LoadTimeWeaver` that is defined and registered by the configuration can be later
|
||||
retrieved from the Spring container by using the well known name, `loadTimeWeaver`.
|
||||
|
||||
@@ -36,7 +36,7 @@ NOTE: At the moment, AOT is focused on allowing Spring applications to be deploy
|
||||
We intend to support more JVM-based use cases in future generations.
|
||||
|
||||
[[aot.basics]]
|
||||
== AOT Engine Overview
|
||||
== AOT engine overview
|
||||
|
||||
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`]:
|
||||
|
||||
@@ -225,7 +225,7 @@ When a `datasource` instance is required, a `BeanInstanceSupplier` is called.
|
||||
This supplier invokes the `dataSource()` method on the `dataSourceConfiguration` bean.
|
||||
|
||||
[[aot.running]]
|
||||
== Running with AOT Optimizations
|
||||
== Running with AOT optimizations
|
||||
|
||||
AOT is a mandatory step to transform a Spring application to a native executable, so it
|
||||
is automatically enabled when running in this mode. It is possible to use those optimizations
|
||||
@@ -244,7 +244,7 @@ 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
|
||||
== 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
|
||||
@@ -266,7 +266,7 @@ notion of a classpath. For cases like this, it is crucial that the scanning happ
|
||||
build time.
|
||||
|
||||
[[aot.bestpractices.bean-type]]
|
||||
=== Expose the Most Precise 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.
|
||||
@@ -326,21 +326,8 @@ However, this is not a best practice and flagging the preferred constructor with
|
||||
|
||||
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.complex-data-structures]]
|
||||
=== Avoid Complex Data Structures for Constructor Parameters and Properties
|
||||
|
||||
When crafting a `RootBeanDefinition` programmatically, you are not constrained in terms of types that you can use.
|
||||
For instance, you may have a custom `record` with several properties that your bean takes as a constructor argument.
|
||||
|
||||
While this works fine with the regular runtime, AOT does not know how to generate the code of your custom data structure.
|
||||
A good rule of thumb is to keep in mind that bean definitions are an abstraction on top of several models.
|
||||
Rather than using such structures, decomposing to simple types or referring to a bean that is built as such is recommended.
|
||||
|
||||
As a last resort, you can implement your own `org.springframework.aot.generate.ValueCodeGenerator$Delegate`.
|
||||
To use it, register its fully qualified name in `META-INF/spring/aot.factories` using the `Delegate` as the key.
|
||||
|
||||
[[aot.bestpractices.custom-arguments]]
|
||||
=== Avoid Creating Beans with Custom Arguments
|
||||
=== Avoid Creating Bean with Custom Arguments
|
||||
|
||||
Spring AOT detects what needs to be done to create a bean and translates that in generated code using an instance supplier.
|
||||
The container also supports creating a bean with {spring-framework-api}++/beans/factory/BeanFactory.html#getBean(java.lang.String,java.lang.Object...)++[custom arguments] that leads to several issues with AOT:
|
||||
@@ -352,20 +339,6 @@ For instance, autowiring on fields and methods will be skipped as they are handl
|
||||
|
||||
Rather than having prototype-scoped beans created with custom arguments, we recommend a manual factory pattern where a bean is responsible for the creation of the instance.
|
||||
|
||||
[[aot.bestpractices.circular-dependencies]]
|
||||
=== Avoid Circular Dependencies
|
||||
|
||||
Certain use cases can result in circular dependencies between one or more beans. With the
|
||||
regular runtime, it may be possible to wire those circular dependencies via `@Autowired`
|
||||
on setter methods or fields. However, an AOT-optimized context will fail to start with
|
||||
explicit circular dependencies.
|
||||
|
||||
In an AOT-optimized application, you should therefore strive to avoid circular
|
||||
dependencies. If that is not possible, you can use `@Lazy` injection points or
|
||||
`ObjectProvider` to lazily access or retrieve the necessary collaborating beans. See
|
||||
xref:core/beans/classpath-scanning.adoc#beans-factorybeans-annotations-lazy-injection-points[this tip]
|
||||
for further information.
|
||||
|
||||
[[aot.bestpractices.factory-bean]]
|
||||
=== FactoryBean
|
||||
|
||||
|
||||
+46
-3
@@ -1,5 +1,5 @@
|
||||
[[beans-autowired-annotation-primary]]
|
||||
= Fine-tuning Annotation-based Autowiring with `@Primary`
|
||||
= Fine-tuning Annotation-based Autowiring with `@Primary` or `@Fallback`
|
||||
|
||||
Because autowiring by type may lead to multiple candidates, it is often necessary to have
|
||||
more control over the selection process. One way to accomplish this is with Spring's
|
||||
@@ -50,8 +50,51 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
With the preceding configuration, the following `MovieRecommender` is autowired with the
|
||||
`firstMovieCatalog`:
|
||||
Alternatively, as of 6.2, there is a `@Fallback` annotation for demarcating
|
||||
any beans other than the regular ones to be injected. If only one regular
|
||||
bean is left, it is effectively primary as well:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class MovieConfiguration {
|
||||
|
||||
@Bean
|
||||
public MovieCatalog firstMovieCatalog() { ... }
|
||||
|
||||
@Bean
|
||||
@Fallback
|
||||
public MovieCatalog secondMovieCatalog() { ... }
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class MovieConfiguration {
|
||||
|
||||
@Bean
|
||||
fun firstMovieCatalog(): MovieCatalog { ... }
|
||||
|
||||
@Bean
|
||||
@Fallback
|
||||
fun secondMovieCatalog(): MovieCatalog { ... }
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
With both variants of the preceding configuration, the following
|
||||
`MovieRecommender` is autowired with the `firstMovieCatalog`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
||||
+14
-11
@@ -1,12 +1,13 @@
|
||||
[[beans-autowired-annotation-qualifiers]]
|
||||
= Fine-tuning Annotation-based Autowiring with Qualifiers
|
||||
|
||||
`@Primary` is an effective way to use autowiring by type with several instances when one
|
||||
primary candidate can be determined. When you need more control over the selection process,
|
||||
you can use Spring's `@Qualifier` annotation. You can associate qualifier values
|
||||
with specific arguments, narrowing the set of type matches so that a specific bean is
|
||||
chosen for each argument. In the simplest case, this can be a plain descriptive value, as
|
||||
shown in the following example:
|
||||
`@Primary` and `@Fallback` are effective ways to use autowiring by type with several
|
||||
instances when one primary (or non-fallback) candidate can be determined.
|
||||
|
||||
When you need more control over the selection process, you can use Spring's `@Qualifier`
|
||||
annotation. You can associate qualifier values with specific arguments, narrowing the set
|
||||
of type matches so that a specific bean is chosen for each argument. In the simplest case,
|
||||
this can be a plain descriptive value, as shown in the following example:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
@@ -153,15 +154,17 @@ Letting qualifier values select against target bean names, within the type-match
|
||||
candidates, does not require a `@Qualifier` annotation at the injection point.
|
||||
If there is no other resolution indicator (such as a qualifier or a primary marker),
|
||||
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 (either by bean name or by associated alias).
|
||||
(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.
|
||||
====
|
||||
|
||||
As an alternative for injection by name, consider the JSR-250 `@Resource` annotation
|
||||
which is semantically defined to identify a specific target component by its unique name,
|
||||
with the declared type being irrelevant for the matching process. `@Autowired` has rather
|
||||
That said, if you intend to express annotation-driven injection by name, do not
|
||||
primarily use `@Autowired`, even if it is capable of selecting by bean name among
|
||||
type-matching candidates. Instead, use the JSR-250 `@Resource` annotation, which is
|
||||
semantically defined to identify a specific target component by its unique name, with
|
||||
the declared type being irrelevant for the matching process. `@Autowired` has rather
|
||||
different semantics: After selecting candidate beans by type, the specified `String`
|
||||
qualifier value is considered within those type-selected candidates only (for example,
|
||||
matching an `account` qualifier against beans marked with the same qualifier label).
|
||||
|
||||
+2
-2
@@ -101,8 +101,8 @@ NOTE: When configuring a `PropertySourcesPlaceholderConfigurer` using JavaConfig
|
||||
|
||||
Using the above configuration ensures Spring initialization failure if any `${}`
|
||||
placeholder could not be resolved. It is also possible to use methods like
|
||||
`setPlaceholderPrefix`, `setPlaceholderSuffix`, or `setValueSeparator` to customize
|
||||
placeholders.
|
||||
`setPlaceholderPrefix`, `setPlaceholderSuffix`, `setValueSeparator`, or
|
||||
`setEscapeCharacter` to customize placeholders.
|
||||
|
||||
NOTE: Spring Boot configures by default a `PropertySourcesPlaceholderConfigurer` bean that
|
||||
will get properties from `application.properties` and `application.yml` files.
|
||||
|
||||
@@ -317,8 +317,8 @@ exposed based on security policies in some environments -- for example, standalo
|
||||
JDK 1.7.0_45 and higher (which requires 'Trusted-Library' setup in your manifests -- see
|
||||
{stackoverflow-questions}/19394570/java-jre-7u45-breaks-classloader-getresources).
|
||||
|
||||
On the module path (Java Module System), Spring's classpath scanning generally works as
|
||||
expected. However, make sure that your component classes are exported in your `module-info`
|
||||
On JDK 9's module path (Jigsaw), Spring's classpath scanning generally works as expected.
|
||||
However, make sure that your component classes are exported in your `module-info`
|
||||
descriptors. If you expect Spring to invoke non-public members of your classes, make
|
||||
sure that they are 'opened' (that is, that they use an `opens` declaration instead of an
|
||||
`exports` declaration in your `module-info` descriptor).
|
||||
@@ -480,15 +480,11 @@ factory method and other bean definition properties, such as a qualifier value t
|
||||
the `@Qualifier` annotation. Other method-level annotations that can be specified are
|
||||
`@Scope`, `@Lazy`, and custom qualifier annotations.
|
||||
|
||||
[[beans-factorybeans-annotations-lazy-injection-points]]
|
||||
[TIP]
|
||||
====
|
||||
In addition to its role for component initialization, you can also place the `@Lazy`
|
||||
TIP: In addition to its role for component initialization, you can also place the `@Lazy`
|
||||
annotation on injection points marked with `@Autowired` or `@Inject`. In this context,
|
||||
it leads to the injection of a lazy-resolution proxy. However, such a proxy approach
|
||||
is rather limited. For sophisticated lazy interactions, in particular in combination
|
||||
with optional dependencies, we recommend `ObjectProvider<MyTargetBean>` instead.
|
||||
====
|
||||
|
||||
Autowired fields and methods are supported, as previously discussed, with additional
|
||||
support for autowiring of `@Bean` methods. The following example shows how to do so:
|
||||
@@ -755,7 +751,7 @@ and bean definition show.
|
||||
TIP: If you run into naming conflicts due to multiple autodetected components having the
|
||||
same non-qualified class name (i.e., classes with identical names but residing in
|
||||
different packages), you may need to configure a `BeanNameGenerator` that defaults to the
|
||||
fully qualified class name for the generated bean name. The
|
||||
fully qualified class name for the generated bean name. As of Spring Framework 5.2.3, the
|
||||
`FullyQualifiedAnnotationBeanNameGenerator` located in package
|
||||
`org.springframework.context.annotation` can be used for such purposes.
|
||||
|
||||
@@ -924,8 +920,7 @@ Kotlin::
|
||||
[[beans-scanning-qualifiers]]
|
||||
== Providing Qualifier Metadata with Annotations
|
||||
|
||||
The `@Qualifier` annotation is discussed in
|
||||
xref:core/beans/annotation-config/autowired-qualifiers.adoc[Fine-tuning Annotation-based Autowiring with Qualifiers].
|
||||
The `@Qualifier` annotation is discussed in xref:core/beans/annotation-config/autowired-qualifiers.adoc[Fine-tuning Annotation-based Autowiring with Qualifiers].
|
||||
The examples in that section demonstrate the use of the `@Qualifier` annotation and
|
||||
custom qualifier annotations to provide fine-grained control when you resolve autowire
|
||||
candidates. Because those examples were based on XML bean definitions, the qualifier
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[[context-introduction]]
|
||||
= Additional Capabilities of the `ApplicationContext`
|
||||
|
||||
As discussed in the xref:core/beans/introduction.adoc[chapter introduction], the `org.springframework.beans.factory`
|
||||
As discussed in the xref:web/webmvc-view/mvc-xslt.adoc#mvc-view-xslt-beandefs[chapter introduction], the `org.springframework.beans.factory`
|
||||
package provides basic functionality for managing and manipulating beans, including in a
|
||||
programmatic way. The `org.springframework.context` package adds the
|
||||
{spring-framework-api}/context/ApplicationContext.html[`ApplicationContext`]
|
||||
@@ -644,7 +644,7 @@ Each `SpEL` expression evaluates against a dedicated context. The following tabl
|
||||
items made available to the context so that you can use them for conditional event processing:
|
||||
|
||||
[[context-functionality-events-annotation-tbl]]
|
||||
.Event metadata available in SpEL expressions
|
||||
.Event SpEL available metadata
|
||||
|===
|
||||
| Name| Location| Description| Example
|
||||
|
||||
@@ -660,8 +660,8 @@ items made available to the context so that you can use them for conditional eve
|
||||
|
||||
| __Argument name__
|
||||
| evaluation context
|
||||
| The name of a particular method argument. If the names are not available
|
||||
(for example, because the code was compiled without the `-parameters` flag), individual
|
||||
| The name of any of the method arguments. If, for some reason, the names are not available
|
||||
(for example, because there is no debug information in the compiled byte code), individual
|
||||
arguments are also available using the `#a<#arg>` syntax where `<#arg>` stands for the
|
||||
argument index (starting from 0).
|
||||
| `#blEvent` or `#a0` (you can also use `#p0` or `#p<#arg>` parameter notation as an alias)
|
||||
@@ -883,12 +883,11 @@ e.g. for processing all events asynchronously and/or for handling listener excep
|
||||
== Convenient Access to Low-level Resources
|
||||
|
||||
For optimal usage and understanding of application contexts, you should familiarize
|
||||
yourself with Spring's `Resource` abstraction, as described in
|
||||
xref:core/resources.adoc[Resources].
|
||||
yourself with Spring's `Resource` abstraction, as described in xref:web/webflux-webclient/client-builder.adoc#webflux-client-builder-reactor-resources[Resources].
|
||||
|
||||
An application context is a `ResourceLoader`, which can be used to load `Resource` objects.
|
||||
A `Resource` is essentially a more feature rich version of the JDK `java.net.URL` class.
|
||||
In fact, implementations of `Resource` wrap an instance of `java.net.URL`, where
|
||||
In fact, the implementations of the `Resource` wrap an instance of `java.net.URL`, where
|
||||
appropriate. A `Resource` can obtain low-level resources from almost any location in a
|
||||
transparent fashion, including from the classpath, a filesystem location, anywhere
|
||||
describable with a standard URL, and some other variations. If the resource location
|
||||
|
||||
@@ -73,8 +73,6 @@ runtime (concurrently with live access to the factory) is not officially support
|
||||
lead to concurrent access exceptions, inconsistent state in the bean container, or both.
|
||||
====
|
||||
|
||||
|
||||
|
||||
[[beans-definition-overriding]]
|
||||
== Overriding Beans
|
||||
|
||||
@@ -83,22 +81,15 @@ already allocated. While bean overriding is possible, it makes the configuration
|
||||
to read and this feature will be deprecated in a future release.
|
||||
|
||||
To disable bean overriding altogether, you can set the `allowBeanDefinitionOverriding`
|
||||
flag to `false` on the `ApplicationContext` before it is refreshed. In such setup, an
|
||||
to `false` on the `ApplicationContext` before it is refreshed. In such setup, an
|
||||
exception is thrown if bean overriding is used.
|
||||
|
||||
By default, the container logs every bean overriding at `INFO` level so that you can
|
||||
adapt your configuration accordingly. While not recommended, you can silence those logs
|
||||
by setting the `allowBeanDefinitionOverriding` flag to `true`.
|
||||
|
||||
.Java-configuration
|
||||
****
|
||||
If you use Java Configuration, a corresponding `@Bean` method always silently overrides
|
||||
a scanned bean class with the same component name as long as the return type of the
|
||||
`@Bean` method matches that bean class. This simply means that the container will call
|
||||
the `@Bean` factory method in favor of any pre-declared constructor on the bean class.
|
||||
****
|
||||
|
||||
|
||||
NOTE: We acknowledge that overriding beans in a test is convenient, and there is
|
||||
explicit support for this. For more details please refer to xref:testing/testcontext-framework/bean-overriding.adoc[this section].
|
||||
|
||||
[[beans-beanname]]
|
||||
== Naming Beans
|
||||
|
||||
@@ -60,7 +60,6 @@ instance's values consist of all bean instances that match the expected type, an
|
||||
`Map` instance's keys contain the corresponding bean names.
|
||||
|
||||
|
||||
|
||||
[[beans-autowired-exceptions]]
|
||||
== Limitations and Disadvantages of Autowiring
|
||||
|
||||
@@ -102,10 +101,10 @@ In the latter scenario, you have several options:
|
||||
== Excluding a Bean from Autowiring
|
||||
|
||||
On a per-bean basis, you can exclude a bean from autowiring. In Spring's XML format, set
|
||||
the `autowire-candidate` attribute of the `<bean/>` element to `false`; with the `@Bean`
|
||||
annotation, the attribute is named `autowireCandidate`. The container makes that specific
|
||||
bean definition unavailable to the autowiring infrastructure, including annotation-based
|
||||
injection points such as xref:core/beans/annotation-config/autowired.adoc[`@Autowired`].
|
||||
the `autowire-candidate` attribute of the `<bean/>` element to `false`. The container
|
||||
makes that specific bean definition unavailable to the autowiring infrastructure
|
||||
(including annotation style configurations such as xref:core/beans/annotation-config/autowired.adoc[`@Autowired`]
|
||||
).
|
||||
|
||||
NOTE: The `autowire-candidate` attribute is designed to only affect type-based autowiring.
|
||||
It does not affect explicit references by name, which get resolved even if the
|
||||
@@ -120,8 +119,8 @@ provide multiple patterns, define them in a comma-separated list. An explicit va
|
||||
`true` or `false` for a bean definition's `autowire-candidate` attribute always takes
|
||||
precedence. For such beans, the pattern matching rules do not apply.
|
||||
|
||||
These techniques are useful for beans that you never want to be injected into other beans
|
||||
by autowiring. It does not mean that an excluded bean cannot itself be configured by
|
||||
These techniques are useful for beans that you never want to be injected into other
|
||||
beans by autowiring. It does not mean that an excluded bean cannot itself be configured by
|
||||
using autowiring. Rather, the bean itself is not a candidate for autowiring other beans.
|
||||
|
||||
|
||||
|
||||
+12
-15
@@ -159,12 +159,10 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
[discrete]
|
||||
[[beans-factory-ctor-arguments-type]]
|
||||
==== Constructor argument type matching
|
||||
|
||||
.[[beans-factory-ctor-arguments-type]]Constructor argument type matching
|
||||
--
|
||||
In the preceding scenario, the container can use type matching with simple types if
|
||||
you explicitly specify the type of the constructor argument via the `type` attribute,
|
||||
you explicitly specify the type of the constructor argument by using the `type` attribute,
|
||||
as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
@@ -174,11 +172,10 @@ as the following example shows:
|
||||
<constructor-arg type="java.lang.String" value="42"/>
|
||||
</bean>
|
||||
----
|
||||
--
|
||||
|
||||
[discrete]
|
||||
[[beans-factory-ctor-arguments-index]]
|
||||
==== Constructor argument index
|
||||
|
||||
.[[beans-factory-ctor-arguments-index]]Constructor argument index
|
||||
--
|
||||
You can use the `index` attribute to specify explicitly the index of constructor arguments,
|
||||
as the following example shows:
|
||||
|
||||
@@ -194,11 +191,10 @@ In addition to resolving the ambiguity of multiple simple values, specifying an
|
||||
resolves ambiguity where a constructor has two arguments of the same type.
|
||||
|
||||
NOTE: The index is 0-based.
|
||||
--
|
||||
|
||||
[discrete]
|
||||
[[beans-factory-ctor-arguments-name]]
|
||||
==== Constructor argument name
|
||||
|
||||
.[[beans-factory-ctor-arguments-name]]Constructor argument name
|
||||
--
|
||||
You can also use the constructor parameter name for value disambiguation, as the following
|
||||
example shows:
|
||||
|
||||
@@ -211,8 +207,8 @@ example shows:
|
||||
----
|
||||
|
||||
Keep in mind that, to make this work out of the box, your code must be compiled with the
|
||||
`-parameters` flag enabled so that Spring can look up the parameter name from the constructor.
|
||||
If you cannot or do not want to compile your code with the `-parameters` flag, you can use the
|
||||
debug flag enabled so that Spring can look up the parameter name from the constructor.
|
||||
If you cannot or do not want to compile your code with the debug flag, you can use the
|
||||
https://download.oracle.com/javase/8/docs/api/java/beans/ConstructorProperties.html[@ConstructorProperties]
|
||||
JDK annotation to explicitly name your constructor arguments. The sample class would
|
||||
then have to look as follows:
|
||||
@@ -248,6 +244,7 @@ Kotlin::
|
||||
constructor(val years: Int, val ultimateAnswer: String)
|
||||
----
|
||||
======
|
||||
--
|
||||
|
||||
|
||||
[[beans-setter-injection]]
|
||||
|
||||
@@ -2,15 +2,13 @@
|
||||
= Using `depends-on`
|
||||
|
||||
If a bean is a dependency of another bean, that usually means that one bean is set as a
|
||||
property of another. Typically you accomplish this with the
|
||||
xref:core/beans/dependencies/factory-properties-detailed.adoc#beans-ref-element[`<ref/>` element>]
|
||||
in XML-based metadata or through xref:core/beans/dependencies/factory-autowire.adoc[autowiring].
|
||||
|
||||
However, sometimes dependencies between beans are less direct. An example is when a static
|
||||
initializer in a class needs to be triggered, such as for database driver registration.
|
||||
The `depends-on` attribute or `@DependsOn` annotation can explicitly force one or more beans
|
||||
to be initialized before the bean using this element is initialized. The following example
|
||||
uses the `depends-on` attribute to express a dependency on a single bean:
|
||||
property of another. Typically you accomplish this with the <<beans-ref-element, `<ref/>`
|
||||
element>> in XML-based configuration metadata. However, sometimes dependencies between
|
||||
beans are less direct. An example is when a static initializer in a class needs to be
|
||||
triggered, such as for database driver registration. The `depends-on` attribute can
|
||||
explicitly force one or more beans to be initialized before the bean using this element
|
||||
is initialized. The following example uses the `depends-on` attribute to express a
|
||||
dependency on a single bean:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -33,10 +31,10 @@ delimiters):
|
||||
----
|
||||
|
||||
NOTE: The `depends-on` attribute can specify both an initialization-time dependency and,
|
||||
in the case of xref:core/beans/factory-scopes.adoc#beans-factory-scopes-singleton[singleton]
|
||||
beans only, a corresponding destruction-time dependency. Dependent beans that define a
|
||||
`depends-on` relationship with a given bean are destroyed first, prior to the given bean
|
||||
itself being destroyed. Thus, `depends-on` can also control shutdown order.
|
||||
in the case of xref:core/beans/factory-scopes.adoc#beans-factory-scopes-singleton[singleton] beans only, a corresponding
|
||||
destruction-time dependency. Dependent beans that define a `depends-on` relationship
|
||||
with a given bean are destroyed first, prior to the given bean itself being destroyed.
|
||||
Thus, `depends-on` can also control shutdown order.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -10,33 +10,25 @@ pre-instantiation of a singleton bean by marking the bean definition as being
|
||||
lazy-initialized. A lazy-initialized bean tells the IoC container to create a bean
|
||||
instance when it is first requested, rather than at startup.
|
||||
|
||||
In XML, this behavior is controlled by the `lazy-init` attribute on the `<bean/>`
|
||||
element, as the following example shows:
|
||||
This behavior is controlled by the `@Lazy` annotation or in XML the `lazy-init` attribute on the `<bean/>` element, as
|
||||
the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
|
||||
<bean name="not.lazy" class="com.something.AnotherBean"/>
|
||||
----
|
||||
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
|
||||
|
||||
When the preceding configuration is consumed by an `ApplicationContext`, the `lazy` bean
|
||||
is not eagerly pre-instantiated when the `ApplicationContext` starts,
|
||||
whereas the `not.lazy` bean is eagerly pre-instantiated.
|
||||
whereas the `notLazy` one is eagerly pre-instantiated.
|
||||
|
||||
However, when a lazy-initialized bean is a dependency of a singleton bean that is
|
||||
not lazy-initialized, the `ApplicationContext` creates the lazy-initialized bean at
|
||||
startup, because it must satisfy the singleton's dependencies. The lazy-initialized bean
|
||||
is injected into a singleton bean elsewhere that is not lazy-initialized.
|
||||
|
||||
You can also control lazy-initialization at the container level by using the
|
||||
`default-lazy-init` attribute on the `<beans/>` element, as the following example shows:
|
||||
You can also control lazy-initialization for a set of beans by using the `@Lazy` annotation on your `@Configuration`
|
||||
annotated class or in XML using the `default-lazy-init` attribute on the `<beans/>` element, as the following example
|
||||
shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans default-lazy-init="true">
|
||||
<!-- no beans will be pre-instantiated... -->
|
||||
</beans>
|
||||
----
|
||||
include-code::./LazyConfiguration[tag=snippet,indent=0]
|
||||
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -201,7 +201,7 @@ the original class. Consider the following example:
|
||||
<!-- inject dependencies here as required -->
|
||||
</bean>
|
||||
|
||||
<!-- commandManager uses myCommand prototype bean -->
|
||||
<!-- commandProcessor uses statefulCommandHelper -->
|
||||
<bean id="commandManager" class="fiona.apple.CommandManager">
|
||||
<lookup-method name="createCommand" bean="myCommand"/>
|
||||
</bean>
|
||||
|
||||
+3
-3
@@ -102,8 +102,8 @@ following snippet:
|
||||
----
|
||||
<bean id="theTargetBean" class="..." />
|
||||
|
||||
<bean id="theClientBean" class="...">
|
||||
<property name="targetName" ref="theTargetBean"/>
|
||||
<bean id="client" class="...">
|
||||
<property name="targetName" value="theTargetBean"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
@@ -582,7 +582,7 @@ it needs to be declared in the XML file even though it is not defined in an XSD
|
||||
(it exists inside the Spring core).
|
||||
|
||||
For the rare cases where the constructor argument names are not available (usually if
|
||||
the bytecode was compiled without the `-parameters` flag), you can fall back to the
|
||||
the bytecode was compiled without debugging information), you can use fallback to the
|
||||
argument indexes, as follows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
|
||||
@@ -439,7 +439,7 @@ dataSource.url=jdbc:mysql:mydb
|
||||
----
|
||||
|
||||
This example file can be used with a container definition that contains a bean called
|
||||
`dataSource` that has `driverClassName` and `url` properties.
|
||||
`dataSource` that has `driver` and `url` properties.
|
||||
|
||||
Compound property names are also supported, as long as every component of the path
|
||||
except the final property being overridden is already non-null (presumably initialized
|
||||
|
||||
+73
-59
@@ -116,7 +116,7 @@ the configuration model, in that references to other beans must be valid Java sy
|
||||
Fortunately, solving this problem is simple. As
|
||||
xref:core/beans/java/bean-annotation.adoc#beans-java-dependencies[we already discussed],
|
||||
a `@Bean` method can have an arbitrary number of parameters that describe the bean
|
||||
dependencies. Consider the following more realistic scenario with several `@Configuration`
|
||||
dependencies. Consider the following more real-world scenario with several `@Configuration`
|
||||
classes, each depending on beans declared in the others:
|
||||
|
||||
[tabs]
|
||||
@@ -331,10 +331,8 @@ TIP: Constructor injection in `@Configuration` classes is only supported as of S
|
||||
Framework 4.3. Note also that there is no need to specify `@Autowired` if the target
|
||||
bean defines only one constructor.
|
||||
|
||||
[discrete]
|
||||
[[beans-java-injecting-imported-beans-fq]]
|
||||
==== Fully-qualifying imported beans for ease of navigation
|
||||
|
||||
.[[beans-java-injecting-imported-beans-fq]]Fully-qualifying imported beans for ease of navigation
|
||||
--
|
||||
In the preceding scenario, using `@Autowired` works well and provides the desired
|
||||
modularity, but determining exactly where the autowired bean definitions are declared is
|
||||
still somewhat ambiguous. For example, as a developer looking at `ServiceConfig`, how do
|
||||
@@ -503,11 +501,49 @@ Now `ServiceConfig` is loosely coupled with respect to the concrete
|
||||
get a type hierarchy of `RepositoryConfig` implementations. In this
|
||||
way, navigating `@Configuration` classes and their dependencies becomes no different
|
||||
than the usual process of navigating interface-based code.
|
||||
--
|
||||
|
||||
TIP: If you want to influence the startup creation order of certain beans, consider
|
||||
declaring some of them as `@Lazy` (for creation on first access instead of on startup)
|
||||
or as `@DependsOn` certain other beans (making sure that specific other beans are
|
||||
created before the current bean, beyond what the latter's direct dependencies imply).
|
||||
|
||||
[[beans-java-startup]]
|
||||
== Influencing the Startup of `@Bean`-defined Singletons
|
||||
|
||||
If you want to influence the startup creation order of certain singleton beans, consider
|
||||
declaring some of them as `@Lazy` for creation on first access instead of on startup.
|
||||
|
||||
`@DependsOn` forces certain other beans to be initialized first, making sure that
|
||||
the specified beans are created before the current bean, beyond what the latter's
|
||||
direct dependencies imply.
|
||||
|
||||
[[beans-java-startup-background]]
|
||||
=== Background Initialization
|
||||
|
||||
As of 6.2, there is a background initialization option: `@Bean(bootstrap=BACKGROUND)`
|
||||
allows for singling out specific beans for background initialization, covering the
|
||||
entire bean creation step for each such bean on context startup.
|
||||
|
||||
Dependent beans with non-lazy injection points automatically wait for the bean instance
|
||||
to be completed. All regular background initializations are forced to complete at the end
|
||||
of context startup. Only beans additionally marked as `@Lazy` are allowed to be completed
|
||||
later (up until the first actual access).
|
||||
|
||||
Background initialization typically goes together with `@Lazy` (or `ObjectProvider`)
|
||||
injection points in dependent beans. Otherwise, the main bootstrap thread is going to
|
||||
block when an actual background-initialized bean instance needs to be injected early.
|
||||
|
||||
This form of concurrent startup applies to individual beans: if such a bean depends on
|
||||
other beans, they need to have been initialized already, either simply through being
|
||||
declared earlier or through `@DependsOn` which enforces initialization in the main
|
||||
bootstrap thread before background initialization for the affected bean is triggered.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
A `bootstrapExecutor` bean of type `Executor` must be declared for background
|
||||
bootstrapping to be actually active. Otherwise, the background markers will be ignored at
|
||||
runtime.
|
||||
|
||||
The bootstrap executor may be a bounded executor just for startup purposes or a shared
|
||||
thread pool which serves for other purposes as well.
|
||||
====
|
||||
|
||||
|
||||
[[beans-java-conditional]]
|
||||
@@ -541,7 +577,7 @@ Java::
|
||||
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
|
||||
if (attrs != null) {
|
||||
for (Object value : attrs.get("value")) {
|
||||
if (context.getEnvironment().matchesProfiles((String[]) value)) {
|
||||
if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -560,7 +596,7 @@ Kotlin::
|
||||
val attrs = metadata.getAllAnnotationAttributes(Profile::class.java.name)
|
||||
if (attrs != null) {
|
||||
for (value in attrs["value"]!!) {
|
||||
if (context.environment.matchesProfiles(*value as Array<String>)) {
|
||||
if (context.environment.acceptsProfiles(Profiles.of(*value as Array<String>))) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -595,18 +631,16 @@ that uses Spring XML, it is easier to create `@Configuration` classes on an
|
||||
as-needed basis and include them from the existing XML files. Later in this section, we cover the
|
||||
options for using `@Configuration` classes in this kind of "`XML-centric`" situation.
|
||||
|
||||
[discrete]
|
||||
[[beans-java-combining-xml-centric-declare-as-bean]]
|
||||
==== Declaring `@Configuration` classes as plain Spring `<bean/>` elements
|
||||
|
||||
Remember that `@Configuration` classes are ultimately bean definitions in the container.
|
||||
In this series of examples, we create a `@Configuration` class named `AppConfig` and
|
||||
.[[beans-java-combining-xml-centric-declare-as-bean]]Declaring `@Configuration` classes as plain Spring `<bean/>` elements
|
||||
--
|
||||
Remember that `@Configuration` classes are ultimately bean definitions in the
|
||||
container. In this series examples, we create a `@Configuration` class named `AppConfig` and
|
||||
include it within `system-test-config.xml` as a `<bean/>` definition. Because
|
||||
`<context:annotation-config/>` is switched on, the container recognizes the
|
||||
`@Configuration` annotation and processes the `@Bean` methods declared in `AppConfig`
|
||||
properly.
|
||||
|
||||
The following example shows the `AppConfig` configuration class in Java and Kotlin:
|
||||
The following example shows an ordinary configuration class in Java:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -627,7 +661,7 @@ Java::
|
||||
|
||||
@Bean
|
||||
public TransferService transferService() {
|
||||
return new TransferServiceImpl(accountRepository());
|
||||
return new TransferService(accountRepository());
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -660,7 +694,6 @@ The following example shows part of a sample `system-test-config.xml` file:
|
||||
<beans>
|
||||
<!-- enable processing of annotations such as @Autowired and @Configuration -->
|
||||
<context:annotation-config/>
|
||||
|
||||
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
|
||||
|
||||
<bean class="com.acme.AppConfig"/>
|
||||
@@ -707,20 +740,20 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: In the `system-test-config.xml` file, the `AppConfig` `<bean/>` does not declare an `id`
|
||||
attribute. While it would be acceptable to do so, it is unnecessary, given that no other bean
|
||||
|
||||
NOTE: In `system-test-config.xml` file, the `AppConfig` `<bean/>` does not declare an `id`
|
||||
element. While it would be acceptable to do so, it is unnecessary, given that no other bean
|
||||
ever refers to it, and it is unlikely to be explicitly fetched from the container by name.
|
||||
Similarly, the `DataSource` bean is only ever autowired by type, so an explicit bean `id`
|
||||
is not strictly required.
|
||||
--
|
||||
|
||||
[discrete]
|
||||
[[beans-java-combining-xml-centric-component-scan]]
|
||||
==== Using <context:component-scan/> to pick up `@Configuration` classes
|
||||
|
||||
.[[beans-java-combining-xml-centric-component-scan]] Using <context:component-scan/> to pick up `@Configuration` classes
|
||||
--
|
||||
Because `@Configuration` is meta-annotated with `@Component`, `@Configuration`-annotated
|
||||
classes are automatically candidates for component scanning. Using the same scenario as
|
||||
described in the previous example, we can redefine `system-test-config.xml` to take
|
||||
advantage of component-scanning. Note that, in this case, we need not explicitly declare
|
||||
described in the previous example, we can redefine `system-test-config.xml` to take advantage of component-scanning.
|
||||
Note that, in this case, we need not explicitly declare
|
||||
`<context:annotation-config/>`, because `<context:component-scan/>` enables the same
|
||||
functionality.
|
||||
|
||||
@@ -731,7 +764,6 @@ The following example shows the modified `system-test-config.xml` file:
|
||||
<beans>
|
||||
<!-- picks up and registers AppConfig as a bean definition -->
|
||||
<context:component-scan base-package="com.acme"/>
|
||||
|
||||
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
|
||||
|
||||
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
|
||||
@@ -741,17 +773,19 @@ The following example shows the modified `system-test-config.xml` file:
|
||||
</bean>
|
||||
</beans>
|
||||
----
|
||||
--
|
||||
|
||||
[[beans-java-combining-java-centric]]
|
||||
=== `@Configuration` Class-centric Use of XML with `@ImportResource`
|
||||
|
||||
In applications where `@Configuration` classes are the primary mechanism for configuring
|
||||
the container, it may still be necessary to use at least some XML. In such scenarios, you
|
||||
can use `@ImportResource` and define only as much XML as you need. Doing so achieves a
|
||||
"`Java-centric`" approach to configuring the container and keeps XML to a bare minimum.
|
||||
The following example (which includes a configuration class, an XML file that defines a
|
||||
bean, a properties file, and the `main()` method) shows how to use the `@ImportResource`
|
||||
annotation to achieve "`Java-centric`" configuration that uses XML as needed:
|
||||
the container, it is still likely necessary to use at least some XML. In these
|
||||
scenarios, you can use `@ImportResource` and define only as much XML as you need. Doing
|
||||
so achieves a "`Java-centric`" approach to configuring the container and keeps XML to a
|
||||
bare minimum. The following example (which includes a configuration class, an XML file
|
||||
that defines a bean, a properties file, and the `main` class) shows how to use
|
||||
the `@ImportResource` annotation to achieve "`Java-centric`" configuration that uses XML
|
||||
as needed:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -776,17 +810,6 @@ Java::
|
||||
public DataSource dataSource() {
|
||||
return new DriverManagerDataSource(url, username, password);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AccountRepository accountRepository(DataSource dataSource) {
|
||||
return new JdbcAccountRepository(dataSource);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TransferService transferService(AccountRepository accountRepository) {
|
||||
return new TransferServiceImpl(accountRepository);
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
@@ -811,32 +834,21 @@ Kotlin::
|
||||
fun dataSource(): DataSource {
|
||||
return DriverManagerDataSource(url, username, password)
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun accountRepository(dataSource: DataSource): AccountRepository {
|
||||
return JdbcAccountRepository(dataSource)
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun transferService(accountRepository: AccountRepository): TransferService {
|
||||
return TransferServiceImpl(accountRepository)
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
.properties-config.xml
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
properties-config.xml
|
||||
<beans>
|
||||
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
|
||||
</beans>
|
||||
----
|
||||
|
||||
.jdbc.properties
|
||||
[literal,subs="verbatim,quotes"]
|
||||
----
|
||||
jdbc.properties
|
||||
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
|
||||
jdbc.username=sa
|
||||
jdbc.password=
|
||||
@@ -869,3 +881,5 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,219 +1,34 @@
|
||||
[[expressions-beandef]]
|
||||
= Expressions in Bean Definitions
|
||||
|
||||
You can use SpEL expressions with XML-based or annotation-based configuration metadata for
|
||||
defining `BeanDefinition` instances. In both cases, the syntax to define the expression is of the
|
||||
form `#{ <expression string> }`.
|
||||
|
||||
|
||||
|
||||
[[expressions-beandef-xml-based]]
|
||||
== XML Configuration
|
||||
|
||||
A property or constructor argument value can be set by using expressions, as the following
|
||||
example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<bean id="numberGuess" class="org.spring.samples.NumberGuess">
|
||||
<property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
|
||||
|
||||
<!-- other properties -->
|
||||
</bean>
|
||||
----
|
||||
You can use SpEL expressions with configuration metadata for defining bean instances. In both
|
||||
cases, the syntax to define the expression is of the form `#{ <expression string> }`.
|
||||
|
||||
All beans in the application context are available as predefined variables with their
|
||||
common bean name. This includes standard context beans such as `environment` (of type
|
||||
`org.springframework.core.env.Environment`) as well as `systemProperties` and
|
||||
`systemEnvironment` (of type `Map<String, Object>`) for access to the runtime environment.
|
||||
|
||||
The following example shows access to the `systemProperties` bean as a SpEL variable:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<bean id="taxCalculator" class="org.spring.samples.TaxCalculator">
|
||||
<property name="defaultLocale" value="#{ systemProperties['user.region'] }"/>
|
||||
|
||||
<!-- other properties -->
|
||||
</bean>
|
||||
----
|
||||
|
||||
Note that you do not have to prefix the predefined variable with the `#` symbol here.
|
||||
|
||||
You can also refer to other bean properties by name, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<bean id="numberGuess" class="org.spring.samples.NumberGuess">
|
||||
<property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
|
||||
|
||||
<!-- other properties -->
|
||||
</bean>
|
||||
|
||||
<bean id="shapeGuess" class="org.spring.samples.ShapeGuess">
|
||||
<property name="initialShapeSeed" value="#{ numberGuess.randomNumber }"/>
|
||||
|
||||
<!-- other properties -->
|
||||
</bean>
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[expressions-beandef-annotation-based]]
|
||||
== Annotation Configuration
|
||||
|
||||
To specify a default value, you can place the `@Value` annotation on fields, methods,
|
||||
and method or constructor parameters.
|
||||
and method or constructor parameters (or XML equivalent).
|
||||
|
||||
The following example sets the default value of a field:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class FieldValueTestBean {
|
||||
include-code::./FieldValueTestBean[tag=snippet,indent=0]
|
||||
|
||||
@Value("#{ systemProperties['user.region'] }")
|
||||
private String defaultLocale;
|
||||
|
||||
public void setDefaultLocale(String defaultLocale) {
|
||||
this.defaultLocale = defaultLocale;
|
||||
}
|
||||
|
||||
public String getDefaultLocale() {
|
||||
return this.defaultLocale;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class FieldValueTestBean {
|
||||
|
||||
@Value("#{ systemProperties['user.region'] }")
|
||||
var defaultLocale: String? = null
|
||||
}
|
||||
----
|
||||
======
|
||||
Note that you do not have to prefix the predefined variable with the `#` symbol here.
|
||||
|
||||
The following example shows the equivalent but on a property setter method:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class PropertyValueTestBean {
|
||||
|
||||
private String defaultLocale;
|
||||
|
||||
@Value("#{ systemProperties['user.region'] }")
|
||||
public void setDefaultLocale(String defaultLocale) {
|
||||
this.defaultLocale = defaultLocale;
|
||||
}
|
||||
|
||||
public String getDefaultLocale() {
|
||||
return this.defaultLocale;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class PropertyValueTestBean {
|
||||
|
||||
@Value("#{ systemProperties['user.region'] }")
|
||||
var defaultLocale: String? = null
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./PropertyValueTestBean[tag=snippet,indent=0]
|
||||
|
||||
Autowired methods and constructors can also use the `@Value` annotation, as the following
|
||||
examples show:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class SimpleMovieLister {
|
||||
|
||||
private MovieFinder movieFinder;
|
||||
private String defaultLocale;
|
||||
|
||||
@Autowired
|
||||
public void configure(MovieFinder movieFinder,
|
||||
@Value("#{ systemProperties['user.region'] }") String defaultLocale) {
|
||||
this.movieFinder = movieFinder;
|
||||
this.defaultLocale = defaultLocale;
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class SimpleMovieLister {
|
||||
|
||||
private lateinit var movieFinder: MovieFinder
|
||||
private lateinit var defaultLocale: String
|
||||
|
||||
@Autowired
|
||||
fun configure(movieFinder: MovieFinder,
|
||||
@Value("#{ systemProperties['user.region'] }") defaultLocale: String) {
|
||||
this.movieFinder = movieFinder
|
||||
this.defaultLocale = defaultLocale
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class MovieRecommender {
|
||||
|
||||
private String defaultLocale;
|
||||
|
||||
private CustomerPreferenceDao customerPreferenceDao;
|
||||
|
||||
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao,
|
||||
@Value("#{systemProperties['user.country']}") String defaultLocale) {
|
||||
this.customerPreferenceDao = customerPreferenceDao;
|
||||
this.defaultLocale = defaultLocale;
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class MovieRecommender(private val customerPreferenceDao: CustomerPreferenceDao,
|
||||
@Value("#{systemProperties['user.country']}") private val defaultLocale: String) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
include-code::./SimpleMovieLister[tag=snippet,indent=0]
|
||||
|
||||
include-code::./MovieRecommender[tag=snippet,indent=0]
|
||||
|
||||
You can also refer to other bean properties by name, as the following example shows:
|
||||
|
||||
include-code::./ShapeGuess[tag=snippet,indent=0]
|
||||
|
||||
@@ -222,38 +222,29 @@ Kotlin::
|
||||
[[expressions-evaluation-context]]
|
||||
== Understanding `EvaluationContext`
|
||||
|
||||
The `EvaluationContext` API is used when evaluating an expression to resolve properties,
|
||||
methods, or fields and to help perform type conversion. Spring provides two
|
||||
The `EvaluationContext` interface is used when evaluating an expression to resolve
|
||||
properties, methods, or fields and to help perform type conversion. Spring provides two
|
||||
implementations.
|
||||
|
||||
`SimpleEvaluationContext`::
|
||||
Exposes a subset of essential SpEL language features and configuration options, for
|
||||
categories of expressions that do not require the full extent of the SpEL language
|
||||
syntax and should be meaningfully restricted. Examples include but are not limited to
|
||||
data binding expressions and property-based filters.
|
||||
* `SimpleEvaluationContext`: Exposes a subset of essential SpEL language features and
|
||||
configuration options, for categories of expressions that do not require the full extent
|
||||
of the SpEL language syntax and should be meaningfully restricted. Examples include but
|
||||
are not limited to data binding expressions and property-based filters.
|
||||
|
||||
`StandardEvaluationContext`::
|
||||
Exposes the full set of SpEL language features and configuration options. You can use
|
||||
it to specify a default root object and to configure every available evaluation-related
|
||||
strategy.
|
||||
* `StandardEvaluationContext`: Exposes the full set of SpEL language features and
|
||||
configuration options. You can use it to specify a default root object and to configure
|
||||
every available evaluation-related strategy.
|
||||
|
||||
`SimpleEvaluationContext` is designed to support only a subset of the SpEL language
|
||||
syntax. For example, it excludes Java type references, constructors, and bean references.
|
||||
It also requires you to explicitly choose the level of support for properties and methods
|
||||
in expressions. When creating a `SimpleEvaluationContext` you need to choose the level of
|
||||
support that you need for data binding in SpEL expressions:
|
||||
`SimpleEvaluationContext` is designed to support only a subset of the SpEL language syntax.
|
||||
It excludes Java type references, constructors, and bean references. It also requires
|
||||
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.
|
||||
|
||||
* Data binding for read-only access
|
||||
* Data binding for read and write access
|
||||
* A custom `PropertyAccessor` (typically not reflection-based), potentially combined with
|
||||
a `DataBindingPropertyAccessor`
|
||||
|
||||
Conveniently, `SimpleEvaluationContext.forReadOnlyDataBinding()` enables read-only access
|
||||
to properties via `DataBindingPropertyAccessor`. Similarly,
|
||||
`SimpleEvaluationContext.forReadWriteDataBinding()` enables read and write access to
|
||||
properties. Alternatively, configure custom accessors via
|
||||
`SimpleEvaluationContext.forPropertyAccessors(...)`, potentially disable assignment, and
|
||||
optionally activate method resolution and/or a type converter through the builder.
|
||||
* Custom `PropertyAccessor` only (no reflection)
|
||||
* Data binding properties for read-only access
|
||||
* Data binding properties for read and write
|
||||
|
||||
|
||||
[[expressions-type-conversion]]
|
||||
@@ -323,17 +314,17 @@ Kotlin::
|
||||
It is possible to configure the SpEL expression parser by using a parser configuration
|
||||
object (`org.springframework.expression.spel.SpelParserConfiguration`). The configuration
|
||||
object controls the behavior of some of the expression components. For example, if you
|
||||
index into a collection and the element at the specified index is `null`, SpEL can
|
||||
automatically create the element. This is useful when using expressions made up of a
|
||||
chain of property references. Similarly, if you index into a collection and specify an
|
||||
index that is greater than the current size of the collection, SpEL can automatically
|
||||
grow the collection to accommodate that index. In order to add an element at the
|
||||
index into an array or collection and the element at the specified index is `null`, SpEL
|
||||
can automatically create the element. This is useful when using expressions made up of a
|
||||
chain of property references. If you index into an array or list and specify an index
|
||||
that is beyond the end of the current size of the array or list, SpEL can automatically
|
||||
grow the array or list to accommodate that index. In order to add an element at the
|
||||
specified index, SpEL will try to create the element using the element type's default
|
||||
constructor before setting the specified value. If the element type does not have a
|
||||
default constructor, `null` will be added to the collection. If there is no built-in
|
||||
converter or custom converter that knows how to set the value, `null` will remain in the
|
||||
collection at the specified index. The following example demonstrates how to
|
||||
automatically grow a `List`.
|
||||
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.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -525,7 +516,7 @@ following kinds of expressions cannot be compiled.
|
||||
|
||||
* Expressions involving assignment
|
||||
* Expressions relying on the conversion service
|
||||
* Expressions using custom resolvers or accessors
|
||||
* Expressions using custom resolvers
|
||||
* Expressions using overloaded operators
|
||||
* Expressions using array construction syntax
|
||||
* Expressions using selection or projection
|
||||
|
||||
+60
-1
@@ -1,7 +1,7 @@
|
||||
[[expressions-operator-safe-navigation]]
|
||||
= Safe Navigation Operator
|
||||
|
||||
The safe navigation operator (`?`) is used to avoid a `NullPointerException` and comes
|
||||
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
|
||||
@@ -81,6 +81,65 @@ For example, the expression `#calculator?.max(4, 2)` evaluates to `null` if the
|
||||
`max(int, int)` method will be invoked on the `#calculator`.
|
||||
====
|
||||
|
||||
[[expressions-operator-safe-navigation-indexing]]
|
||||
== Safe Index Access
|
||||
|
||||
Since Spring Framework 6.2, the Spring Expression Language supports safe navigation for
|
||||
indexing into the following types of structures.
|
||||
|
||||
* xref:core/expressions/language-ref/properties-arrays.adoc#expressions-indexing-arrays-and-collections[arrays and collections]
|
||||
* xref:core/expressions/language-ref/properties-arrays.adoc#expressions-indexing-strings[strings]
|
||||
* xref:core/expressions/language-ref/properties-arrays.adoc#expressions-indexing-maps[maps]
|
||||
* xref:core/expressions/language-ref/properties-arrays.adoc#expressions-indexing-objects[objects]
|
||||
* xref:core/expressions/language-ref/properties-arrays.adoc#expressions-indexing-custom[custom]
|
||||
|
||||
The following example shows how to use the safe navigation operator for indexing into
|
||||
a list (`?.[]`).
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
IEEE society = new IEEE();
|
||||
EvaluationContext context = new StandardEvaluationContext(society);
|
||||
|
||||
// evaluates to Inventor("Nikola Tesla")
|
||||
Inventor inventor = parser.parseExpression("members?.[0]") // <1>
|
||||
.getValue(context, Inventor.class);
|
||||
|
||||
society.members = null;
|
||||
|
||||
// evaluates to null - does not throw an exception
|
||||
inventor = parser.parseExpression("members?.[0]") // <2>
|
||||
.getValue(context, Inventor.class);
|
||||
----
|
||||
<1> Use null-safe index operator on a non-null `members` list
|
||||
<2> Use null-safe index operator on a 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 Inventor("Nikola Tesla")
|
||||
var inventor = parser.parseExpression("members?.[0]") // <1>
|
||||
.getValue(context, Inventor::class.java)
|
||||
|
||||
society.members = null
|
||||
|
||||
// evaluates to null - does not throw an exception
|
||||
inventor = parser.parseExpression("members?.[0]") // <2>
|
||||
.getValue(context, Inventor::class.java)
|
||||
----
|
||||
<1> Use null-safe index operator on a non-null `members` list
|
||||
<2> Use null-safe index operator on a null `members` list
|
||||
======
|
||||
|
||||
[[expressions-operator-safe-navigation-selection-and-projection]]
|
||||
== Safe Collection Selection and Projection
|
||||
|
||||
+258
-36
@@ -1,11 +1,25 @@
|
||||
[[expressions-properties-arrays]]
|
||||
= Properties, Arrays, Lists, Maps, and Indexers
|
||||
|
||||
Navigating with property references is easy. To do so, use a period to indicate a nested
|
||||
property value. The instances of the `Inventor` class, `pupin` and `tesla`, were
|
||||
populated with data listed in the xref:core/expressions/example-classes.adoc[Classes used in the examples]
|
||||
section. To navigate "down" the object graph and get Tesla's year of birth and
|
||||
Pupin's city of birth, we use the following expressions:
|
||||
The Spring Expression Language provides support for navigating object graphs and indexing
|
||||
into various structures.
|
||||
|
||||
NOTE: Numerical index values are zero-based, such as when accessing the n^th^ element of
|
||||
an array in Java.
|
||||
|
||||
TIP: See the xref:core/expressions/language-ref/operator-safe-navigation.adoc[Safe Navigation Operator]
|
||||
section for details on how to navigate object graphs and index into various structures
|
||||
using the null-safe operator.
|
||||
|
||||
[[expressions-property-navigation]]
|
||||
== Property Navigation
|
||||
|
||||
You can navigate property references within an object graph by using a period to indicate
|
||||
a nested property value. The instances of the `Inventor` class, `pupin` and `tesla`, were
|
||||
populated with data listed in the
|
||||
xref:core/expressions/example-classes.adoc[Classes used in the examples] section. To
|
||||
navigate _down_ the object graph and get Tesla's year of birth and Pupin's city of birth,
|
||||
we use the following expressions:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -16,6 +30,7 @@ Java::
|
||||
// evaluates to 1856
|
||||
int year = (Integer) parser.parseExpression("birthdate.year + 1900").getValue(context);
|
||||
|
||||
// evaluates to "Smiljan"
|
||||
String city = (String) parser.parseExpression("placeOfBirth.city").getValue(context);
|
||||
----
|
||||
|
||||
@@ -26,6 +41,7 @@ Kotlin::
|
||||
// evaluates to 1856
|
||||
val year = parser.parseExpression("birthdate.year + 1900").getValue(context) as Int
|
||||
|
||||
// evaluates to "Smiljan"
|
||||
val city = parser.parseExpression("placeOfBirth.city").getValue(context) as String
|
||||
----
|
||||
======
|
||||
@@ -39,8 +55,20 @@ method invocations -- for example, `getPlaceOfBirth().getCity()` instead of
|
||||
`placeOfBirth.city`.
|
||||
====
|
||||
|
||||
The contents of arrays and lists are obtained by using square bracket notation, as the
|
||||
following example shows:
|
||||
[[expressions-indexing-arrays-and-collections]]
|
||||
== Indexing into Arrays and Collections
|
||||
|
||||
The n^th^ element of an array or collection (for example, a `Set` or `List`) can be
|
||||
obtained by using square bracket notation, as the following example shows.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
If the indexed collection is a `java.util.List`, the n^th^ element will be accessed
|
||||
directly via `list.get(n)`.
|
||||
|
||||
For any other type of `Collection`, the n^th^ element will be accessed by iterating over
|
||||
the collection using its `Iterator` and returning the n^th^ element encountered.
|
||||
====
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -63,7 +91,8 @@ Java::
|
||||
String name = parser.parseExpression("members[0].name").getValue(
|
||||
context, ieee, String.class);
|
||||
|
||||
// List and Array navigation
|
||||
// List and Array Indexing
|
||||
|
||||
// evaluates to "Wireless communication"
|
||||
String invention = parser.parseExpression("members[0].inventions[6]").getValue(
|
||||
context, ieee, String.class);
|
||||
@@ -88,16 +117,22 @@ Kotlin::
|
||||
val name = parser.parseExpression("members[0].name").getValue(
|
||||
context, ieee, String::class.java)
|
||||
|
||||
// List and Array navigation
|
||||
// List and Array Indexing
|
||||
|
||||
// evaluates to "Wireless communication"
|
||||
val invention = parser.parseExpression("members[0].inventions[6]").getValue(
|
||||
context, ieee, String::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
The contents of maps are obtained by specifying the literal key value within the
|
||||
brackets. In the following example, because keys for the `officers` map are strings, we can specify
|
||||
string literals:
|
||||
[[expressions-indexing-strings]]
|
||||
== Indexing into Strings
|
||||
|
||||
The n^th^ character of a string can be obtained by specifying the index within square
|
||||
brackets, as demonstrated in the following example.
|
||||
|
||||
NOTE: The n^th^ character of a string will evaluate to a `java.lang.String`, not a
|
||||
`java.lang.Character`.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -105,38 +140,225 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// Officer's Dictionary
|
||||
|
||||
Inventor pupin = parser.parseExpression("officers['president']").getValue(
|
||||
societyContext, Inventor.class);
|
||||
|
||||
// evaluates to "Idvor"
|
||||
String city = parser.parseExpression("officers['president'].placeOfBirth.city").getValue(
|
||||
societyContext, String.class);
|
||||
|
||||
// setting values
|
||||
parser.parseExpression("officers['advisors'][0].placeOfBirth.country").setValue(
|
||||
societyContext, "Croatia");
|
||||
// evaluates to "T" (8th letter of "Nikola Tesla")
|
||||
String character = parser.parseExpression("members[0].name[7]")
|
||||
.getValue(societyContext, String.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// Officer's Dictionary
|
||||
|
||||
val pupin = parser.parseExpression("officers['president']").getValue(
|
||||
societyContext, Inventor::class.java)
|
||||
|
||||
// evaluates to "Idvor"
|
||||
val city = parser.parseExpression("officers['president'].placeOfBirth.city").getValue(
|
||||
societyContext, String::class.java)
|
||||
|
||||
// setting values
|
||||
parser.parseExpression("officers['advisors'][0].placeOfBirth.country").setValue(
|
||||
societyContext, "Croatia")
|
||||
// evaluates to "T" (8th letter of "Nikola Tesla")
|
||||
val character = parser.parseExpression("members[0].name[7]")
|
||||
.getValue(societyContext, String::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
[[expressions-indexing-maps]]
|
||||
== Indexing into Maps
|
||||
|
||||
The contents of maps are obtained by specifying the key value within square brackets. In
|
||||
the following example, because keys for the `officers` map are strings, we can specify
|
||||
string literals such as `'president'`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// Officer's Map
|
||||
|
||||
// evaluates to Inventor("Pupin")
|
||||
Inventor pupin = parser.parseExpression("officers['president']")
|
||||
.getValue(societyContext, Inventor.class);
|
||||
|
||||
// evaluates to "Idvor"
|
||||
String city = parser.parseExpression("officers['president'].placeOfBirth.city")
|
||||
.getValue(societyContext, String.class);
|
||||
|
||||
String countryExpression = "officers['advisors'][0].placeOfBirth.country";
|
||||
|
||||
// setting values
|
||||
parser.parseExpression(countryExpression)
|
||||
.setValue(societyContext, "Croatia");
|
||||
|
||||
// evaluates to "Croatia"
|
||||
String country = parser.parseExpression(countryExpression)
|
||||
.getValue(societyContext, String.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// Officer's Map
|
||||
|
||||
// evaluates to Inventor("Pupin")
|
||||
val pupin = parser.parseExpression("officers['president']")
|
||||
.getValue(societyContext, Inventor::class.java)
|
||||
|
||||
// evaluates to "Idvor"
|
||||
val city = parser.parseExpression("officers['president'].placeOfBirth.city")
|
||||
.getValue(societyContext, String::class.java)
|
||||
|
||||
val countryExpression = "officers['advisors'][0].placeOfBirth.country"
|
||||
|
||||
// setting values
|
||||
parser.parseExpression(countryExpression)
|
||||
.setValue(societyContext, "Croatia")
|
||||
|
||||
// evaluates to "Croatia"
|
||||
val country = parser.parseExpression(countryExpression)
|
||||
.getValue(societyContext, String::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
[[expressions-indexing-objects]]
|
||||
== Indexing into Objects
|
||||
|
||||
A property of an object can be obtained by specifying the name of the property within
|
||||
square brackets. This is analogous to accessing the value of a map based on its key. The
|
||||
following example demonstrates how to _index_ into an object to retrieve a specific
|
||||
property.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// Create an inventor to use as the root context object.
|
||||
Inventor tesla = new Inventor("Nikola Tesla");
|
||||
|
||||
// evaluates to "Nikola Tesla"
|
||||
String name = parser.parseExpression("#root['name']")
|
||||
.getValue(context, tesla, String.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// Create an inventor to use as the root context object.
|
||||
val tesla = Inventor("Nikola Tesla")
|
||||
|
||||
// evaluates to "Nikola Tesla"
|
||||
val name = parser.parseExpression("#root['name']")
|
||||
.getValue(context, tesla, String::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
[[expressions-indexing-custom]]
|
||||
== Indexing into Custom Structures
|
||||
|
||||
Since Spring Framework 6.2, the Spring Expression Language supports indexing into custom
|
||||
structures by allowing developers to implement and register an `IndexAccessor` with the
|
||||
`EvaluationContext`. If you would like to support
|
||||
xref:core/expressions/evaluation.adoc#expressions-spel-compilation[compilation] of
|
||||
expressions that rely on a custom index accessor, that index accessor must implement the
|
||||
`CompilableIndexAccessor` SPI.
|
||||
|
||||
To support common use cases, Spring provides a built-in `ReflectiveIndexAccessor` which
|
||||
is a flexible `IndexAccessor` that uses reflection to read from and optionally write to
|
||||
an indexed structure of a target object. The indexed structure can be accessed through a
|
||||
`public` read-method (when being read) or a `public` write-method (when being written).
|
||||
The relationship between the read-method and write-method is based on a convention that
|
||||
is applicable for typical implementations of indexed structures.
|
||||
|
||||
NOTE: `ReflectiveIndexAccessor` also implements `CompilableIndexAccessor` in order to
|
||||
support xref:core/expressions/evaluation.adoc#expressions-spel-compilation[compilation]
|
||||
to bytecode for read access. Note, however, that the configured read-method must be
|
||||
invokable via a `public` class or `public` interface for compilation to succeed.
|
||||
|
||||
The following code listings define a `Color` enum and `FruitMap` type that behaves like a
|
||||
map but does not implement the `java.util.Map` interface. Thus, if you want to index into
|
||||
a `FruitMap` within a SpEL expression, you will need to register an `IndexAccessor`.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
package example;
|
||||
|
||||
public enum Color {
|
||||
RED, ORANGE, YELLOW
|
||||
}
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public class FruitMap {
|
||||
|
||||
private final Map<Color, String> map = new HashMap<>();
|
||||
|
||||
public FruitMap() {
|
||||
this.map.put(Color.RED, "cherry");
|
||||
this.map.put(Color.ORANGE, "orange");
|
||||
this.map.put(Color.YELLOW, "banana");
|
||||
}
|
||||
|
||||
public String getFruit(Color color) {
|
||||
return this.map.get(color);
|
||||
}
|
||||
|
||||
public void setFruit(Color color, String fruit) {
|
||||
this.map.put(color, fruit);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
A read-only `IndexAccessor` for `FruitMap` can be created via `new
|
||||
ReflectiveIndexAccessor(FruitMap.class, Color.class, "getFruit")`. With that accessor
|
||||
registered and a `FruitMap` registered as a variable named `#fruitMap`, the SpEL
|
||||
expression `#fruitMap[T(example.Color).RED]` will evaluate to `"cherry"`.
|
||||
|
||||
A read-write `IndexAccessor` for `FruitMap` can be created via `new
|
||||
ReflectiveIndexAccessor(FruitMap.class, Color.class, "getFruit", "setFruit")`. With that
|
||||
accessor registered and a `FruitMap` registered as a variable named `#fruitMap`, the SpEL
|
||||
expression `#fruitMap[T(example.Color).RED] = 'strawberry'` can be used to change the
|
||||
fruit mapping for the color red from `"cherry"` to `"strawberry"`.
|
||||
|
||||
The following example demonstrates how to register a `ReflectiveIndexAccessor` to index
|
||||
into a `FruitMap` and then index into the `FruitMap` within a SpEL expression.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// Create a ReflectiveIndexAccessor for FruitMap
|
||||
IndexAccessor fruitMapAccessor = new ReflectiveIndexAccessor(
|
||||
FruitMap.class, Color.class, "getFruit", "setFruit");
|
||||
|
||||
// Register the IndexAccessor for FruitMap
|
||||
context.addIndexAccessor(fruitMapAccessor);
|
||||
|
||||
// Register the fruitMap variable
|
||||
context.setVariable("fruitMap", new FruitMap());
|
||||
|
||||
// evaluates to "cherry"
|
||||
String fruit = parser.parseExpression("#fruitMap[T(example.Color).RED]")
|
||||
.getValue(context, String.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// Create a ReflectiveIndexAccessor for FruitMap
|
||||
val fruitMapAccessor = ReflectiveIndexAccessor(
|
||||
FruitMap::class.java, Color::class.java, "getFruit", "setFruit")
|
||||
|
||||
// Register the IndexAccessor for FruitMap
|
||||
context.addIndexAccessor(fruitMapAccessor)
|
||||
|
||||
// Register the fruitMap variable
|
||||
context.setVariable("fruitMap", FruitMap())
|
||||
|
||||
// evaluates to "cherry"
|
||||
val fruit = parser.parseExpression("#fruitMap[T(example.Color).RED]")
|
||||
.getValue(context, String::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
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
|
||||
define. A common choice is to use `#{ }` as the delimiters, as the following example
|
||||
define. A common choice is to use `+#{ }+` as the delimiters, as the following example
|
||||
shows:
|
||||
|
||||
[tabs]
|
||||
@@ -32,7 +32,7 @@ Kotlin::
|
||||
======
|
||||
|
||||
The string is evaluated by concatenating the literal text `'random number is '` with the
|
||||
result of evaluating the expression inside the `#{ }` delimiters (in this case, the
|
||||
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.
|
||||
|
||||
@@ -196,7 +196,7 @@ Kotlin::
|
||||
|
||||
When used as `org.springframework.validation.Validator`, `LocalValidatorFactoryBean`
|
||||
invokes the underlying `jakarta.validation.Validator`, and then adapts
|
||||
``ConstraintViolation``s to ``FieldError``s, and registers them with the `Errors` object
|
||||
``ContraintViolation``s to ``FieldError``s, and registers them with the `Errors` object
|
||||
passed into the `validate` method.
|
||||
|
||||
|
||||
@@ -299,7 +299,7 @@ Java::
|
||||
public class AppConfig {
|
||||
|
||||
@Bean
|
||||
public static MethodValidationPostProcessor validationPostProcessor() {
|
||||
public MethodValidationPostProcessor validationPostProcessor() {
|
||||
return new MethodValidationPostProcessor();
|
||||
}
|
||||
}
|
||||
@@ -341,7 +341,7 @@ xref:web/webflux/ann-rest-exceptions.adoc[Error Responses] sections.
|
||||
=== Method Validation Exceptions
|
||||
|
||||
By default, `jakarta.validation.ConstraintViolationException` is raised with the set of
|
||||
``ConstraintViolation``s returned by `jakarta.validation.Validator`. As an alternative,
|
||||
``ConstraintViolation``s returned by `jakarata.validation.Validator`. As an alternative,
|
||||
you can have `MethodValidationException` raised instead with ``ConstraintViolation``s
|
||||
adapted to `MessageSourceResolvable` errors. To enable set the following flag:
|
||||
|
||||
@@ -357,7 +357,7 @@ Java::
|
||||
public class AppConfig {
|
||||
|
||||
@Bean
|
||||
public static MethodValidationPostProcessor validationPostProcessor() {
|
||||
public MethodValidationPostProcessor validationPostProcessor() {
|
||||
MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
|
||||
processor.setAdaptConstraintViolations(true);
|
||||
return processor;
|
||||
@@ -431,22 +431,22 @@ Kotlin::
|
||||
|
||||
A `ConstraintViolation` on `Person.name()` is adapted to a `FieldError` with the following:
|
||||
|
||||
- Error codes `"Size.person.name"`, `"Size.name"`, `"Size.java.lang.String"`, and `"Size"`
|
||||
- Error codes `"Size.student.name"`, `"Size.name"`, `"Size.java.lang.String"`, and `"Size"`
|
||||
- Message arguments `"name"`, `10`, and `1` (the field name and the constraint attributes)
|
||||
- Default message "size must be between 1 and 10"
|
||||
|
||||
To customize the default message, you can add properties to
|
||||
xref:core/beans/context-introduction.adoc#context-functionality-messagesource[MessageSource]
|
||||
resource bundles using any of the above errors codes and message arguments. Note also that the
|
||||
message argument `"name"` is itself a `MessagreSourceResolvable` with error codes
|
||||
`"person.name"` and `"name"` and can customized too. For example:
|
||||
message argument `"name"` is itself a `MessageSourceResolvable` with error codes
|
||||
`"student.name"` and `"name"` and can customized too. For example:
|
||||
|
||||
Properties::
|
||||
+
|
||||
[source,properties,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
Size.person.name=Please, provide a {0} that is between {2} and {1} characters long
|
||||
person.name=username
|
||||
Size.student.name=Please, provide a {0} that is between {2} and {1} characters long
|
||||
student.name=username
|
||||
----
|
||||
|
||||
A `ConstraintViolation` on the `degrees` method parameter is adapted to a
|
||||
|
||||
+2
-99
@@ -11,106 +11,9 @@ formatters manually with the help of:
|
||||
* `org.springframework.format.datetime.standard.DateTimeFormatterRegistrar`
|
||||
* `org.springframework.format.datetime.DateFormatterRegistrar`
|
||||
|
||||
For example, the following Java configuration registers a global `yyyyMMdd` format:
|
||||
For example, the following configuration registers a global `yyyyMMdd` format:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
|
||||
@Bean
|
||||
public FormattingConversionService conversionService() {
|
||||
|
||||
// Use the DefaultFormattingConversionService but do not register defaults
|
||||
DefaultFormattingConversionService conversionService =
|
||||
new DefaultFormattingConversionService(false);
|
||||
|
||||
// Ensure @NumberFormat is still supported
|
||||
conversionService.addFormatterForFieldAnnotation(
|
||||
new NumberFormatAnnotationFormatterFactory());
|
||||
|
||||
// Register JSR-310 date conversion with a specific global format
|
||||
DateTimeFormatterRegistrar dateTimeRegistrar = new DateTimeFormatterRegistrar();
|
||||
dateTimeRegistrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"));
|
||||
dateTimeRegistrar.registerFormatters(conversionService);
|
||||
|
||||
// Register date conversion with a specific global format
|
||||
DateFormatterRegistrar dateRegistrar = new DateFormatterRegistrar();
|
||||
dateRegistrar.setFormatter(new DateFormatter("yyyyMMdd"));
|
||||
dateRegistrar.registerFormatters(conversionService);
|
||||
|
||||
return conversionService;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class AppConfig {
|
||||
|
||||
@Bean
|
||||
fun conversionService(): FormattingConversionService {
|
||||
// Use the DefaultFormattingConversionService but do not register defaults
|
||||
return DefaultFormattingConversionService(false).apply {
|
||||
|
||||
// Ensure @NumberFormat is still supported
|
||||
addFormatterForFieldAnnotation(NumberFormatAnnotationFormatterFactory())
|
||||
|
||||
// Register JSR-310 date conversion with a specific global format
|
||||
val dateTimeRegistrar = DateTimeFormatterRegistrar()
|
||||
dateTimeRegistrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"))
|
||||
dateTimeRegistrar.registerFormatters(this)
|
||||
|
||||
// Register date conversion with a specific global format
|
||||
val dateRegistrar = DateFormatterRegistrar()
|
||||
dateRegistrar.setFormatter(DateFormatter("yyyyMMdd"))
|
||||
dateRegistrar.registerFormatters(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
If you prefer XML-based configuration, you can use a
|
||||
`FormattingConversionServiceFactoryBean`. The following example shows how to do so:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
|
||||
<property name="registerDefaultFormatters" value="false" />
|
||||
<property name="formatters">
|
||||
<set>
|
||||
<bean class="org.springframework.format.number.NumberFormatAnnotationFormatterFactory" />
|
||||
</set>
|
||||
</property>
|
||||
<property name="formatterRegistrars">
|
||||
<set>
|
||||
<bean class="org.springframework.format.datetime.standard.DateTimeFormatterRegistrar">
|
||||
<property name="dateFormatter">
|
||||
<bean class="org.springframework.format.datetime.standard.DateTimeFormatterFactoryBean">
|
||||
<property name="pattern" value="yyyyMMdd"/>
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
</set>
|
||||
</property>
|
||||
</bean>
|
||||
</beans>
|
||||
----
|
||||
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
|
||||
|
||||
Note there are extra considerations when configuring date and time formats in web
|
||||
applications. Please see
|
||||
|
||||
@@ -280,11 +280,10 @@ Kotlin::
|
||||
|
||||
A portable format annotation API exists in the `org.springframework.format.annotation`
|
||||
package. You can use `@NumberFormat` to format `Number` fields such as `Double` and
|
||||
`Long`, and `@DateTimeFormat` to format fields such as `java.util.Date`,
|
||||
`java.util.Calendar`, and `Long` (for millisecond timestamps) as well as JSR-310
|
||||
`java.time` types.
|
||||
`Long`, and `@DateTimeFormat` to format `java.util.Date`, `java.util.Calendar`, `Long`
|
||||
(for millisecond timestamps) as well as JSR-310 `java.time`.
|
||||
|
||||
The following example uses `@DateTimeFormat` to format a `java.util.Date` as an ISO date
|
||||
The following example uses `@DateTimeFormat` to format a `java.util.Date` as an ISO Date
|
||||
(yyyy-MM-dd):
|
||||
|
||||
[tabs]
|
||||
@@ -310,28 +309,6 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
For further details, see the javadoc for
|
||||
{spring-framework-api}/format/annotation/DateTimeFormat.html[`@DateTimeFormat`] and
|
||||
{spring-framework-api}/format/annotation/NumberFormat.html[`@NumberFormat`].
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
Style-based formatting and parsing rely on locale-sensitive patterns which may change
|
||||
depending on the Java runtime. Specifically, applications that rely on date, time, or
|
||||
number parsing and formatting may encounter incompatible changes in behavior when running
|
||||
on JDK 20 or higher.
|
||||
|
||||
Using an ISO standardized format or a concrete pattern that you control allows for
|
||||
reliable system-independent and locale-independent parsing and formatting of date, time,
|
||||
and number values.
|
||||
|
||||
For `@DateTimeFormat`, the use of fallback patterns can also help to address
|
||||
compatibility issues.
|
||||
|
||||
For further details, see the
|
||||
https://github.com/spring-projects/spring-framework/wiki/Date-and-Time-Formatting-with-JDK-20-and-higher[Date and Time Formatting with JDK 20 and higher]
|
||||
page in the Spring Framework wiki.
|
||||
====
|
||||
|
||||
[[format-FormatterRegistry-SPI]]
|
||||
== The `FormatterRegistry` SPI
|
||||
|
||||
@@ -198,7 +198,7 @@ methods it offers can be found in the {spring-framework-api}/validation/Errors.h
|
||||
Validators may also get locally invoked for the immediate validation of a given object,
|
||||
not involving a binding process. As of 6.1, this has been simplified through a new
|
||||
`Validator.validateObject(Object)` method which is available by default now, returning
|
||||
a simple `Errors` representation which can be inspected: typically calling `hasErrors()`
|
||||
a simple ´Errors` representation which can be inspected: typically calling `hasErrors()`
|
||||
or the new `failOnError` method for turning the error summary message into an exception
|
||||
(e.g. `validator.validateObject(myObject).failOnError(IllegalArgumentException::new)`).
|
||||
|
||||
|
||||
@@ -211,28 +211,19 @@ the JDBC driver. If the count is not available, the JDBC driver returns a value
|
||||
====
|
||||
In such a scenario, with automatic setting of values on an underlying `PreparedStatement`,
|
||||
the corresponding JDBC type for each value needs to be derived from the given Java type.
|
||||
While this usually works well, there is a potential for issues (for example, with
|
||||
Map-contained `null` values). Spring, by default, calls `ParameterMetaData.getParameterType`
|
||||
in such a case, which can be expensive with your JDBC driver. You should use a recent driver
|
||||
While this usually works well, there is a potential for issues (for example, with Map-contained
|
||||
`null` values). Spring, by default, calls `ParameterMetaData.getParameterType` in such a
|
||||
case, which can be expensive with your JDBC driver. You should use a recent driver
|
||||
version and consider setting the `spring.jdbc.getParameterType.ignore` property to `true`
|
||||
(as a JVM system property or via the
|
||||
xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism)
|
||||
if you encounter a specific performance issue for your application.
|
||||
xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism) if you encounter
|
||||
a performance issue (as reported on Oracle 12c, JBoss, and PostgreSQL).
|
||||
|
||||
As of 6.1.2, Spring bypasses the default `getParameterType` resolution on PostgreSQL and
|
||||
MS SQL Server. This is a common optimization to avoid further roundtrips to the DBMS just
|
||||
for parameter type resolution which is known to make a very significant difference on
|
||||
PostgreSQL and MS SQL Server specifically, in particular for batch operations. If you
|
||||
happen to see a side effect e.g. when setting a byte array to null without specific type
|
||||
indication, you may explicitly set the `spring.jdbc.getParameterType.ignore=false` flag
|
||||
as a system property (see above) to restore full `getParameterType` resolution.
|
||||
|
||||
Alternatively, you could consider specifying the corresponding JDBC types explicitly,
|
||||
either through a `BatchPreparedStatementSetter` (as shown earlier), through an explicit
|
||||
type array given to a `List<Object[]>` based call, through `registerSqlType` calls on a
|
||||
custom `MapSqlParameterSource` instance, through a `BeanPropertySqlParameterSource`
|
||||
that derives the SQL type from the Java-declared property type even for a null value, or
|
||||
through providing individual `SqlParameterValue` instances instead of plain null values.
|
||||
Alternatively, you might consider specifying the corresponding JDBC types explicitly,
|
||||
either through a `BatchPreparedStatementSetter` (as shown earlier), through an explicit type
|
||||
array given to a `List<Object[]>` based call, through `registerSqlType` calls on a
|
||||
custom `MapSqlParameterSource` instance, or through a `BeanPropertySqlParameterSource`
|
||||
that derives the SQL type from the Java-declared property type even for a null value.
|
||||
====
|
||||
|
||||
|
||||
|
||||
@@ -46,47 +46,9 @@ To configure a `DriverManagerDataSource`:
|
||||
for the correct value.)
|
||||
. Provide a username and a password to connect to the database.
|
||||
|
||||
The following example shows how to configure a `DriverManagerDataSource` in Java:
|
||||
The following example shows how to configure a `DriverManagerDataSource`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
DriverManagerDataSource dataSource = new DriverManagerDataSource();
|
||||
dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
|
||||
dataSource.setUrl("jdbc:hsqldb:hsql://localhost:");
|
||||
dataSource.setUsername("sa");
|
||||
dataSource.setPassword("");
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val dataSource = DriverManagerDataSource().apply {
|
||||
setDriverClassName("org.hsqldb.jdbcDriver")
|
||||
url = "jdbc:hsqldb:hsql://localhost:"
|
||||
username = "sa"
|
||||
password = ""
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows the corresponding XML configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
|
||||
<property name="driverClassName" value="${jdbc.driverClassName}"/>
|
||||
<property name="url" value="${jdbc.url}"/>
|
||||
<property name="username" value="${jdbc.username}"/>
|
||||
<property name="password" value="${jdbc.password}"/>
|
||||
</bean>
|
||||
|
||||
<context:property-placeholder location="jdbc.properties"/>
|
||||
----
|
||||
include-code::./DriverManagerDataSourceConfiguration[tag=snippet,indent=0]
|
||||
|
||||
The next two examples show the basic connectivity and configuration for DBCP and C3P0.
|
||||
To learn about more options that help control the pooling features, see the product
|
||||
@@ -94,32 +56,11 @@ documentation for the respective connection pooling implementations.
|
||||
|
||||
The following example shows DBCP configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
|
||||
<property name="driverClassName" value="${jdbc.driverClassName}"/>
|
||||
<property name="url" value="${jdbc.url}"/>
|
||||
<property name="username" value="${jdbc.username}"/>
|
||||
<property name="password" value="${jdbc.password}"/>
|
||||
</bean>
|
||||
|
||||
<context:property-placeholder location="jdbc.properties"/>
|
||||
----
|
||||
include-code::./BasicDataSourceConfiguration[tag=snippet,indent=0]
|
||||
|
||||
The following example shows C3P0 configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
|
||||
<property name="driverClass" value="${jdbc.driverClassName}"/>
|
||||
<property name="jdbcUrl" value="${jdbc.url}"/>
|
||||
<property name="user" value="${jdbc.username}"/>
|
||||
<property name="password" value="${jdbc.password}"/>
|
||||
</bean>
|
||||
|
||||
<context:property-placeholder location="jdbc.properties"/>
|
||||
----
|
||||
|
||||
include-code::./ComboPooledDataSourceConfiguration[tag=snippet,indent=0]
|
||||
|
||||
[[jdbc-DataSourceUtils]]
|
||||
== Using `DataSourceUtils`
|
||||
|
||||
@@ -339,7 +339,7 @@ Kotlin::
|
||||
|
||||
More sophisticated stored procedure support is xref:data-access/jdbc/object.adoc#jdbc-StoredProcedure[covered later].
|
||||
|
||||
[[jdbc-JdbcTemplate-idioms]]
|
||||
[[jdbc-jdbctemplate-idioms]]
|
||||
=== `JdbcTemplate` Best Practices
|
||||
|
||||
Instances of the `JdbcTemplate` class are thread-safe, once configured. This is
|
||||
@@ -352,147 +352,23 @@ A common practice when using the `JdbcTemplate` class (and the associated
|
||||
xref:data-access/jdbc/core.adoc#jdbc-NamedParameterJdbcTemplate[`NamedParameterJdbcTemplate`] class) is to
|
||||
configure a `DataSource` in your Spring configuration file and then dependency-inject
|
||||
that shared `DataSource` bean into your DAO classes. The `JdbcTemplate` is created in
|
||||
the setter for the `DataSource`. This leads to DAOs that resemble the following:
|
||||
the setter for the `DataSource` or in the constructor. This leads to DAOs that resemble the following:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class JdbcCorporateEventDao implements CorporateEventDao {
|
||||
include-code::./JdbcCorporateEventDao[tag=snippet,indent=0]
|
||||
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
The following example shows the corresponding configuration:
|
||||
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
// JDBC-backed implementations of the methods on the CorporateEventDao follow...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class JdbcCorporateEventDao(dataSource: DataSource) : CorporateEventDao {
|
||||
|
||||
private val jdbcTemplate = JdbcTemplate(dataSource)
|
||||
|
||||
// JDBC-backed implementations of the methods on the CorporateEventDao follow...
|
||||
}
|
||||
----
|
||||
======
|
||||
--
|
||||
|
||||
The following example shows the corresponding XML configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
https://www.springframework.org/schema/context/spring-context.xsd">
|
||||
|
||||
<bean id="corporateEventDao" class="com.example.JdbcCorporateEventDao">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
</bean>
|
||||
|
||||
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
|
||||
<property name="driverClassName" value="${jdbc.driverClassName}"/>
|
||||
<property name="url" value="${jdbc.url}"/>
|
||||
<property name="username" value="${jdbc.username}"/>
|
||||
<property name="password" value="${jdbc.password}"/>
|
||||
</bean>
|
||||
|
||||
<context:property-placeholder location="jdbc.properties"/>
|
||||
|
||||
</beans>
|
||||
----
|
||||
include-code::./JdbcCorporateEventDaoConfiguration[tag=snippet,indent=0]
|
||||
|
||||
An alternative to explicit configuration is to use component-scanning and annotation
|
||||
support for dependency injection. In this case, you can annotate the class with `@Repository`
|
||||
(which makes it a candidate for component-scanning) and annotate the `DataSource` setter
|
||||
method with `@Autowired`. The following example shows how to do so:
|
||||
(which makes it a candidate for component-scanning). The following example shows how to do so:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Repository // <1>
|
||||
public class JdbcCorporateEventDao implements CorporateEventDao {
|
||||
include-code::./JdbcCorporateEventRepository[tag=snippet,indent=0]
|
||||
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
The following example shows the corresponding configuration:
|
||||
|
||||
@Autowired // <2>
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.jdbcTemplate = new JdbcTemplate(dataSource); // <3>
|
||||
}
|
||||
|
||||
// JDBC-backed implementations of the methods on the CorporateEventDao follow...
|
||||
}
|
||||
----
|
||||
<1> Annotate the class with `@Repository`.
|
||||
<2> Annotate the `DataSource` setter method with `@Autowired`.
|
||||
<3> Create a new `JdbcTemplate` with the `DataSource`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Repository // <1>
|
||||
class JdbcCorporateEventDao(dataSource: DataSource) : CorporateEventDao { // <2>
|
||||
|
||||
private val jdbcTemplate = JdbcTemplate(dataSource) // <3>
|
||||
|
||||
// JDBC-backed implementations of the methods on the CorporateEventDao follow...
|
||||
}
|
||||
----
|
||||
<1> Annotate the class with `@Repository`.
|
||||
<2> Constructor injection of the `DataSource`.
|
||||
<3> Create a new `JdbcTemplate` with the `DataSource`.
|
||||
======
|
||||
--
|
||||
|
||||
|
||||
The following example shows the corresponding XML configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
https://www.springframework.org/schema/context/spring-context.xsd">
|
||||
|
||||
<!-- Scans within the base package of the application for @Component classes to configure as beans -->
|
||||
<context:component-scan base-package="org.springframework.docs.test" />
|
||||
|
||||
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
|
||||
<property name="driverClassName" value="${jdbc.driverClassName}"/>
|
||||
<property name="url" value="${jdbc.url}"/>
|
||||
<property name="username" value="${jdbc.username}"/>
|
||||
<property name="password" value="${jdbc.password}"/>
|
||||
</bean>
|
||||
|
||||
<context:property-placeholder location="jdbc.properties"/>
|
||||
|
||||
</beans>
|
||||
----
|
||||
include-code::./JdbcCorporateEventRepositoryConfiguration[tag=snippet,indent=0]
|
||||
|
||||
If you use Spring's `JdbcDaoSupport` class and your various JDBC-backed DAO classes
|
||||
extend from it, your sub-class inherits a `setDataSource(..)` method from the
|
||||
@@ -698,7 +574,7 @@ functionality that is present only in the `JdbcTemplate` class, you can use the
|
||||
`getJdbcOperations()` method to access the wrapped `JdbcTemplate` through the
|
||||
`JdbcOperations` interface.
|
||||
|
||||
See also xref:data-access/jdbc/core.adoc#jdbc-JdbcTemplate-idioms[`JdbcTemplate` Best Practices]
|
||||
See also xref:data-access/jdbc/core.adoc#jdbc-jdbctemplate-idioms[`JdbcTemplate` Best Practices]
|
||||
for guidelines on using the `NamedParameterJdbcTemplate` class in the context of an application.
|
||||
|
||||
|
||||
|
||||
+75
-109
@@ -16,124 +16,22 @@ lightweight nature. Benefits include ease of configuration, quick startup time,
|
||||
testability, and the ability to rapidly evolve your SQL during development.
|
||||
|
||||
|
||||
[[jdbc-embedded-database-xml]]
|
||||
== Creating an Embedded Database by Using Spring XML
|
||||
[[jdbc-embedded-database]]
|
||||
== Creating an Embedded Database
|
||||
|
||||
If you want to expose an embedded database instance as a bean in a Spring
|
||||
`ApplicationContext`, you can use the `embedded-database` tag in the `spring-jdbc` namespace:
|
||||
You can expose an embedded database instance as a bean as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<jdbc:embedded-database id="dataSource" generate-name="true">
|
||||
<jdbc:script location="classpath:schema.sql"/>
|
||||
<jdbc:script location="classpath:test-data.sql"/>
|
||||
</jdbc:embedded-database>
|
||||
----
|
||||
include-code::./JdbcEmbeddedDatabaseConfiguration[tag=snippet,indent=0]
|
||||
|
||||
The preceding configuration creates an embedded HSQL database that is populated with SQL from
|
||||
The preceding configuration creates an embedded H2 database that is populated with SQL from
|
||||
the `schema.sql` and `test-data.sql` resources in the root of the classpath. In addition, as
|
||||
a best practice, the embedded database is assigned a uniquely generated name. The
|
||||
embedded database is made available to the Spring container as a bean of type
|
||||
`javax.sql.DataSource` that can then be injected into data access objects as needed.
|
||||
|
||||
|
||||
[[jdbc-embedded-database-java]]
|
||||
== Creating an Embedded Database Programmatically
|
||||
|
||||
The `EmbeddedDatabaseBuilder` class provides a fluent API for constructing an embedded
|
||||
database programmatically. You can use this when you need to create an embedded database in a
|
||||
stand-alone environment or in a stand-alone integration test, as in the following example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
EmbeddedDatabase db = new EmbeddedDatabaseBuilder()
|
||||
.generateUniqueName(true)
|
||||
.setType(H2)
|
||||
.setScriptEncoding("UTF-8")
|
||||
.ignoreFailedDrops(true)
|
||||
.addScript("schema.sql")
|
||||
.addScripts("user_data.sql", "country_data.sql")
|
||||
.build();
|
||||
|
||||
// perform actions against the db (EmbeddedDatabase extends javax.sql.DataSource)
|
||||
|
||||
db.shutdown()
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val db = EmbeddedDatabaseBuilder()
|
||||
.generateUniqueName(true)
|
||||
.setType(H2)
|
||||
.setScriptEncoding("UTF-8")
|
||||
.ignoreFailedDrops(true)
|
||||
.addScript("schema.sql")
|
||||
.addScripts("user_data.sql", "country_data.sql")
|
||||
.build()
|
||||
|
||||
// perform actions against the db (EmbeddedDatabase extends javax.sql.DataSource)
|
||||
|
||||
db.shutdown()
|
||||
----
|
||||
======
|
||||
|
||||
See the {spring-framework-api}/jdbc/datasource/embedded/EmbeddedDatabaseBuilder.html[javadoc for `EmbeddedDatabaseBuilder`]
|
||||
for further details on all supported options.
|
||||
|
||||
You can also use the `EmbeddedDatabaseBuilder` to create an embedded database by using Java
|
||||
configuration, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class DataSourceConfig {
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
return new EmbeddedDatabaseBuilder()
|
||||
.generateUniqueName(true)
|
||||
.setType(H2)
|
||||
.setScriptEncoding("UTF-8")
|
||||
.ignoreFailedDrops(true)
|
||||
.addScript("schema.sql")
|
||||
.addScripts("user_data.sql", "country_data.sql")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class DataSourceConfig {
|
||||
|
||||
@Bean
|
||||
fun dataSource(): DataSource {
|
||||
return EmbeddedDatabaseBuilder()
|
||||
.generateUniqueName(true)
|
||||
.setType(H2)
|
||||
.setScriptEncoding("UTF-8")
|
||||
.ignoreFailedDrops(true)
|
||||
.addScript("schema.sql")
|
||||
.addScripts("user_data.sql", "country_data.sql")
|
||||
.build()
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[jdbc-embedded-database-types]]
|
||||
== Selecting the Embedded Database Type
|
||||
@@ -168,6 +66,74 @@ attribute of the `embedded-database` tag to `DERBY`. If you use the builder API,
|
||||
call the `setType(EmbeddedDatabaseType)` method with `EmbeddedDatabaseType.DERBY`.
|
||||
|
||||
|
||||
[[jdbc-embedded-database-types-custom]]
|
||||
== Customizing the Embedded Database Type
|
||||
|
||||
While each supported type comes with default connection settings, it is possible
|
||||
to customize them if necessary. The following example uses H2 with a custom driver:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class DataSourceConfig {
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
return new EmbeddedDatabaseBuilder()
|
||||
.setDatabaseConfigurer(EmbeddedDatabaseConfigurers
|
||||
.customizeConfigurer(H2, this::customize))
|
||||
.addScript("schema.sql")
|
||||
.build();
|
||||
}
|
||||
|
||||
private EmbeddedDatabaseConfigurer customize(EmbeddedDatabaseConfigurer defaultConfigurer) {
|
||||
return new EmbeddedDatabaseConfigurerDelegate(defaultConfigurer) {
|
||||
@Override
|
||||
public void configureConnectionProperties(ConnectionProperties properties, String databaseName) {
|
||||
super.configureConnectionProperties(properties, databaseName);
|
||||
properties.setDriverClass(CustomDriver.class);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class DataSourceConfig {
|
||||
|
||||
@Bean
|
||||
fun dataSource(): DataSource {
|
||||
return EmbeddedDatabaseBuilder()
|
||||
.setDatabaseConfigurer(EmbeddedDatabaseConfigurers
|
||||
.customizeConfigurer(EmbeddedDatabaseType.H2) { this.customize(it) })
|
||||
.addScript("schema.sql")
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun customize(defaultConfigurer: EmbeddedDatabaseConfigurer): EmbeddedDatabaseConfigurer {
|
||||
return object : EmbeddedDatabaseConfigurerDelegate(defaultConfigurer) {
|
||||
override fun configureConnectionProperties(
|
||||
properties: ConnectionProperties,
|
||||
databaseName: String
|
||||
) {
|
||||
super.configureConnectionProperties(properties, databaseName)
|
||||
properties.setDriverClass(CustomDriver::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[jdbc-embedded-database-dao-testing]]
|
||||
== Testing Data Access Logic with an Embedded Database
|
||||
|
||||
@@ -177,8 +143,8 @@ can be useful for one-offs when the embedded database does not need to be reused
|
||||
classes. However, if you wish to create an embedded database that is shared within a test suite,
|
||||
consider using the xref:testing/testcontext-framework.adoc[Spring TestContext Framework] and
|
||||
configuring the embedded database as a bean in the Spring `ApplicationContext` as described
|
||||
in xref:data-access/jdbc/embedded-database-support.adoc#jdbc-embedded-database-xml[Creating an Embedded Database by Using Spring XML] and xref:data-access/jdbc/embedded-database-support.adoc#jdbc-embedded-database-java[Creating an Embedded Database Programmatically]. The following listing
|
||||
shows the test template:
|
||||
in xref:data-access/jdbc/embedded-database-support.adoc#jdbc-embedded-database[Creating an Embedded Database].
|
||||
The following listing shows the test template:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
||||
@@ -112,7 +112,7 @@ Java::
|
||||
this.actorMappingQuery = new ActorMappingQuery(dataSource);
|
||||
}
|
||||
|
||||
public Actor getActor(Long id) {
|
||||
public Customer getCustomer(Long id) {
|
||||
return actorMappingQuery.findObject(id);
|
||||
}
|
||||
----
|
||||
@@ -123,11 +123,11 @@ Kotlin::
|
||||
----
|
||||
private val actorMappingQuery = ActorMappingQuery(dataSource)
|
||||
|
||||
fun getActor(id: Long) = actorMappingQuery.findObject(id)
|
||||
fun getCustomer(id: Long) = actorMappingQuery.findObject(id)
|
||||
----
|
||||
======
|
||||
|
||||
The method in the preceding example retrieves the actor with the `id` that is passed in as the
|
||||
The method in the preceding example retrieves the customer with the `id` that is passed in as the
|
||||
only parameter. Since we want only one object to be returned, we call the `findObject` convenience
|
||||
method with the `id` as the parameter. If we had instead a query that returned a
|
||||
list of objects and took additional parameters, we would use one of the `execute`
|
||||
|
||||
@@ -209,26 +209,141 @@ are passed in as a parameter to the stored procedure.
|
||||
|
||||
The `SqlReturnType` interface has a single method (named `getTypeValue`) that must be
|
||||
implemented. This interface is used as part of the declaration of an `SqlOutParameter`.
|
||||
The following example shows returning the value of a `java.sql.Struct` object of the user
|
||||
The following example shows returning the value of an Oracle `STRUCT` object of the user
|
||||
declared type `ITEM_TYPE`:
|
||||
|
||||
include-code::./TestItemStoredProcedure[]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class TestItemStoredProcedure extends StoredProcedure {
|
||||
|
||||
public TestItemStoredProcedure(DataSource dataSource) {
|
||||
// ...
|
||||
declareParameter(new SqlOutParameter("item", OracleTypes.STRUCT, "ITEM_TYPE",
|
||||
(CallableStatement cs, int colIndx, int sqlType, String typeName) -> {
|
||||
STRUCT struct = (STRUCT) cs.getObject(colIndx);
|
||||
Object[] attr = struct.getAttributes();
|
||||
TestItem item = new TestItem();
|
||||
item.setId(((Number) attr[0]).longValue());
|
||||
item.setDescription((String) attr[1]);
|
||||
item.setExpirationDate((java.util.Date) attr[2]);
|
||||
return item;
|
||||
}));
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class TestItemStoredProcedure(dataSource: DataSource) : StoredProcedure() {
|
||||
|
||||
init {
|
||||
// ...
|
||||
declareParameter(SqlOutParameter("item", OracleTypes.STRUCT, "ITEM_TYPE") { cs, colIndx, sqlType, typeName ->
|
||||
val struct = cs.getObject(colIndx) as STRUCT
|
||||
val attr = struct.getAttributes()
|
||||
TestItem((attr[0] as Long, attr[1] as String, attr[2] as Date)
|
||||
})
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
You can use `SqlTypeValue` to pass the value of a Java object (such as `TestItem`) to a
|
||||
stored procedure. The `SqlTypeValue` interface has a single method (named
|
||||
`createTypeValue`) that you must implement. The active connection is passed in, and you
|
||||
can use it to create database-specific objects, such as `java.sql.Struct` instances
|
||||
or `java.sql.Array` instances. The following example creates a `java.sql.Struct` instance:
|
||||
can use it to create database-specific objects, such as `StructDescriptor` instances
|
||||
or `ArrayDescriptor` instances. The following example creates a `StructDescriptor` instance:
|
||||
|
||||
include-code::./SqlTypeValueFactory[tag=struct,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
final TestItem testItem = new TestItem(123L, "A test item",
|
||||
new SimpleDateFormat("yyyy-M-d").parse("2010-12-31"));
|
||||
|
||||
SqlTypeValue value = new AbstractSqlTypeValue() {
|
||||
protected Object createTypeValue(Connection conn, int sqlType, String typeName) throws SQLException {
|
||||
StructDescriptor itemDescriptor = new StructDescriptor(typeName, conn);
|
||||
Struct item = new STRUCT(itemDescriptor, conn,
|
||||
new Object[] {
|
||||
testItem.getId(),
|
||||
testItem.getDescription(),
|
||||
new java.sql.Date(testItem.getExpirationDate().getTime())
|
||||
});
|
||||
return item;
|
||||
}
|
||||
};
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val (id, description, expirationDate) = TestItem(123L, "A test item",
|
||||
SimpleDateFormat("yyyy-M-d").parse("2010-12-31"))
|
||||
|
||||
val value = object : AbstractSqlTypeValue() {
|
||||
override fun createTypeValue(conn: Connection, sqlType: Int, typeName: String?): Any {
|
||||
val itemDescriptor = StructDescriptor(typeName, conn)
|
||||
return STRUCT(itemDescriptor, conn,
|
||||
arrayOf(id, description, java.sql.Date(expirationDate.time)))
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
You can now add this `SqlTypeValue` to the `Map` that contains the input parameters for the
|
||||
`execute` call of the stored procedure.
|
||||
|
||||
Another use for the `SqlTypeValue` is passing in an array of values to an Oracle stored
|
||||
procedure. Oracle has an `createOracleArray` method on `OracleConnection` that you can
|
||||
access by unwrapping it. You can use the `SqlTypeValue` to create an array and populate
|
||||
it with values from the Java `java.sql.Array`, as the following example shows:
|
||||
procedure. Oracle has its own internal `ARRAY` class that must be used in this case, and
|
||||
you can use the `SqlTypeValue` to create an instance of the Oracle `ARRAY` and populate
|
||||
it with values from the Java `ARRAY`, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
final Long[] ids = new Long[] {1L, 2L};
|
||||
|
||||
SqlTypeValue value = new AbstractSqlTypeValue() {
|
||||
protected Object createTypeValue(Connection conn, int sqlType, String typeName) throws SQLException {
|
||||
ArrayDescriptor arrayDescriptor = new ArrayDescriptor(typeName, conn);
|
||||
ARRAY idArray = new ARRAY(arrayDescriptor, conn, ids);
|
||||
return idArray;
|
||||
}
|
||||
};
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class TestItemStoredProcedure(dataSource: DataSource) : StoredProcedure() {
|
||||
|
||||
init {
|
||||
val ids = arrayOf(1L, 2L)
|
||||
val value = object : AbstractSqlTypeValue() {
|
||||
override fun createTypeValue(conn: Connection, sqlType: Int, typeName: String?): Any {
|
||||
val arrayDescriptor = ArrayDescriptor(typeName, conn)
|
||||
return ARRAY(arrayDescriptor, conn, ids)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
include-code::./SqlTypeValueFactory[tag=oracle-array,indent=0]
|
||||
|
||||
|
||||
@@ -323,12 +323,12 @@ use these alternative input classes.
|
||||
|
||||
The `SimpleJdbcCall` class uses metadata in the database to look up names of `in`
|
||||
and `out` parameters so that you do not have to explicitly declare them. You can
|
||||
declare parameters if you prefer to do that or if you have parameters that do not
|
||||
have an automatic mapping to a Java class. The first example shows a simple procedure
|
||||
that returns only scalar values in `VARCHAR` and `DATE` format from a MySQL database.
|
||||
The example procedure reads a specified actor entry and returns `first_name`,
|
||||
`last_name`, and `birth_date` columns in the form of `out` parameters. The following
|
||||
listing shows the first example:
|
||||
declare parameters if you prefer to do that or if you have parameters (such as `ARRAY`
|
||||
or `STRUCT`) that do not have an automatic mapping to a Java class. The first example
|
||||
shows a simple procedure that returns only scalar values in `VARCHAR` and `DATE` format
|
||||
from a MySQL database. The example procedure reads a specified actor entry and returns
|
||||
`first_name`, `last_name`, and `birth_date` columns in the form of `out` parameters.
|
||||
The following listing shows the first example:
|
||||
|
||||
[source,sql,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
|
||||
@@ -272,6 +272,13 @@ is being accessed by other components (for example, calling `createEntityManager
|
||||
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.
|
||||
|
||||
As of 6.2, JPA initialization is enforced before context refresh completion, waiting for
|
||||
asynchronous bootstrapping to complete by then. This makes the availability of the fully
|
||||
initialized database infrastructure predictable and allows for custom post-initialization
|
||||
logic in `ContextRefreshedEvent` listeners etc. Putting such application-level database
|
||||
initialization into `@PostConstruct` methods or the like is not recommended; this is
|
||||
better placed in `Lifecycle.start` (if applicable) or a `ContextRefreshedEvent` listener.
|
||||
|
||||
|
||||
[[orm-jpa-dao]]
|
||||
== Implementing DAOs Based on JPA: `EntityManagerFactory` and `EntityManager`
|
||||
|
||||
+45
-5
@@ -442,12 +442,32 @@ xref:data-access/transaction/declarative/rolling-back.adoc#transaction-declarati
|
||||
for further details on rollback rule semantics, patterns, and warnings
|
||||
regarding possible unintentional matches for pattern-based rollback rules.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
As of 6.2, you can globally change the default rollback behavior – for example, through
|
||||
`@EnableTransactionManagement(rollbackOn=ALL_EXCEPTIONS)`, leading to a rollback
|
||||
for all exceptions raised within a transaction, including any checked exception.
|
||||
For further customizations, `AnnotationTransactionAttributeSource` provides an
|
||||
`addDefaultRollbackRule(RollbackRuleAttribute)` method for custom default rules.
|
||||
|
||||
Note that transaction-specific rollback rules override the default behavior but
|
||||
retain the chosen default for unspecified exceptions. This is the case for
|
||||
Spring's `@Transactional` as well as JTA's `jakarta.transaction.Transactional`
|
||||
annotation.
|
||||
|
||||
Unless you rely on EJB-style business exceptions with commit behavior, it is
|
||||
advisable to switch to `ALL_EXCEPTIONS` for consistent rollback semantics even
|
||||
in case of a (potentially accidental) checked exception. Also, it is advisable
|
||||
to make that switch for Kotlin-based applications where there is no enforcement
|
||||
of checked exceptions at all.
|
||||
====
|
||||
|
||||
Currently, you cannot have explicit control over the name of a transaction, where 'name'
|
||||
means the transaction name that appears in a transaction monitor and in logging output.
|
||||
For declarative transactions, the transaction name is always the fully-qualified class
|
||||
name + `.` + the method name of the transactionally advised class. For example, if the
|
||||
name of the transactionally advised class + `.` + the method name. For example, if the
|
||||
`handlePayment(..)` method of the `BusinessService` class started a transaction, the
|
||||
name of the transaction would be: `com.example.BusinessService.handlePayment`.
|
||||
name of the transaction would be `com.example.BusinessService.handlePayment`.
|
||||
|
||||
[[tx-multiple-tx-mgrs-with-attransactional]]
|
||||
== Multiple Transaction Managers with `@Transactional`
|
||||
@@ -531,12 +551,32 @@ transaction managers, differentiated by the `order`, `account`, and `reactive-ac
|
||||
qualifiers. The default `<tx:annotation-driven>` target bean name, `transactionManager`,
|
||||
is still used if no specifically qualified `TransactionManager` bean is found.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
If all transactional methods on the same class share the same qualifier, consider
|
||||
declaring a type-level `org.springframework.beans.factory.annotation.Qualifier`
|
||||
annotation instead. If its value matches the qualifier value (or bean name) of a
|
||||
specific transaction manager, that transaction manager is going to be used for
|
||||
transaction definitions without a specific qualifier on `@Transactional` itself.
|
||||
|
||||
Such a type-level qualifier can be declared on the concrete class, applying to
|
||||
transaction definitions from a base class as well. This effectively overrides
|
||||
the default transaction manager choice for any unqualified base class methods.
|
||||
|
||||
Last but not least, such a type-level bean qualifier can serve multiple purposes,
|
||||
e.g. with a value of "order" it can be used for autowiring purposes (identifying
|
||||
the order repository) as well as transaction manager selection, as long as the
|
||||
target beans for autowiring as well as the associated transaction manager
|
||||
definitions declare the same qualifier value. Such a qualifier value only needs
|
||||
to be unique within a set of type-matching beans, not having to serve as an ID.
|
||||
====
|
||||
|
||||
[[tx-custom-attributes]]
|
||||
== Custom Composed Annotations
|
||||
|
||||
If you find you repeatedly use the same attributes with `@Transactional` on many different
|
||||
methods, xref:core/beans/classpath-scanning.adoc#beans-meta-annotations[Spring's meta-annotation support] lets you
|
||||
define custom composed annotations for your specific use cases. For example, consider the
|
||||
If you find you repeatedly use the same attributes with `@Transactional` on many different methods,
|
||||
xref:core/beans/classpath-scanning.adoc#beans-meta-annotations[Spring's meta-annotation support]
|
||||
lets you define custom composed annotations for your specific use cases. For example, consider the
|
||||
following annotation definitions:
|
||||
|
||||
[tabs]
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ As of Spring Framework 5.2, the default configuration also provides support for
|
||||
Vavr's `Try` method to trigger transaction rollbacks when it returns a 'Failure'.
|
||||
This allows you to handle functional-style errors using Try and have the transaction
|
||||
automatically rolled back in case of a failure. For more information on Vavr's Try,
|
||||
refer to the {vavr-docs}/#_try[official Vavr documentation].
|
||||
refer to the https://docs.vavr.io/#_try[official Vavr documentation].
|
||||
Here's an example of how to use Vavr's Try with a transactional method:
|
||||
|
||||
[tabs]
|
||||
|
||||
@@ -29,6 +29,8 @@ 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 - 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
|
||||
copy contains the Copyright Notice, whether distributed in print or electronically.
|
||||
copy contains this Copyright Notice, whether distributed in print or electronically.
|
||||
|
||||
@@ -332,7 +332,7 @@ metadata, such as the argument names. The following table describes the items ma
|
||||
available to the context so that you can use them for key and conditional computations:
|
||||
|
||||
[[cache-spel-context-tbl]]
|
||||
.Cache metadata available in SpEL expressions
|
||||
.Cache SpEL available metadata
|
||||
|===
|
||||
| Name| Location| Description| Example
|
||||
|
||||
@@ -358,7 +358,7 @@ available to the context so that you can use them for key and conditional comput
|
||||
|
||||
| `args`
|
||||
| Root object
|
||||
| The arguments (as an object array) used for invoking the target
|
||||
| The arguments (as array) used for invoking the target
|
||||
| `#root.args[0]`
|
||||
|
||||
| `caches`
|
||||
@@ -368,10 +368,9 @@ available to the context so that you can use them for key and conditional comput
|
||||
|
||||
| Argument name
|
||||
| Evaluation context
|
||||
| The name of a particular method argument. If the names are not available
|
||||
(for example, because the code was compiled without the `-parameters` flag), individual
|
||||
arguments are also available using the `#a<#arg>` syntax where `<#arg>` stands for the
|
||||
argument index (starting from 0).
|
||||
| Name of any of the method arguments. If the names are not available
|
||||
(perhaps due to having no debug information), the argument names are also available under the `#a<#arg>`
|
||||
where `#arg` stands for the argument index (starting from `0`).
|
||||
| `#iban` or `#a0` (you can also use `#p0` or `#p<#arg>` notation as an alias).
|
||||
|
||||
| `result`
|
||||
@@ -519,41 +518,9 @@ disable it by removing only one configuration line rather than all the annotatio
|
||||
your code).
|
||||
|
||||
To enable caching annotations add the annotation `@EnableCaching` to one of your
|
||||
`@Configuration` classes:
|
||||
`@Configuration` classes or use the `cache:annotation-driven` element with XML:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
class AppConfig {
|
||||
|
||||
@Bean
|
||||
CacheManager cacheManager() {
|
||||
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
|
||||
cacheManager.setCacheSpecification(...);
|
||||
return cacheManager;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Alternatively, for XML configuration you can use the `cache:annotation-driven` element:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:cache="http://www.springframework.org/schema/cache"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/cache https://www.springframework.org/schema/cache/spring-cache.xsd">
|
||||
|
||||
<cache:annotation-driven/>
|
||||
|
||||
<bean id="cacheManager" class="org.springframework.cache.caffeine.CaffeineCacheManager">
|
||||
<property name="cacheSpecification" value="..."/>
|
||||
</bean>
|
||||
</beans>
|
||||
----
|
||||
include-code::./CacheConfiguration[tag=snippet,indent=0]
|
||||
|
||||
Both the `cache:annotation-driven` element and the `@EnableCaching` annotation let you
|
||||
specify various options that influence the way the caching behavior is added to the
|
||||
|
||||
+5
-49
@@ -13,18 +13,7 @@ The JDK-based `Cache` implementation resides under
|
||||
`org.springframework.cache.concurrent` package. It lets you use `ConcurrentHashMap`
|
||||
as a backing `Cache` store. The following example shows how to configure two caches:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<!-- simple cache manager -->
|
||||
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
|
||||
<property name="caches">
|
||||
<set>
|
||||
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="default"/>
|
||||
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="books"/>
|
||||
</set>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
include-code::./CacheConfiguration[tag=snippet,indent=0]
|
||||
|
||||
The preceding snippet uses the `SimpleCacheManager` to create a `CacheManager` for the
|
||||
two nested `ConcurrentMapCache` instances named `default` and `books`. Note that the
|
||||
@@ -52,26 +41,12 @@ of Caffeine.
|
||||
|
||||
The following example configures a `CacheManager` that creates the cache on demand:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="cacheManager"
|
||||
class="org.springframework.cache.caffeine.CaffeineCacheManager"/>
|
||||
----
|
||||
include-code::./CacheConfiguration[tag=snippet,indent=0]
|
||||
|
||||
You can also provide the caches to use explicitly. In that case, only those
|
||||
are made available by the manager. The following example shows how to do so:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="cacheManager" class="org.springframework.cache.caffeine.CaffeineCacheManager">
|
||||
<property name="cacheNames">
|
||||
<set>
|
||||
<value>default</value>
|
||||
<value>books</value>
|
||||
</set>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
include-code::./CustomCacheConfiguration[tag=snippet,indent=0]
|
||||
|
||||
The Caffeine `CacheManager` also supports custom `Caffeine` and `CacheLoader`.
|
||||
See the https://github.com/ben-manes/caffeine/wiki[Caffeine documentation]
|
||||
@@ -97,15 +72,7 @@ implementation is located in the `org.springframework.cache.jcache` package.
|
||||
Again, to use it, you need to declare the appropriate `CacheManager`.
|
||||
The following example shows how to do so:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="cacheManager"
|
||||
class="org.springframework.cache.jcache.JCacheCacheManager"
|
||||
p:cache-manager-ref="jCacheManager"/>
|
||||
|
||||
<!-- JSR-107 cache manager setup -->
|
||||
<bean id="jCacheManager" .../>
|
||||
----
|
||||
include-code::./CacheConfiguration[tag=snippet,indent=0]
|
||||
|
||||
|
||||
[[cache-store-configuration-noop]]
|
||||
@@ -119,18 +86,7 @@ cache declarations (which can prove tedious), you can wire in a simple dummy cac
|
||||
performs no caching -- that is, it forces the cached methods to be invoked every time.
|
||||
The following example shows how to do so:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="cacheManager" class="org.springframework.cache.support.CompositeCacheManager">
|
||||
<property name="cacheManagers">
|
||||
<list>
|
||||
<ref bean="jdkCache"/>
|
||||
<ref bean="gemfireCache"/>
|
||||
</list>
|
||||
</property>
|
||||
<property name="fallbackToNoOpCache" value="true"/>
|
||||
</bean>
|
||||
----
|
||||
include-code::./CacheConfiguration[tag=snippet,indent=0]
|
||||
|
||||
The `CompositeCacheManager` in the preceding chains multiple `CacheManager` instances and,
|
||||
through the `fallbackToNoOpCache` flag, adds a no-op cache for all the definitions not
|
||||
|
||||
@@ -24,7 +24,7 @@ To create the archive, two additional JVM flags must be specified:
|
||||
* `-Dspring.context.exit=onRefresh`: starts and then immediately exits your Spring
|
||||
application as described above
|
||||
|
||||
To create a CDS archive, your JDK/JRE must have a base image. If you add the flags above to
|
||||
To create a CDS archive, your JDK must have a base image. If you add the flags above to
|
||||
your startup script, you may get a warning that looks like this:
|
||||
|
||||
[source,shell,indent=0,subs="verbatim"]
|
||||
@@ -32,8 +32,7 @@ your startup script, you may get a warning that looks like this:
|
||||
-XX:ArchiveClassesAtExit is unsupported when base CDS archive is not loaded. Run with -Xlog:cds for more info.
|
||||
----
|
||||
|
||||
The base CDS archive is usually provided out-of-the-box, but can also be created if needed by issuing the following
|
||||
command:
|
||||
The base CDS archive can be created by issuing the following command:
|
||||
|
||||
[source,shell,indent=0,subs="verbatim"]
|
||||
----
|
||||
@@ -45,9 +44,6 @@ command:
|
||||
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 check if the CDS cache is effective, you can use (for testing purposes only, not in production) `-Xshare:on` which
|
||||
prints an error message and exits if CDS can't be enabled.
|
||||
|
||||
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
|
||||
attempt to load a class and its source. Classes that are loaded from the cache should have
|
||||
@@ -62,11 +58,8 @@ a "shared objects file" source, as shown in the following example:
|
||||
[0.065s][info][class,load] org.springframework.context.MessageSource source: shared objects file (top)
|
||||
----
|
||||
|
||||
If CDS can't be enabled or if you have a large number of classes that are not loaded from the cache, make sure that
|
||||
the following conditions are fulfilled when creating and using the archive:
|
||||
|
||||
- The very same JVM must be used.
|
||||
- The classpath must be specified as a list of JARs, and avoid the usage of directories and `*` wildcard characters.
|
||||
- The timestamps of the JARs must be preserved.
|
||||
- When using the archive, the classpath must be the same than the one used to create the archive, in the same order.
|
||||
Additional JARs or directories can be specified *at the end* (but won't be cached).
|
||||
TIP: If you have a large number of classes that are not loaded from the cache, make sure that
|
||||
the JDK and classpath used by the commands that create the archive and start the application
|
||||
are identical. Note also that to effectively cache classes, the classpath should be specified
|
||||
as a list of JARs containing those classes, and avoid the usage of directories and `*`
|
||||
wildcard characters.
|
||||
|
||||
@@ -19,8 +19,6 @@ A checkpoint can be created on demand, for example using a command like `jcmd ap
|
||||
|
||||
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.
|
||||
|
||||
WARNING: Be aware that when defining scheduling tasks at a fixed rate, for example with an annotation like `@Scheduled(fixedRate = 5000)`, all missed executions between checkpoint and restore will be performed when the JVM is restored with on-demand checkpoint/restore. If this is not the behavior you want, it is recommended to schedule tasks at a fixed delay (for example with `@Scheduled(fixedDelay = 5000)`) or with a cron expression as those are calculated after every task execution.
|
||||
|
||||
NOTE: If the checkpoint is created on a warmed-up JVM, the restored JVM will be equally warmed-up, allowing potentially peak performance immediately. This method typically requires access to remote services, and thus requires some level of platform integration.
|
||||
|
||||
== Automatic checkpoint/restore at startup
|
||||
|
||||
@@ -41,14 +41,7 @@ JavaMail features, such as MIME message support to the `MailSender` interface
|
||||
|
||||
Assume that we have a business interface called `OrderManager`, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface OrderManager {
|
||||
|
||||
void placeOrder(Order order);
|
||||
|
||||
}
|
||||
----
|
||||
include-code::./OrderManager[tag=snippet,indent=0]
|
||||
|
||||
Further assume that we have a requirement stating that an email message with an
|
||||
order number needs to be generated and sent to a customer who placed the relevant order.
|
||||
@@ -60,70 +53,11 @@ order number needs to be generated and sent to a customer who placed the relevan
|
||||
The following example shows how to use `MailSender` and `SimpleMailMessage` to send an
|
||||
email when someone places an order:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import org.springframework.mail.MailException;
|
||||
import org.springframework.mail.MailSender;
|
||||
import org.springframework.mail.SimpleMailMessage;
|
||||
|
||||
public class SimpleOrderManager implements OrderManager {
|
||||
|
||||
private MailSender mailSender;
|
||||
private SimpleMailMessage templateMessage;
|
||||
|
||||
public void setMailSender(MailSender mailSender) {
|
||||
this.mailSender = mailSender;
|
||||
}
|
||||
|
||||
public void setTemplateMessage(SimpleMailMessage templateMessage) {
|
||||
this.templateMessage = templateMessage;
|
||||
}
|
||||
|
||||
public void placeOrder(Order order) {
|
||||
|
||||
// Do the business calculations...
|
||||
|
||||
// Call the collaborators to persist the order...
|
||||
|
||||
// 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(
|
||||
"Dear " + order.getCustomer().getFirstName()
|
||||
+ order.getCustomer().getLastName()
|
||||
+ ", thank you for placing order. Your order number is "
|
||||
+ order.getOrderNumber());
|
||||
try {
|
||||
this.mailSender.send(msg);
|
||||
}
|
||||
catch (MailException ex) {
|
||||
// simply log it and go on...
|
||||
System.err.println(ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
include-code::./SimpleOrderManager[tag=snippet,indent=0]
|
||||
|
||||
The following example shows the bean definitions for the preceding code:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
|
||||
<property name="host" value="mail.mycompany.example"/>
|
||||
</bean>
|
||||
|
||||
<!-- this is a template message that we can pre-load with default state -->
|
||||
<bean id="templateMessage" class="org.springframework.mail.SimpleMailMessage">
|
||||
<property name="from" value="customerservice@mycompany.example"/>
|
||||
<property name="subject" value="Your order"/>
|
||||
</bean>
|
||||
|
||||
<bean id="orderManager" class="com.mycompany.businessapp.support.SimpleOrderManager">
|
||||
<property name="mailSender" ref="mailSender"/>
|
||||
<property name="templateMessage" ref="templateMessage"/>
|
||||
</bean>
|
||||
----
|
||||
include-code::./MailConfiguration[tag=snippet,indent=0]
|
||||
|
||||
|
||||
[[mail-usage-mime]]
|
||||
|
||||
@@ -37,28 +37,12 @@ declarations to it.
|
||||
To enable support for `@JmsListener` annotations, you can add `@EnableJms` to one of
|
||||
your `@Configuration` classes, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableJms
|
||||
public class AppConfig {
|
||||
|
||||
@Bean
|
||||
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
|
||||
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
|
||||
factory.setConnectionFactory(connectionFactory());
|
||||
factory.setDestinationResolver(destinationResolver());
|
||||
factory.setSessionTransacted(true);
|
||||
factory.setConcurrency("3-10");
|
||||
return factory;
|
||||
}
|
||||
}
|
||||
----
|
||||
include-code::./JmsConfiguration[tag=snippet,indent=0]
|
||||
|
||||
By default, the infrastructure looks for a bean named `jmsListenerContainerFactory`
|
||||
as the source for the factory to use to create message listener containers. In this
|
||||
case (and ignoring the JMS infrastructure setup), you can invoke the `processOrder`
|
||||
method with a core pool size of three threads and a maximum pool size of ten threads.
|
||||
method with a core poll size of three threads and a maximum pool size of ten threads.
|
||||
|
||||
You can customize the listener container factory to use for each annotation or you can
|
||||
configure an explicit default by implementing the `JmsListenerConfigurer` interface.
|
||||
@@ -67,22 +51,6 @@ container factory. See the javadoc of classes that implement
|
||||
{spring-framework-api}/jms/annotation/JmsListenerConfigurer.html[`JmsListenerConfigurer`]
|
||||
for details and examples.
|
||||
|
||||
If you prefer xref:integration/jms/namespace.adoc[XML configuration], you can use the `<jms:annotation-driven>`
|
||||
element, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<jms:annotation-driven/>
|
||||
|
||||
<bean id="jmsListenerContainerFactory"
|
||||
class="org.springframework.jms.config.DefaultJmsListenerContainerFactory">
|
||||
<property name="connectionFactory" ref="connectionFactory"/>
|
||||
<property name="destinationResolver" ref="destinationResolver"/>
|
||||
<property name="sessionTransacted" value="true"/>
|
||||
<property name="concurrency" value="3-10"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
|
||||
[[jms-annotated-programmatic-registration]]
|
||||
== Programmatic Endpoint Registration
|
||||
|
||||
+2
-51
@@ -7,62 +7,13 @@ automatically determine the `ActivationSpec` class name from the provider's
|
||||
`ResourceAdapter` class name. Therefore, it is typically possible to provide
|
||||
Spring's generic `JmsActivationSpecConfig`, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean class="org.springframework.jms.listener.endpoint.JmsMessageEndpointManager">
|
||||
<property name="resourceAdapter" ref="resourceAdapter"/>
|
||||
<property name="activationSpecConfig">
|
||||
<bean class="org.springframework.jms.listener.endpoint.JmsActivationSpecConfig">
|
||||
<property name="destinationName" value="myQueue"/>
|
||||
</bean>
|
||||
</property>
|
||||
<property name="messageListener" ref="myMessageListener"/>
|
||||
</bean>
|
||||
----
|
||||
include-code::./JmsConfiguration[tag=snippet,indent=0]
|
||||
|
||||
Alternatively, you can set up a `JmsMessageEndpointManager` with a given
|
||||
`ActivationSpec` object. The `ActivationSpec` object may also come from a JNDI lookup
|
||||
(using `<jee:jndi-lookup>`). The following example shows how to do so:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean class="org.springframework.jms.listener.endpoint.JmsMessageEndpointManager">
|
||||
<property name="resourceAdapter" ref="resourceAdapter"/>
|
||||
<property name="activationSpec">
|
||||
<bean class="org.apache.activemq.ra.ActiveMQActivationSpec">
|
||||
<property name="destination" value="myQueue"/>
|
||||
<property name="destinationType" value="jakarta.jms.Queue"/>
|
||||
</bean>
|
||||
</property>
|
||||
<property name="messageListener" ref="myMessageListener"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
Using Spring's `ResourceAdapterFactoryBean`, you can configure the target `ResourceAdapter`
|
||||
locally, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="resourceAdapter" class="org.springframework.jca.support.ResourceAdapterFactoryBean">
|
||||
<property name="resourceAdapter">
|
||||
<bean class="org.apache.activemq.ra.ActiveMQResourceAdapter">
|
||||
<property name="serverUrl" value="tcp://localhost:61616"/>
|
||||
</bean>
|
||||
</property>
|
||||
<property name="workManager">
|
||||
<bean class="org.springframework.jca.work.SimpleTaskWorkManager"/>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
The specified `WorkManager` can also point to an environment-specific thread pool --
|
||||
typically through a `SimpleTaskWorkManager` instance's `asyncTaskExecutor` property.
|
||||
Consider defining a shared thread pool for all your `ResourceAdapter` instances
|
||||
if you happen to use multiple adapters.
|
||||
|
||||
In some environments, you can instead obtain the entire `ResourceAdapter` object from JNDI
|
||||
(by using `<jee:jndi-lookup>`). The Spring-based message listeners can then interact with
|
||||
the server-hosted `ResourceAdapter`, which also use the server's built-in `WorkManager`.
|
||||
include-code::./AlternativeJmsConfiguration[tag=snippet,indent=0]
|
||||
|
||||
See the javadoc for {spring-framework-api}/jms/listener/endpoint/JmsMessageEndpointManager.html[`JmsMessageEndpointManager`],
|
||||
{spring-framework-api}/jms/listener/endpoint/JmsActivationSpecConfig.html[`JmsActivationSpecConfig`],
|
||||
|
||||
@@ -31,30 +31,7 @@ on multiple threads, it is important to ensure that your implementation is threa
|
||||
|
||||
The following example shows a simple implementation of an MDP:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.jms.JMSException;
|
||||
import jakarta.jms.Message;
|
||||
import jakarta.jms.MessageListener;
|
||||
import jakarta.jms.TextMessage;
|
||||
|
||||
public class ExampleListener implements MessageListener {
|
||||
|
||||
public void onMessage(Message message) {
|
||||
if (message instanceof TextMessage textMessage) {
|
||||
try {
|
||||
System.out.println(textMessage.getText());
|
||||
}
|
||||
catch (JMSException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Message must be of type TextMessage");
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
include-code::./ExampleListener[tag=snippet,indent=0]
|
||||
|
||||
Once you have implemented your `MessageListener`, it is time to create a message listener
|
||||
container.
|
||||
@@ -62,18 +39,7 @@ container.
|
||||
The following example shows how to define and configure one of the message listener
|
||||
containers that ships with Spring (in this case, `DefaultMessageListenerContainer`):
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<!-- this is the Message Driven POJO (MDP) -->
|
||||
<bean id="messageListener" class="jmsexample.ExampleListener"/>
|
||||
|
||||
<!-- and this is the message listener container -->
|
||||
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
|
||||
<property name="connectionFactory" ref="connectionFactory"/>
|
||||
<property name="destination" ref="destination"/>
|
||||
<property name="messageListener" ref="messageListener"/>
|
||||
</bean>
|
||||
----
|
||||
include-code::./JmsConfiguration[tag=snippet,indent=0]
|
||||
|
||||
See the Spring javadoc of the various message listener containers (all of which implement
|
||||
{spring-framework-api}/jms/listener/MessageListenerContainer.html[MessageListenerContainer])
|
||||
@@ -123,19 +89,7 @@ messaging support. In a nutshell, it lets you expose almost any class as an MDP
|
||||
|
||||
Consider the following interface definition:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface MessageDelegate {
|
||||
|
||||
void handleMessage(String message);
|
||||
|
||||
void handleMessage(Map message);
|
||||
|
||||
void handleMessage(byte[] message);
|
||||
|
||||
void handleMessage(Serializable message);
|
||||
}
|
||||
----
|
||||
include-code::./MessageDelegate[tag=snippet,indent=0]
|
||||
|
||||
Notice that, although the interface extends neither the `MessageListener` nor the
|
||||
`SessionAwareMessageListener` interface, you can still use it as an MDP by using the
|
||||
@@ -145,33 +99,13 @@ receive and handle.
|
||||
|
||||
Now consider the following implementation of the `MessageDelegate` interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public class DefaultMessageDelegate implements MessageDelegate {
|
||||
// implementation elided for clarity...
|
||||
}
|
||||
----
|
||||
include-code::./DefaultMessageDelegate[tag=snippet,indent=0]
|
||||
|
||||
In particular, note how the preceding implementation of the `MessageDelegate` interface (the
|
||||
`DefaultMessageDelegate` class) has no JMS dependencies at all. It truly is a
|
||||
POJO that we can make into an MDP through the following configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<!-- this is the Message Driven POJO (MDP) -->
|
||||
<bean id="messageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
|
||||
<constructor-arg>
|
||||
<bean class="jmsexample.DefaultMessageDelegate"/>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
|
||||
<!-- and this is the message listener container... -->
|
||||
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
|
||||
<property name="connectionFactory" ref="connectionFactory"/>
|
||||
<property name="destination" ref="destination"/>
|
||||
<property name="messageListener" ref="messageListener"/>
|
||||
</bean>
|
||||
----
|
||||
include-code::./JmsConfiguration[tag=snippet,indent=0]
|
||||
|
||||
The next example shows another MDP that can handle only receiving JMS
|
||||
`TextMessage` messages. Notice how the message handling method is actually called
|
||||
@@ -181,38 +115,15 @@ also how the `receive(..)` method is strongly typed to receive and respond only
|
||||
`TextMessage` messages.
|
||||
The following listing shows the definition of the `TextMessageDelegate` interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface TextMessageDelegate {
|
||||
|
||||
void receive(TextMessage message);
|
||||
}
|
||||
----
|
||||
include-code::./TextMessageDelegate[tag=snippet,indent=0]
|
||||
|
||||
The following listing shows a class that implements the `TextMessageDelegate` interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public class DefaultTextMessageDelegate implements TextMessageDelegate {
|
||||
// implementation elided for clarity...
|
||||
}
|
||||
----
|
||||
include-code::./DefaultTextMessageDelegate[tag=snippet,indent=0]
|
||||
|
||||
The configuration of the attendant `MessageListenerAdapter` would then be as follows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="messageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
|
||||
<constructor-arg>
|
||||
<bean class="jmsexample.DefaultTextMessageDelegate"/>
|
||||
</constructor-arg>
|
||||
<property name="defaultListenerMethod" value="receive"/>
|
||||
<!-- we don't want automatic message context extraction -->
|
||||
<property name="messageConverter">
|
||||
<null/>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
include-code::./MessageListenerConfiguration[tag=snippet,indent=0]
|
||||
|
||||
Note that, if the `messageListener` receives a JMS `Message` of a type
|
||||
other than `TextMessage`, an `IllegalStateException` is thrown (and subsequently
|
||||
@@ -220,21 +131,9 @@ swallowed). Another of the capabilities of the `MessageListenerAdapter` class is
|
||||
ability to automatically send back a response `Message` if a handler method returns a
|
||||
non-void value. Consider the following interface and class:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface ResponsiveTextMessageDelegate {
|
||||
include-code::./ResponsiveTextMessageDelegate[tag=snippet,indent=0]
|
||||
|
||||
// notice the return type...
|
||||
String receive(TextMessage message);
|
||||
}
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public class DefaultResponsiveTextMessageDelegate implements ResponsiveTextMessageDelegate {
|
||||
// implementation elided for clarity...
|
||||
}
|
||||
----
|
||||
include-code::./DefaultResponsiveTextMessageDelegate[tag=snippet,indent=0]
|
||||
|
||||
If you use the `DefaultResponsiveTextMessageDelegate` in conjunction with a
|
||||
`MessageListenerAdapter`, any non-null value that is returned from the execution of
|
||||
@@ -264,15 +163,7 @@ has committed but message processing failed to commit.
|
||||
|
||||
Consider the following bean definition:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
|
||||
<property name="connectionFactory" ref="connectionFactory"/>
|
||||
<property name="destination" ref="destination"/>
|
||||
<property name="messageListener" ref="messageListener"/>
|
||||
<property name="sessionTransacted" value="true"/>
|
||||
</bean>
|
||||
----
|
||||
include-code::./JmsConfiguration[tag=snippet,indent=0]
|
||||
|
||||
To participate in an externally managed transaction, you need to configure a
|
||||
transaction manager and use a listener container that supports externally managed
|
||||
@@ -288,24 +179,9 @@ semantics, at the expense of XA transaction log overhead).
|
||||
|
||||
The following bean definition creates a transaction manager:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>
|
||||
----
|
||||
include-code::./ExternalTxJmsConfiguration[tag=transactionManagerSnippet,indent=0]
|
||||
|
||||
Then we need to add it to our earlier container configuration. The container
|
||||
takes care of the rest. The following example shows how to do so:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
|
||||
<property name="connectionFactory" ref="connectionFactory"/>
|
||||
<property name="destination" ref="destination"/>
|
||||
<property name="messageListener" ref="messageListener"/>
|
||||
<property name="transactionManager" ref="transactionManager"/> <1>
|
||||
</bean>
|
||||
----
|
||||
<1> Our transaction manager.
|
||||
|
||||
|
||||
|
||||
include-code::./ExternalTxJmsConfiguration[tag=jmsContainerSnippet,indent=0]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user