mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
578 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1526ceec1d | |||
| 739d5ba77b | |||
| 19ab92ef59 | |||
| 04313f062e | |||
| e634ced56b | |||
| 1502c2296e | |||
| cc5c7ba186 | |||
| 0269eb80da | |||
| 9cedcd65ef | |||
| 4e8acb9ef2 | |||
| dbbbf37ac9 | |||
| d8216d719b | |||
| 63ced3f3b4 | |||
| 89391fd94c | |||
| 5168e3a38b | |||
| d4893fb32f | |||
| 104a2033c5 | |||
| cc1514be85 | |||
| 6e9758700a | |||
| 37e8aa76e9 | |||
| 27686dc2e2 | |||
| 3b8efbe5a6 | |||
| 6f2e59a995 | |||
| 644a20ae2a | |||
| 77bc13dc8c | |||
| ab9895b5a9 | |||
| ce5c4f3b4b | |||
| cd0c26b6db | |||
| 1203ace285 | |||
| 1219943e73 | |||
| eeddf9a73a | |||
| b2a4bc8900 | |||
| 3a266e65d6 | |||
| 463138acbc | |||
| b8a4961b81 | |||
| 06d4a8fc4b | |||
| 13b0643828 | |||
| 548b2167b0 | |||
| de417beff7 | |||
| ccc6789cb5 | |||
| f73302a66e | |||
| 348482b5a0 | |||
| 25bb264bd9 | |||
| 9819e1cad5 | |||
| ce2402a581 | |||
| c2d0d292bd | |||
| bbe733def7 | |||
| 03391dfa94 | |||
| 50c29e64f8 | |||
| 22301fd069 | |||
| 11ab0b4351 | |||
| 4734c15d81 | |||
| 02af3e7a17 | |||
| 3c6f1b35ba | |||
| 7a420b28df | |||
| 63723fadb6 | |||
| ab7a2c4e70 | |||
| a87b2e0146 | |||
| bd40f0e6bb | |||
| 04186fdf0e | |||
| b2b731b0ba | |||
| a3b9098850 | |||
| 36815f4021 | |||
| 3b1b7ff594 | |||
| 95080cabfd | |||
| 017892265e | |||
| d096a33f13 | |||
| b6833ff31f | |||
| 3bc55c77ec | |||
| 824aa137f8 | |||
| 0dc44f79d5 | |||
| f481f99516 | |||
| 72ed2eaa31 | |||
| 0676c958f7 | |||
| 482519d03b | |||
| d6b7cea327 | |||
| 9eea227ab9 | |||
| 706c98228d | |||
| b83cb4a947 | |||
| 7e79d0e206 | |||
| e58ba2c665 | |||
| 0c9127d111 | |||
| c2088517b9 | |||
| 59b9057e59 | |||
| 2ec630ef45 | |||
| cf2b59b68b | |||
| f1ca29b05d | |||
| ee6a115a18 | |||
| 95a31c4e8c | |||
| e0b54e244e | |||
| f2b64ad09c | |||
| cff48fff2d | |||
| ab8cb8de6e | |||
| 434da06d1f | |||
| a37447e07f | |||
| b1cb9c5052 | |||
| 057633edb5 | |||
| 7299ff9326 | |||
| 53f1656f56 | |||
| 2691489381 | |||
| f103af4982 | |||
| b9a59853fa | |||
| d4b2a493f9 | |||
| 188cb9b24d | |||
| e3568a3f0a | |||
| e106fc0434 | |||
| ca3adf43eb | |||
| 85c18caf25 | |||
| 22bd8bd704 | |||
| 9b10bb5e08 | |||
| 727ccd04ef | |||
| a9f447e8d7 | |||
| 0841e79e32 | |||
| a1868d3e9e | |||
| e5aac66157 | |||
| 8bf85d2596 | |||
| 508b31da4f | |||
| d291272736 | |||
| db01f07037 | |||
| b9e190e313 | |||
| d712ec3d49 | |||
| 5baa4fdd69 | |||
| 9273a11a2c | |||
| 7ea11baff9 | |||
| 655fd2e2bf | |||
| a31574a0a2 | |||
| 44f3e7b427 | |||
| e8e24e65d2 | |||
| 9a002a77b2 | |||
| 97e96895db | |||
| e98c2fe488 | |||
| 75705bcbb1 | |||
| 47a2e7059e | |||
| 14b6339351 | |||
| bc2e89a786 | |||
| 4abbddf601 | |||
| 926bcbd9a6 | |||
| cb8ed43be1 | |||
| 1703388074 | |||
| d84c4a39e2 | |||
| 6f03c186b9 | |||
| e10d37ad54 | |||
| 4734837fe4 | |||
| 4a13655aa5 | |||
| 5ef9972528 | |||
| a9b1d6335e | |||
| 98d4046e4b | |||
| 282fee5c95 | |||
| 0d4737aeec | |||
| 1ef8734881 | |||
| 6dfa54b8ea | |||
| 2d3a2c5cbd | |||
| 62e590a437 | |||
| 7f29df4d79 | |||
| 8fb119df1e | |||
| 1bb0fdda6b | |||
| 6507367d10 | |||
| fee7e6b7e2 | |||
| 35c4973504 | |||
| 77b54c7409 | |||
| ff9ba15762 | |||
| c33b82f2a1 | |||
| ec459aefbf | |||
| b227701473 | |||
| 7a85ce959b | |||
| e93e55b456 | |||
| e22b1648bf | |||
| 78967d4bd6 | |||
| 1d2351c87b | |||
| 02dc4b6342 | |||
| 143828e27c | |||
| 7998bc9de2 | |||
| 8a1a9d2ab2 | |||
| 394a97d7e0 | |||
| 05ffe8249f | |||
| 20970a4a37 | |||
| cb4b87cce2 | |||
| 1d470fc375 | |||
| 7fc619acdc | |||
| e5f8c5b7ae | |||
| 3666ad9d7f | |||
| 652d509cfc | |||
| 153c378bb2 | |||
| cdbaa7f3a7 | |||
| d1b0bdb3d5 | |||
| 59a9d0fcb9 | |||
| db9045ae28 | |||
| 7c2159fbe9 | |||
| f2d3da3f32 | |||
| f1c86b1bbc | |||
| 7fde8a6fa3 | |||
| 033c309d39 | |||
| ac243fae65 | |||
| ad19c1ac82 | |||
| 29d125e659 | |||
| d31bd8139d | |||
| fb10455d3c | |||
| 40350653e1 | |||
| a08a4d6068 | |||
| 6fd84e4c2f | |||
| 516bf1c606 | |||
| 32b55e68fc | |||
| b1f427f137 | |||
| f52481493c | |||
| 149397ed10 | |||
| e1d3076570 | |||
| 16f4b23c32 | |||
| 54c3d01270 | |||
| 799a520c35 | |||
| bf5386712c | |||
| 5d33de943b | |||
| 7da65fe4fc | |||
| 0d612b3b1d | |||
| 4dacc64a30 | |||
| ba47dd0714 | |||
| 79386b0e83 | |||
| d1bfe03358 | |||
| 4bc9f68451 | |||
| ce0e37536b | |||
| b0ac8df13e | |||
| 80a57c2468 | |||
| 02e05b40b0 | |||
| c8c99d0464 | |||
| 867dd0e972 | |||
| 2c96f18439 | |||
| 96fb5d72eb | |||
| bc01eeb433 | |||
| 77c24b28b6 | |||
| e809395635 | |||
| 801035bea7 | |||
| 9e4a54bb99 | |||
| e56f7c1e53 | |||
| 65bdc78a1e | |||
| fd3bbb057e | |||
| c47da1247a | |||
| 22cf7958a5 | |||
| ab314aadd9 | |||
| 611811d8fc | |||
| 0c9f40b171 | |||
| 5b4eda3d20 | |||
| af8fd8f07d | |||
| 345fa51e77 | |||
| c05ea841bc | |||
| dab28b5dce | |||
| 1c4b20287f | |||
| 8fbd147f0b | |||
| 1c56ec3f7e | |||
| 65565de1a8 | |||
| b3c26255b1 | |||
| 66962875f9 | |||
| 5490ba94ae | |||
| 17e02804ba | |||
| 357205723f | |||
| c4c167c0e1 | |||
| 4119ecc25b | |||
| 9ebfdb8b6a | |||
| c4cb55fbde | |||
| 7917ae53e8 | |||
| 693ec8219f | |||
| 9b95482557 | |||
| a029dfc6a8 | |||
| a921c487b4 | |||
| 3f01830d11 | |||
| 64d95d7f3e | |||
| 5a4b1bd1d1 | |||
| a91402dcc6 | |||
| 0014e0b3c9 | |||
| 49da766551 | |||
| 50bbe6191a | |||
| a8c33c13e2 | |||
| b164db35c1 | |||
| 385e62dbf0 | |||
| 4d4adeb2c6 | |||
| 7aa862f5ec | |||
| 2d2809f45c | |||
| 3f390d55f0 | |||
| 2b96a61063 | |||
| c55351aa4a | |||
| 48bedd0af1 | |||
| c1b0e68d6c | |||
| fe2113f5a3 | |||
| 2ff2a89f22 | |||
| 14641c4299 | |||
| 62fd09dfa5 | |||
| 9df19dedaf | |||
| 01a57a7219 | |||
| b5c2003c51 | |||
| 5f5da06467 | |||
| 9f19b4074d | |||
| a784eb0ab4 | |||
| fa404063ab | |||
| 948af8bcc6 | |||
| e3997e3060 | |||
| 0323218129 | |||
| a62699ccfc | |||
| dd6faf62bb | |||
| cbc2c63abc | |||
| 2f83e6b4b5 | |||
| 19c2506476 | |||
| ee4780fab6 | |||
| 8e6d44349d | |||
| ceb6ab3c74 | |||
| 7140ab5bca | |||
| 615c25d4fa | |||
| 3df66580f3 | |||
| 9711db787e | |||
| d3a385d222 | |||
| 38a397873e | |||
| 73fa708905 | |||
| 23a532ed82 | |||
| e0db67aa3b | |||
| 1370a833ed | |||
| 8d4320e9ef | |||
| c53a00a6c4 | |||
| 00606d2171 | |||
| 3e5d8d184a | |||
| b69cbad38d | |||
| 39106b5811 | |||
| 2f91baa71c | |||
| d8e303f95b | |||
| 8ff89ffda2 | |||
| dcf5d69d94 | |||
| 7d33a87278 | |||
| b878771dca | |||
| 06f0f2f317 | |||
| f16474d585 | |||
| dfb17d84d6 | |||
| eea9130ea6 | |||
| d85ac300bb | |||
| d4d10dc75d | |||
| 169465cce1 | |||
| c4d5e3c57d | |||
| 38f5f4de8e | |||
| 644072f7ea | |||
| 859940b824 | |||
| 992ae2ea6b | |||
| 589a10e6e6 | |||
| 79bd2b11da | |||
| 0624c3a597 | |||
| bf695c058d | |||
| a1204a405a | |||
| e0aa116217 | |||
| 7fdda1cf0f | |||
| 05b98b0a32 | |||
| 46efd930f0 | |||
| a9027ddf76 | |||
| 5551292639 | |||
| 1240962d68 | |||
| 9ef4ceb047 | |||
| c7413cbdfc | |||
| e94aaab288 | |||
| c5044bfdbc | |||
| 9abe4b46ca | |||
| 192da6229c | |||
| e1402fd252 | |||
| 253eb28458 | |||
| 97cd9dd03b | |||
| 3027d78f40 | |||
| d752e80356 | |||
| e7f019bd3f | |||
| 6939cb90df | |||
| 3ddaf51f5d | |||
| 6a28f06b2e | |||
| 5ecece91dc | |||
| 13d7a6c480 | |||
| 6e66af15a6 | |||
| cd8e38448c | |||
| 93ce99ea06 | |||
| bf8a1600e5 | |||
| 96eee8c8b1 | |||
| b8ed4f2ba8 | |||
| 0048f01074 | |||
| 0b904a878d | |||
| 9c78f84915 | |||
| a55309d478 | |||
| 46fb7c026c | |||
| d36244ee56 | |||
| 4b07edbaeb | |||
| eb3bfc0d65 | |||
| d077e043d6 | |||
| 7212fbe36a | |||
| 5b6c231788 | |||
| aeb0115605 | |||
| 08104100d3 | |||
| 214cb2c1e1 | |||
| 26e0f550b0 | |||
| 012fb29097 | |||
| 92a43c007a | |||
| fc1ff88ede | |||
| fc2e1dd2e5 | |||
| ffd2e03a9f | |||
| fd6e15bb4d | |||
| 85c6fb0fd0 | |||
| 0b2bb7e751 | |||
| faa17abdae | |||
| 89ca8e6976 | |||
| 7353ab41d2 | |||
| f1db0ef036 | |||
| e9a4b93477 | |||
| 86e89d53a9 | |||
| c813577908 | |||
| ec6b7730a2 | |||
| fc29d88778 | |||
| 033df68da6 | |||
| df5b06dabc | |||
| 3d8d7ffa1e | |||
| 6d5a512d90 | |||
| d4e4250bb1 | |||
| 93bc022a46 | |||
| a4c72c8fcf | |||
| 3d7862abb6 | |||
| 7fd0c5041d | |||
| d99ace81c1 | |||
| ed451c107f | |||
| 452257eb96 | |||
| a7863a0877 | |||
| 1da0e13a2b | |||
| ee6a156275 | |||
| 0b6fe8dcc5 | |||
| e2c9dc7138 | |||
| 1818161f58 | |||
| 8916ee9f81 | |||
| d835fe311d | |||
| 0eefac21c9 | |||
| e99791f289 | |||
| 658775b914 | |||
| ea7a1d789e | |||
| 4ae471df01 | |||
| 75e3f44a7b | |||
| adcd7cb4cb | |||
| d0be180a69 | |||
| 3731fed4ca | |||
| bfc02cda0c | |||
| f734d04a0d | |||
| ce4c9ebe3c | |||
| 7c4801304d | |||
| 2731183420 | |||
| 38cf4ab3fc | |||
| 2137ec70d2 | |||
| 61201db704 | |||
| 2643c6212f | |||
| b46328a000 | |||
| cc4c693db7 | |||
| a206ea8b12 | |||
| 9f1d9fe82c | |||
| ab33000750 | |||
| e2ab9cd5da | |||
| 103db5429a | |||
| 68f8139206 | |||
| a9da900b5b | |||
| e7e4b3559a | |||
| feb77f924a | |||
| 12c3dc0cbe | |||
| df27627516 | |||
| 939aa84214 | |||
| 9f77f401ad | |||
| f19f1a667c | |||
| 69207c6c16 | |||
| f61832ecb8 | |||
| 14790493f4 | |||
| 53d9ba879d | |||
| 9d2d9ae0ff | |||
| c74af40288 | |||
| 196c1dd51c | |||
| 44689b9ec0 | |||
| b916dc962e | |||
| 87d0113b25 | |||
| cee185ce3b | |||
| e45727d4fa | |||
| bc088ebdef | |||
| f1cf91c9f9 | |||
| d36475cb4c | |||
| 8b6d7e0da7 | |||
| 0a86a7e3a8 | |||
| 8edde374bb | |||
| 35b58cd0ee | |||
| dfc900c7c4 | |||
| 07c0213f20 | |||
| 667851c0fa | |||
| 96aadc2b12 | |||
| 2c6ccaea05 | |||
| 62d09be2ae | |||
| 47f65b3dff | |||
| c7e24a5c26 | |||
| a493bd959d | |||
| 92e9543ad4 | |||
| 68231aa08c | |||
| 71d18ebabc | |||
| 3ccb0786db | |||
| 15c16d2c3a | |||
| 5dcb85db8d | |||
| 4ae03ecd40 | |||
| 1603045c7d | |||
| ace2b2bac9 | |||
| f082c435e1 | |||
| c6810334ca | |||
| 3b90311c17 | |||
| 946fc65712 | |||
| fd25e2f468 | |||
| 46ceedf992 | |||
| a4b3111928 | |||
| 4c4161c8c0 | |||
| 61d5413c23 | |||
| 08e6d762d2 | |||
| 7a19cbb452 | |||
| 2f1e1d483a | |||
| 24d152cdab | |||
| cc530d4df2 | |||
| 6c3132cb8c | |||
| a15274d431 | |||
| 45d4fd3b7e | |||
| 3fd5d69939 | |||
| 8647c44364 | |||
| 6504177e7b | |||
| c1125699bb | |||
| f58d0f6aae | |||
| c1b6bfb681 | |||
| 9d4abb63d8 | |||
| a9a404266c | |||
| b29f4ed37e | |||
| 4847ee80b0 | |||
| f4ee120a42 | |||
| e625a28f6d | |||
| 45c1cd9295 | |||
| 2641b5d783 | |||
| dc0c463137 | |||
| 83bbf16e6c | |||
| 3686b89ab5 | |||
| 85ca9f46dd | |||
| e2287020c3 | |||
| 9e18c75ff1 | |||
| fb9f31d101 | |||
| 4ee0a8ee33 | |||
| f3908832c5 | |||
| c10266e57e | |||
| 1b6b163f24 | |||
| 8642a39a03 | |||
| f62cc98a97 | |||
| c599775a9e | |||
| 4bc97ad532 | |||
| abec289e9f | |||
| 3026f0a49b | |||
| 9fe4e7798d | |||
| d178930186 | |||
| 91d2a51f3f | |||
| f456674529 | |||
| 35b8fbf901 | |||
| 1d1851f48a | |||
| 23f0cfb925 | |||
| 2cca56064e | |||
| e0353fe75f | |||
| f80b79bc45 | |||
| adffd3dcf6 | |||
| 284a28659d | |||
| 57a1d4007b | |||
| bc3431f435 | |||
| de5b9aab55 | |||
| f72891c24d | |||
| fac8708857 | |||
| 0342cd0904 | |||
| e0fd42981a | |||
| 87d95dc30a | |||
| 47de8b05e6 | |||
| 3b6be3d4d3 | |||
| 6115c3966c | |||
| 09a8bbc0c7 | |||
| ed75906834 | |||
| 4756a4be23 | |||
| 40c5c5d5f0 | |||
| 6b8552b4c1 | |||
| f42eea183e | |||
| ebcfe113d1 | |||
| b133c2b346 | |||
| 648629fab2 | |||
| f2bf227cd0 | |||
| 1260081436 | |||
| 18d8d458e3 | |||
| 1aa520162b |
@@ -1,6 +1,13 @@
|
||||
changelog:
|
||||
repository: spring-projects/spring-framework
|
||||
sections:
|
||||
- title: ":warning: Attention Required"
|
||||
labels:
|
||||
- "for: upgrade-attention"
|
||||
summary:
|
||||
mode: "member-comment"
|
||||
config:
|
||||
prefix: "Attention Required:"
|
||||
- title: ":star: New Features"
|
||||
labels:
|
||||
- "type: enhancement"
|
||||
|
||||
@@ -24,7 +24,7 @@ runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Set Up Java
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: ${{ inputs.java-early-access == 'true' && 'temurin' || (inputs.java-distribution || 'liberica') }}
|
||||
java-version: |
|
||||
@@ -32,7 +32,7 @@ runs:
|
||||
${{ inputs.java-toolchain == 'true' && '17' || '' }}
|
||||
25
|
||||
- name: Set Up Gradle
|
||||
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
|
||||
with:
|
||||
cache-read-only: false
|
||||
develocity-access-key: ${{ inputs.develocity-access-key }}
|
||||
|
||||
@@ -17,14 +17,14 @@ runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Set Up JFrog CLI
|
||||
uses: jfrog/setup-jfrog-cli@f748a0599171a192a2668afee8d0497f7c1069df # v4.5.6
|
||||
uses: jfrog/setup-jfrog-cli@5b06f730cc5a6f55d78b30753f8583454b08c0aa # v4.8.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/central-publish-action@0cdd90d12e6876341e82860d951e1bcddc1e51b6 # v0.2.0
|
||||
uses: spring-io/central-publish-action@0c03960e9b16fdfe70e2443e1d5393cbc3a35622 # v0.3.0
|
||||
with:
|
||||
token: ${{ inputs.central-token-password }}
|
||||
token-name: ${{ inputs.central-token-username }}
|
||||
|
||||
@@ -18,9 +18,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'liberica'
|
||||
java-version: 17
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Build and Publish
|
||||
id: build-and-publish
|
||||
uses: ./.github/actions/build
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
|
||||
publish: true
|
||||
- name: Deploy
|
||||
uses: spring-io/artifactory-deploy-action@dc1913008c0599f0c4b1fdafb6ff3c502b3565ea # v0.0.2
|
||||
uses: spring-io/artifactory-deploy-action@926d7f7cc810569395346bf3a4d91b380b3e355b # v0.0.4
|
||||
with:
|
||||
artifact-properties: |
|
||||
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Build
|
||||
id: build
|
||||
uses: ./.github/actions/build
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
uses: ./.github/actions/print-jvm-thread-dumps
|
||||
- name: Upload Build Reports
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: build-reports
|
||||
path: '**/build/reports/'
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
git config --global core.longPaths true
|
||||
Stop-Service -name Docker
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Build
|
||||
id: build
|
||||
uses: ./.github/actions/build
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
ref: docs-build
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Build and Publish
|
||||
id: build-and-publish
|
||||
uses: ./.github/actions/build
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
|
||||
publish: true
|
||||
- name: Stage Release
|
||||
uses: spring-io/artifactory-deploy-action@26bbe925a75f4f863e1e529e85be2d0093cac116 # v0.0.1
|
||||
uses: spring-io/artifactory-deploy-action@926d7f7cc810569395346bf3a4d91b380b3e355b # v0.0.4
|
||||
with:
|
||||
artifact-properties: |
|
||||
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
uses: actions/checkout@v6
|
||||
- name: Sync to Maven Central
|
||||
uses: ./.github/actions/sync-to-maven-central
|
||||
with:
|
||||
@@ -73,7 +73,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up JFrog CLI
|
||||
uses: jfrog/setup-jfrog-cli@dff217c085c17666e8849ebdbf29c8fe5e3995e6 # v4.5.2
|
||||
uses: jfrog/setup-jfrog-cli@5b06f730cc5a6f55d78b30753f8583454b08c0aa # v4.8.1
|
||||
env:
|
||||
JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }}
|
||||
- name: Promote build
|
||||
@@ -86,7 +86,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
uses: actions/checkout@v6
|
||||
- name: Create GitHub Release
|
||||
uses: ./.github/actions/create-github-release
|
||||
with:
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Build and Publish
|
||||
id: build-and-publish
|
||||
uses: ./.github/actions/build
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
|
||||
publish: true
|
||||
- name: Stage Release
|
||||
uses: spring-io/artifactory-deploy-action@dc1913008c0599f0c4b1fdafb6ff3c502b3565ea # v0.0.2
|
||||
uses: spring-io/artifactory-deploy-action@926d7f7cc810569395346bf3a4d91b380b3e355b # v0.0.4
|
||||
with:
|
||||
artifact-properties: |
|
||||
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
uses: actions/checkout@v6
|
||||
- name: Sync to Maven Central
|
||||
uses: ./.github/actions/sync-to-maven-central
|
||||
with:
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up JFrog CLI
|
||||
uses: jfrog/setup-jfrog-cli@dff217c085c17666e8849ebdbf29c8fe5e3995e6 # v4.5.2
|
||||
uses: jfrog/setup-jfrog-cli@5b06f730cc5a6f55d78b30753f8583454b08c0aa # v4.8.1
|
||||
env:
|
||||
JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }}
|
||||
- name: Promote build
|
||||
@@ -85,7 +85,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
uses: actions/checkout@v6
|
||||
- name: Create GitHub Release
|
||||
uses: ./.github/actions/create-github-release
|
||||
with:
|
||||
|
||||
@@ -30,23 +30,23 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Release Verification Tests
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
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
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
path: send-notification
|
||||
sparse-checkout: .github/actions/send-notification
|
||||
- name: Set Up Java
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'liberica'
|
||||
java-version: 17
|
||||
- name: Set Up Gradle
|
||||
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
|
||||
with:
|
||||
cache-read-only: false
|
||||
- name: Configure Gradle Properties
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
run: ./gradlew spring-framework-release-verification-tests:test
|
||||
- name: Upload Build Reports on Failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: build-reports
|
||||
path: '**/build/reports/'
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ bin
|
||||
.springBeans
|
||||
spring-*/src/main/java/META-INF/MANIFEST.MF
|
||||
|
||||
# IDEA artifacts and output dirs
|
||||
# IntelliJ IDEA artifacts and output dirs
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
|
||||
+1
-1
@@ -120,7 +120,7 @@ source code into your IDE.
|
||||
The wiki pages
|
||||
[Code Style](https://github.com/spring-projects/spring-framework/wiki/Code-Style) and
|
||||
[IntelliJ IDEA Editor Settings](https://github.com/spring-projects/spring-framework/wiki/IntelliJ-IDEA-Editor-Settings)
|
||||
define the source file coding standards we use along with some IDEA editor settings we customize.
|
||||
define the source file coding standards we use along with some IntelliJ editor settings we customize.
|
||||
|
||||
### Reference Docs
|
||||
|
||||
|
||||
+13
-12
@@ -6,7 +6,7 @@ plugins {
|
||||
id 'com.github.bjornvester.xjc' version '1.8.2' apply false
|
||||
id 'com.gradleup.shadow' version "9.2.2" apply false
|
||||
id 'me.champeau.jmh' version '0.7.2' apply false
|
||||
id 'io.spring.nullability' version '0.0.8' apply false
|
||||
id 'io.spring.nullability' version '0.0.11' apply false
|
||||
}
|
||||
|
||||
ext {
|
||||
@@ -68,17 +68,18 @@ configure([rootProject] + javaProjects) { project ->
|
||||
}
|
||||
|
||||
ext.javadocLinks = [
|
||||
"https://docs.oracle.com/en/java/javase/17/docs/api/",
|
||||
//"https://jakarta.ee/specifications/platform/11/apidocs/",
|
||||
"https://docs.hibernate.org/orm/5.6/javadocs/",
|
||||
"https://www.quartz-scheduler.org/api/2.3.0/",
|
||||
"https://hc.apache.org/httpcomponents-client-5.5.x/current/httpclient5/apidocs/",
|
||||
"https://projectreactor.io/docs/test/release/api/",
|
||||
"https://junit.org/junit4/javadoc/4.13.2/",
|
||||
"https://docs.junit.org/6.0.1/api/",
|
||||
"https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/",
|
||||
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
|
||||
"https://jspecify.dev/docs/api/"
|
||||
"https://docs.oracle.com/en/java/javase/17/docs/api/",
|
||||
//"https://jakarta.ee/specifications/platform/11/apidocs/",
|
||||
"https://docs.hibernate.org/orm/7.2/javadocs/",
|
||||
"https://www.quartz-scheduler.org/api/2.3.0/",
|
||||
"https://hc.apache.org/httpcomponents-client-5.6.x/current/httpclient5/apidocs/",
|
||||
"https://projectreactor.io/docs/core/release/api/",
|
||||
"https://projectreactor.io/docs/test/release/api/",
|
||||
"https://junit.org/junit4/javadoc/4.13.2/",
|
||||
"https://docs.junit.org/6.0.3/api/",
|
||||
"https://www.reactive-streams.org/reactive-streams-1.0.4-javadoc/",
|
||||
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
|
||||
"https://jspecify.dev/docs/api/"
|
||||
] as String[]
|
||||
}
|
||||
|
||||
|
||||
@@ -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("12.1.2");
|
||||
checkstyle.setToolVersion("13.3.0");
|
||||
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
|
||||
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
|
||||
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
|
||||
|
||||
+1
-3
@@ -49,7 +49,6 @@ import static org.springframework.build.architecture.ArchitectureRules.classesSh
|
||||
import static org.springframework.build.architecture.ArchitectureRules.javaClassesShouldNotImportKotlinAnnotations;
|
||||
import static org.springframework.build.architecture.ArchitectureRules.noClassesShouldCallStringToLowerCaseWithoutLocale;
|
||||
import static org.springframework.build.architecture.ArchitectureRules.noClassesShouldCallStringToUpperCaseWithoutLocale;
|
||||
import static org.springframework.build.architecture.ArchitectureRules.packageInfoShouldBeNullMarked;
|
||||
|
||||
/**
|
||||
* {@link Task} that checks for architecture problems.
|
||||
@@ -64,8 +63,7 @@ public abstract class ArchitectureCheck extends DefaultTask {
|
||||
public ArchitectureCheck() {
|
||||
getOutputDirectory().convention(getProject().getLayout().getBuildDirectory().dir(getName()));
|
||||
getProhibitObjectsRequireNonNull().convention(true);
|
||||
getRules().addAll(packageInfoShouldBeNullMarked(),
|
||||
classesShouldNotImportForbiddenTypes(),
|
||||
getRules().addAll(classesShouldNotImportForbiddenTypes(),
|
||||
javaClassesShouldNotImportKotlinAnnotations(),
|
||||
allPackagesShouldBeFreeOfTangles(),
|
||||
noClassesShouldCallStringToLowerCaseWithoutLocale(),
|
||||
|
||||
@@ -46,13 +46,6 @@ abstract class ArchitectureRules {
|
||||
.because("String.toUpperCase(Locale.ROOT) should be used instead");
|
||||
}
|
||||
|
||||
static ArchRule packageInfoShouldBeNullMarked() {
|
||||
return ArchRuleDefinition.classes()
|
||||
.that().haveSimpleName("package-info")
|
||||
.should().beAnnotatedWith("org.jspecify.annotations.NullMarked")
|
||||
.allowEmptyShould(true);
|
||||
}
|
||||
|
||||
static ArchRule classesShouldNotImportForbiddenTypes() {
|
||||
return ArchRuleDefinition.noClasses()
|
||||
.should().dependOnClassesThat()
|
||||
|
||||
+8
-5
@@ -18,6 +18,7 @@ package org.springframework.build.multirelease;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.gradle.api.JavaVersion;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.ConfigurationContainer;
|
||||
@@ -64,10 +65,12 @@ public class MultiReleaseJarPlugin implements Plugin<Project> {
|
||||
dependencies,
|
||||
objects);
|
||||
|
||||
TaskProvider<MultiReleaseJarValidateTask> validateJarTask = tasks.register(VALIDATE_JAR_TASK_NAME, MultiReleaseJarValidateTask.class, (task) -> {
|
||||
task.getJar().set(tasks.named("jar", Jar.class).flatMap(AbstractArchiveTask::getArchiveFile));
|
||||
task.getJavaLauncher().set(task.getJavaToolchainService().launcherFor(spec -> spec.getLanguageVersion().set(JavaLanguageVersion.of(25))));
|
||||
});
|
||||
tasks.named("check", task -> task.dependsOn(validateJarTask));
|
||||
if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_25)) {
|
||||
TaskProvider<MultiReleaseJarValidateTask> validateJarTask = tasks.register(VALIDATE_JAR_TASK_NAME, MultiReleaseJarValidateTask.class, (task) -> {
|
||||
task.getJar().set(tasks.named("jar", Jar.class).flatMap(AbstractArchiveTask::getArchiveFile));
|
||||
task.getJavaLauncher().set(task.getJavaToolchainService().launcherFor(spec -> spec.getLanguageVersion().set(JavaLanguageVersion.of(25))));
|
||||
});
|
||||
tasks.named("check", task -> task.dependsOn(validateJarTask));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ public class ShadowSource extends DefaultTask {
|
||||
}
|
||||
|
||||
@OutputDirectory
|
||||
DirectoryProperty getOutputDirectory() {
|
||||
public DirectoryProperty getOutputDirectory() {
|
||||
return this.outputDirectory;
|
||||
}
|
||||
|
||||
|
||||
+15
@@ -29,6 +29,8 @@ import org.gradle.testkit.runner.GradleRunner;
|
||||
import org.gradle.testkit.runner.UnexpectedBuildFailure;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledForJreRange;
|
||||
import org.junit.jupiter.api.condition.JRE;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -43,10 +45,13 @@ public class MultiReleaseJarPluginTests {
|
||||
|
||||
private File buildFile;
|
||||
|
||||
private File propertiesFile;
|
||||
|
||||
@BeforeEach
|
||||
void setup(@TempDir File projectDir) {
|
||||
this.projectDir = projectDir;
|
||||
this.buildFile = new File(this.projectDir, "build.gradle");
|
||||
this.propertiesFile = new File(this.projectDir, "gradle.properties");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -119,6 +124,7 @@ public class MultiReleaseJarPluginTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisabledForJreRange(max = JRE.JAVA_24, disabledReason = "'jar --validate' is available as of Java 25")
|
||||
void validateJar() throws IOException {
|
||||
writeBuildFile("""
|
||||
plugins {
|
||||
@@ -131,6 +137,9 @@ public class MultiReleaseJarPluginTests {
|
||||
}
|
||||
multiRelease { releaseVersions 17 }
|
||||
""");
|
||||
writeGradleProperties("""
|
||||
org.gradle.jvmargs=-Duser.language=en
|
||||
""");
|
||||
writeClass("src/main/java17", "Main.java", """
|
||||
public class Main {
|
||||
|
||||
@@ -152,6 +161,12 @@ public class MultiReleaseJarPluginTests {
|
||||
}
|
||||
}
|
||||
|
||||
private void writeGradleProperties(String properties) throws IOException {
|
||||
try (PrintWriter out = new PrintWriter(new FileWriter(this.propertiesFile))) {
|
||||
out.print(properties);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeClass(String path, String fileName, String fileContent) throws IOException {
|
||||
Path folder = this.projectDir.toPath().resolve(path);
|
||||
Files.createDirectories(folder);
|
||||
|
||||
@@ -20,6 +20,7 @@ dependencies {
|
||||
}
|
||||
}
|
||||
|
||||
def springAspectsOutput = project(":spring-aspects").sourceSets.main.output
|
||||
javadoc {
|
||||
javadocTool.set(javaToolchains.javadocToolFor({
|
||||
languageVersion = JavaLanguageVersion.of(25)
|
||||
@@ -48,8 +49,8 @@ javadoc {
|
||||
maxMemory = "1024m"
|
||||
doFirst {
|
||||
classpath += files(
|
||||
// ensure the javadoc process can resolve types compiled from .aj sources
|
||||
project(":spring-aspects").sourceSets.main.output
|
||||
// ensure the javadoc process can resolve types compiled from .aj sources
|
||||
springAspectsOutput
|
||||
)
|
||||
classpath += files(moduleProjects.collect { it.sourceSets.main.compileClasspath })
|
||||
}
|
||||
|
||||
@@ -13,7 +13,9 @@ content:
|
||||
- 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']
|
||||
# branches: We include snapshots for main, 6.2.x, and 7.0.x to 9.*.x.
|
||||
branches: ['main', '6.2.x', '{7..9}.+({0..9}).x']
|
||||
# tags: We effectively include all releases from 6.0.9 to 9.*.*.
|
||||
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:
|
||||
@@ -36,4 +38,4 @@ runtime:
|
||||
failure_level: warn
|
||||
ui:
|
||||
bundle:
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.19/ui-bundle.zip
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.25/ui-bundle.zip
|
||||
|
||||
@@ -15,8 +15,8 @@ apply from: "${rootDir}/gradle/publications.gradle"
|
||||
antora {
|
||||
options = [clean: true, fetch: !project.gradle.startParameter.offline, stacktrace: true]
|
||||
environment = [
|
||||
'BUILD_REFNAME': 'HEAD',
|
||||
'BUILD_VERSION': project.version,
|
||||
'BUILD_REFNAME': 'HEAD',
|
||||
'BUILD_VERSION': project.version,
|
||||
]
|
||||
}
|
||||
|
||||
@@ -49,8 +49,8 @@ tasks.withType(KotlinCompilationTask.class).configureEach {
|
||||
javaSources.from = []
|
||||
compilerOptions.jvmTarget = JvmTarget.JVM_17
|
||||
compilerOptions.freeCompilerArgs.addAll(
|
||||
"-Xjdk-release=17", // Needed due to https://youtrack.jetbrains.com/issue/KT-49746
|
||||
"-Xannotation-default-target=param-property" // Upcoming default, see https://youtrack.jetbrains.com/issue/KT-73255
|
||||
"-Xjdk-release=17", // Needed due to https://youtrack.jetbrains.com/issue/KT-49746
|
||||
"-Xannotation-default-target=param-property" // Upcoming default, see https://youtrack.jetbrains.com/issue/KT-73255
|
||||
)
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ dependencies {
|
||||
implementation("com.github.ben-manes.caffeine:caffeine")
|
||||
implementation("com.mchange:c3p0:0.9.5.5")
|
||||
implementation("com.oracle.database.jdbc:ojdbc11")
|
||||
implementation("io.micrometer:context-propagation")
|
||||
implementation("io.micrometer:context-propagation")
|
||||
implementation("io.projectreactor.netty:reactor-netty-http")
|
||||
implementation("jakarta.jms:jakarta.jms-api")
|
||||
implementation("jakarta.servlet:jakarta.servlet-api")
|
||||
@@ -80,12 +80,14 @@ dependencies {
|
||||
implementation("javax.cache:cache-api")
|
||||
implementation("org.apache.activemq:activemq-ra:6.1.2")
|
||||
implementation("org.apache.commons:commons-dbcp2:2.11.0")
|
||||
implementation("org.apache.groovy:groovy-templates")
|
||||
implementation("org.aspectj:aspectjweaver")
|
||||
implementation("org.assertj:assertj-core")
|
||||
implementation("org.eclipse.jetty.websocket:jetty-websocket-jetty-api")
|
||||
implementation("org.freemarker:freemarker")
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
|
||||
implementation("org.junit.jupiter:junit-jupiter-api")
|
||||
implementation("tools.jackson.core:jackson-databind")
|
||||
implementation("tools.jackson.dataformat:jackson-dataformat-xml")
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 158 KiB |
@@ -17,6 +17,7 @@
|
||||
class="st5"
|
||||
id="svg5499"
|
||||
version="1.1"
|
||||
font-family="Helvetica, Arial, sans-serif"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="mvc-splitted-contexts.svg"
|
||||
style="font-size:12px;overflow:visible;color-interpolation-filters:sRGB;fill:none;fill-rule:evenodd;stroke-linecap:square;stroke-miterlimit:3"
|
||||
@@ -36,7 +37,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
id="path9164" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -47,7 +48,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
id="path8836" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -58,7 +59,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
id="path8520" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -69,7 +70,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
id="path8216" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -80,7 +81,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
id="path7924" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -91,7 +92,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
id="path7644" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -102,7 +103,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
id="path7375" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -113,7 +114,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
id="path7119" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -124,7 +125,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
id="path6875" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -135,7 +136,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
id="path6643" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -146,7 +147,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
id="path6423" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -157,7 +158,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
id="path6215" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -168,7 +169,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
id="path6019" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -179,7 +180,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
id="path5835" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -190,7 +191,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
id="path5663" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -201,7 +202,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
id="path5503" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -212,7 +213,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
id="path5355" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -223,7 +224,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
id="path5219" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -234,7 +235,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
id="path5095" /></marker><marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
@@ -245,7 +246,7 @@
|
||||
inkscape:stockid="Arrow2Mend"><path
|
||||
transform="scale(0.6) rotate(180) translate(0,0)"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
id="path4983" /></marker><marker
|
||||
inkscape:stockid="Arrow2Mend"
|
||||
orient="auto"
|
||||
@@ -256,7 +257,7 @@
|
||||
inkscape:isstock="true"
|
||||
inkscape:collect="always"><path
|
||||
id="path7394"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
transform="scale(0.6) rotate(180) translate(0,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow2Lend"
|
||||
@@ -267,7 +268,7 @@
|
||||
style="overflow:visible;"
|
||||
inkscape:isstock="true"><path
|
||||
id="path8123"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
transform="scale(1.1) rotate(180) translate(1,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
@@ -279,7 +280,7 @@
|
||||
inkscape:isstock="true"><path
|
||||
id="path8035"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke:#333333;stroke-width:1pt;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
transform="scale(0.4) rotate(180) translate(10,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
orient="auto"
|
||||
@@ -290,7 +291,7 @@
|
||||
inkscape:isstock="true"><path
|
||||
id="path7959"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke:#333333;stroke-width:1pt;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
transform="scale(0.4) rotate(180) translate(10,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
orient="auto"
|
||||
@@ -301,7 +302,7 @@
|
||||
inkscape:isstock="true"><path
|
||||
id="path7376"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke:#333333;stroke-width:1pt;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
transform="scale(0.4) rotate(180) translate(10,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow2Lend"
|
||||
orient="auto"
|
||||
@@ -311,7 +312,7 @@
|
||||
style="overflow:visible;"
|
||||
inkscape:isstock="true"><path
|
||||
id="path7388"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
transform="scale(1.1) rotate(180) translate(1,0)" /></marker><marker
|
||||
inkscape:stockid="Arrow1Lend"
|
||||
@@ -323,7 +324,7 @@
|
||||
inkscape:isstock="true"><path
|
||||
id="path7370"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
style="fill-rule:evenodd;stroke:#333333;stroke-width:1pt;stroke-opacity:1;fill:#333333;fill-opacity:1"
|
||||
transform="scale(0.8) rotate(180) translate(12.5,0)" /></marker></defs><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
@@ -352,9 +353,9 @@
|
||||
v:viewMarkup="false" /><style
|
||||
type="text/css"
|
||||
id="style5501"><![CDATA[
|
||||
.st1 {fill:#969696;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
|
||||
.st2 {fill:#dde2cd;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
|
||||
.st3 {fill:#000000;font-family:Arial;font-size:2.50001em;font-weight:bold}
|
||||
.st1 {fill:#969696;stroke:#333333;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
|
||||
.st2 {fill:#F8F9FA;stroke:#333333;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
|
||||
.st3 {fill:#333333;font-family:Helvetica, Arial, sans-serif;font-size:2.50001em;font-weight:bold}
|
||||
.st4 {font-size:0.333333em;font-weight:normal}
|
||||
.st5 {fill:none;fill-rule:evenodd;font-size:12;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
|
||||
]]></style><g
|
||||
@@ -363,20 +364,22 @@
|
||||
v:groupContext="foregroundPage"
|
||||
id="g5503"
|
||||
transform="matrix(0.99998201,0,0,1.0824094,-40.812382,-98.908648)"><rect
|
||||
style="fill:#dde2cd;fill-opacity:1;stroke:#000000;stroke-width:1.53790233;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
style="fill:#F8F9FA;fill-opacity:1;stroke:#6db33f;stroke-width:2;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6599"
|
||||
width="382.68423"
|
||||
height="146.09897"
|
||||
x="87.884865"
|
||||
y="148.26482" /><v:userDefs><v:ud
|
||||
y="148.26482"
|
||||
rx="8" /><v:userDefs><v:ud
|
||||
v:nameU="SchemeName"
|
||||
v:val="VT4(Default)" /></v:userDefs><rect
|
||||
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1.53790233;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
style="fill:none;fill-opacity:1;stroke:#6db33f;stroke-width:2;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5725"
|
||||
width="464.31128"
|
||||
height="374.11411"
|
||||
x="41.684383"
|
||||
y="112.3262" /><title
|
||||
y="112.3262"
|
||||
rx="8" /><title
|
||||
id="title5505">Page-1</title><v:pageProperties
|
||||
v:drawingScale="0.0393701"
|
||||
v:pageScale="0.0393701"
|
||||
@@ -385,15 +388,16 @@
|
||||
v:shadowOffsetY="-8.50394" /><v:layer
|
||||
v:name="Connector"
|
||||
v:index="0" /><rect
|
||||
style="fill:#dde2cd;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.53790233;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
style="fill:#F8F9FA;fill-opacity:1;fill-rule:evenodd;stroke:#6db33f;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5715"
|
||||
width="322.8194"
|
||||
height="43.63184"
|
||||
x="119.95335"
|
||||
y="-135.66222"
|
||||
transform="scale(1,-1)" /><text
|
||||
transform="scale(1,-1)"
|
||||
rx="8" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:30.7580471px;line-height:125%;font-family:sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="168.843"
|
||||
y="124.32391"
|
||||
id="text5717"
|
||||
@@ -401,12 +405,12 @@
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan5719"
|
||||
x="168.843"
|
||||
x="172.843"
|
||||
y="124.32391"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:sans-serif;-inkscape-font-specification:sans-serif">DispatcherServlet</tspan></text>
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;fill:#333333">DispatcherServlet</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="89.770851"
|
||||
y="181.20923"
|
||||
id="text6589"
|
||||
@@ -414,12 +418,12 @@
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6591"
|
||||
x="89.770851"
|
||||
x="106.770851"
|
||||
y="181.20923"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:sans-serif;-inkscape-font-specification:sans-serif">Servlet WebApplicationContext</tspan></text>
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;fill:#333333">Servlet WebApplicationContext</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="260.00443"
|
||||
y="198.41273"
|
||||
id="text6593"
|
||||
@@ -429,36 +433,39 @@
|
||||
id="tspan6595"
|
||||
x="260.00443"
|
||||
y="198.41273"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle">(containing controllers, view resolvers,</tspan><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle;fill:#555555">(containing controllers, view resolvers,</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="260.00443"
|
||||
y="212.83057"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle;fill:#555555"
|
||||
id="tspan6597">and other web-related beans)</tspan></text>
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.86203903;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6620"
|
||||
width="82.040657"
|
||||
height="36.72575"
|
||||
x="114.52653"
|
||||
y="-259.43161"
|
||||
transform="scale(1,-1)" /><rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.89166164;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
transform="scale(1,-1)"
|
||||
rx="4" /><rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6622"
|
||||
width="87.843979"
|
||||
height="36.697304"
|
||||
x="223.39864"
|
||||
y="-287.19809"
|
||||
transform="scale(1,-1)" /><rect
|
||||
transform="scale(1,-1)"
|
||||
rx="4" /><rect
|
||||
transform="scale(1,-1)"
|
||||
y="-264.81918"
|
||||
x="117.92834"
|
||||
height="36.72575"
|
||||
width="82.040657"
|
||||
id="rect6614"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.86203903;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><text
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
rx="4" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="121.24728"
|
||||
y="260.14957"
|
||||
id="text6616"
|
||||
@@ -466,20 +473,20 @@
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6618"
|
||||
x="121.24728"
|
||||
x="124.24728"
|
||||
y="260.14957"
|
||||
style="font-size:11.53426838px">Controllers</tspan></text>
|
||||
style="font-size:11.53426838px;fill:#333333">Controllers</tspan></text>
|
||||
<text
|
||||
transform="scale(1.0403984,0.96117025)"
|
||||
sodipodi:linespacing="125%"
|
||||
id="text6624"
|
||||
y="282.70709"
|
||||
x="219.61203"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-size:11.53426838px"
|
||||
style="font-size:11.53426838px;fill:#333333"
|
||||
y="282.70709"
|
||||
x="219.61203"
|
||||
x="221.61203"
|
||||
id="tspan6626"
|
||||
sodipodi:role="line">ViewResolver</tspan></text>
|
||||
<rect
|
||||
@@ -489,9 +496,10 @@
|
||||
height="36.577778"
|
||||
width="114.4539"
|
||||
id="rect6628"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.0161339;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><text
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
rx="4" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="327.51276"
|
||||
y="255.28464"
|
||||
id="text6630"
|
||||
@@ -499,26 +507,27 @@
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6632"
|
||||
x="327.51276"
|
||||
x="332.51276"
|
||||
y="255.28464"
|
||||
style="font-size:11.53426838px">HandlerMapping</tspan></text>
|
||||
style="font-size:11.53426838px;fill:#333333">HandlerMapping</tspan></text>
|
||||
<rect
|
||||
y="338.69724"
|
||||
x="87.803261"
|
||||
height="121.5683"
|
||||
width="382.84744"
|
||||
id="rect6634"
|
||||
style="fill:#dde2cd;fill-opacity:1;stroke:#000000;stroke-width:1.53790233;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><text
|
||||
style="fill:#F8F9FA;fill-opacity:1;stroke:#6db33f;stroke-width:2;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
rx="8" /><text
|
||||
transform="scale(1.0403984,0.96117025)"
|
||||
sodipodi:linespacing="125%"
|
||||
id="text6636"
|
||||
y="376.61673"
|
||||
x="108.61351"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:sans-serif;-inkscape-font-specification:sans-serif"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;fill:#333333"
|
||||
y="376.61673"
|
||||
x="108.61351"
|
||||
x="113.61351"
|
||||
id="tspan6638"
|
||||
sodipodi:role="line">Root WebApplicationContext</tspan></text>
|
||||
<text
|
||||
@@ -527,10 +536,10 @@
|
||||
id="text6640"
|
||||
y="395.35812"
|
||||
x="260.93863"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
id="tspan6644"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle;fill:#555555"
|
||||
y="395.35812"
|
||||
x="260.93863"
|
||||
sodipodi:role="line">(containing middle-tier services, datasources, etc.)</tspan></text>
|
||||
@@ -541,43 +550,47 @@
|
||||
height="36.72575"
|
||||
width="82.040657"
|
||||
id="rect6648"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.86203903;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.86203903;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
rx="4" /><rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6650"
|
||||
width="82.040657"
|
||||
height="36.72575"
|
||||
x="167.73116"
|
||||
y="-445.45563"
|
||||
transform="scale(1,-1)" /><text
|
||||
transform="scale(1,-1)"
|
||||
rx="4" /><text
|
||||
transform="scale(1.0403984,0.96117025)"
|
||||
sodipodi:linespacing="125%"
|
||||
id="text6652"
|
||||
y="448.55054"
|
||||
x="175.87148"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-size:11.53426838px"
|
||||
style="font-size:11.53426838px;fill:#333333"
|
||||
y="448.55054"
|
||||
x="175.87148"
|
||||
x="178.87148"
|
||||
id="tspan6654"
|
||||
sodipodi:role="line">Services</tspan></text>
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.88435173;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6656"
|
||||
width="86.393044"
|
||||
height="36.704323"
|
||||
x="306.86328"
|
||||
y="-439.60837"
|
||||
transform="scale(1,-1)" /><rect
|
||||
transform="scale(1,-1)"
|
||||
rx="4" /><rect
|
||||
transform="scale(1,-1)"
|
||||
y="-444.99475"
|
||||
x="310.26624"
|
||||
height="36.701977"
|
||||
width="86.876686"
|
||||
id="rect6658"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.88679528;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><text
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
|
||||
rx="4" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="305.30771"
|
||||
y="448.55054"
|
||||
id="text6660"
|
||||
@@ -585,20 +598,20 @@
|
||||
transform="scale(1.0403984,0.96117025)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6662"
|
||||
x="305.30771"
|
||||
x="308.30771"
|
||||
y="448.55054"
|
||||
style="font-size:11.53426838px">Repositories</tspan></text>
|
||||
style="font-size:11.53426838px;fill:#333333">Repositories</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.76895118px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#333333;stroke-width:0.76895118px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 265.33234,295.60379 c 0,42.65169 0,42.65169 0,0 z"
|
||||
id="path7643"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:3.46028023;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow2Mend)"
|
||||
style="fill:#333333;fill-opacity:1;fill-rule:evenodd;stroke:#333333;stroke-width:3.46028023;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow2Mend)"
|
||||
d="m 270.43505,294.70585 c 0,39.4721 0,39.87903 0,39.87903"
|
||||
id="path7645"
|
||||
inkscape:connector-curvature="0" /><text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="352.55331"
|
||||
y="333.03622"
|
||||
id="text4963"
|
||||
@@ -607,6 +620,6 @@
|
||||
sodipodi:role="line"
|
||||
x="352.55331"
|
||||
y="333.03622"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle;fill:#555555"
|
||||
id="tspan4965">Delegates if no bean found</tspan></text>
|
||||
</g></svg>
|
||||
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 31 KiB |
@@ -261,7 +261,6 @@
|
||||
**** xref:web/websocket/stomp/configuration-performance.adoc[]
|
||||
**** xref:web/websocket/stomp/stats.adoc[]
|
||||
**** xref:web/websocket/stomp/testing.adoc[]
|
||||
** xref:web/integration.adoc[]
|
||||
* xref:web-reactive.adoc[]
|
||||
** xref:web/webflux.adoc[]
|
||||
*** xref:web/webflux/new-framework.adoc[]
|
||||
@@ -331,9 +330,10 @@
|
||||
*** xref:testing/testcontext-framework/application-events.adoc[]
|
||||
*** xref:testing/testcontext-framework/test-execution-events.adoc[]
|
||||
*** xref:testing/testcontext-framework/ctx-management.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/javaconfig.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/xml.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/groovy.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/javaconfig.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/default-config.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/mixed-config.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/context-customizers.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/initializers.adoc[]
|
||||
@@ -344,6 +344,7 @@
|
||||
**** xref:testing/testcontext-framework/ctx-management/web.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/web-mocks.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/caching.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/context-pausing.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/failure-threshold.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/hierarchies.adoc[]
|
||||
*** xref:testing/testcontext-framework/fixture-di.adoc[]
|
||||
|
||||
@@ -81,6 +81,11 @@ resolvable otherwise. See
|
||||
{spring-framework-api}++/core/env/AbstractEnvironment.html#IGNORE_GETENV_PROPERTY_NAME++[`AbstractEnvironment`]
|
||||
for details.
|
||||
|
||||
| `spring.http.response.flush.enabled`
|
||||
| Configures the Spring MVC `ServletServerHttpResponse` to allow flushing on the `OutputStream`
|
||||
returned by `ServletServerHttpResponse#getBody()`. By default, such flush calls are ignored and
|
||||
only `ServletServerHttpResponse#flush()` will actually flush the response to the network.
|
||||
|
||||
| `spring.jdbc.getParameterType.ignore`
|
||||
| Instructs Spring to ignore `java.sql.ParameterMetaData.getParameterType` completely.
|
||||
See the note in xref:data-access/jdbc/advanced.adoc#jdbc-batch-list[Batch Operations with a List of Objects].
|
||||
@@ -124,6 +129,10 @@ on a test class. See xref:testing/annotations/integration-junit-jupiter.adoc#int
|
||||
| The maximum size of the context cache in the _Spring TestContext Framework_. See
|
||||
xref:testing/testcontext-framework/ctx-management/caching.adoc[Context Caching].
|
||||
|
||||
| `spring.test.context.cache.pause`
|
||||
| The pause mode for the context cache in the _Spring TestContext Framework_. See
|
||||
xref:testing/testcontext-framework/ctx-management/context-pausing.adoc[Context Pausing].
|
||||
|
||||
| `spring.test.context.failure.threshold`
|
||||
| The failure threshold for errors encountered while attempting to load an `ApplicationContext`
|
||||
in the _Spring TestContext Framework_. See
|
||||
|
||||
@@ -727,7 +727,7 @@ of determining parameter names, an exception will be thrown.
|
||||
parameter names. This discoverer is only used if such APIs are present on the classpath.
|
||||
`StandardReflectionParameterNameDiscoverer` :: Uses the standard `java.lang.reflect.Parameter`
|
||||
API to determine parameter names. Requires that code be compiled with the `-parameters`
|
||||
flag for `javac`. Recommended approach on Java 8+.
|
||||
flag for `javac`. Recommended approach.
|
||||
`AspectJAdviceParameterNameDiscoverer` :: Deduces parameter names from the pointcut
|
||||
expression, `returning`, and `throwing` clauses. See the
|
||||
{spring-framework-api}/aop/aspectj/AspectJAdviceParameterNameDiscoverer.html[javadoc]
|
||||
|
||||
@@ -104,7 +104,7 @@ Note that pointcut definitions are generally matched against any intercepted met
|
||||
If a pointcut is strictly meant to be public-only, even in a CGLIB proxy scenario with
|
||||
potential non-public interactions through proxies, it needs to be defined accordingly.
|
||||
|
||||
If your interception needs include method calls or even constructors within the target
|
||||
If your interception needs to include method calls or even constructors within the target
|
||||
class, consider the use of Spring-driven xref:core/aop/using-aspectj.adoc#aop-aj-ltw[native AspectJ weaving] instead
|
||||
of Spring's proxy-based AOP framework. This constitutes a different mode of AOP usage
|
||||
with different characteristics, so be sure to make yourself familiar with weaving
|
||||
|
||||
@@ -19,10 +19,6 @@ its behavior changes.
|
||||
| Initialization of `SmartInitializingSingleton` beans.
|
||||
| `beanName` the name of the bean.
|
||||
|
||||
| `spring.context.annotated-bean-reader.create`
|
||||
| Creation of the `AnnotatedBeanDefinitionReader`.
|
||||
|
|
||||
|
||||
| `spring.context.base-packages.scan`
|
||||
| Scanning of base packages.
|
||||
| `packages` array of base packages for scanning.
|
||||
|
||||
+10
-10
@@ -151,17 +151,17 @@ injected into a `Set<MovieCatalog>` annotated with `@Qualifier("action")`.
|
||||
[TIP]
|
||||
====
|
||||
Letting qualifier values select against target bean names, within the type-matching
|
||||
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).
|
||||
candidates, does not require a `@Qualifier` annotation at the injection point. If there
|
||||
is no other resolution indicator (such as a qualifier, a primary marker, or a fallback
|
||||
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).
|
||||
|
||||
Since version 6.1, this requires the `-parameters` Java compiler flag to be present.
|
||||
As of 6.2, the container applies fast shortcut resolution for bean name matches,
|
||||
bypassing the full type matching algorithm when the parameter name matches the
|
||||
bean name and no type, qualifier or primary conditions override the match. It is
|
||||
therefore recommendable for your parameter names to match the target bean names.
|
||||
Since version 6.1, this requires the `-parameters` Java compiler flag to be present. As
|
||||
of 6.2, the container applies fast shortcut resolution for bean name matches, bypassing
|
||||
the full type matching algorithm when the parameter name matches the bean name and no
|
||||
type, qualifier, primary, or fallback conditions override the match. It is therefore
|
||||
recommendable for your parameter names to match the target bean names.
|
||||
====
|
||||
|
||||
As an alternative for injection by name, consider the JSR-250 `@Resource` annotation
|
||||
|
||||
@@ -309,7 +309,8 @@ set of multiple matches for the specific bean type (as returned by the factory m
|
||||
|
||||
Note that the standard `jakarta.annotation.Priority` annotation is not available at the
|
||||
`@Bean` level, since it cannot be declared on methods. Its semantics can be modeled
|
||||
through `@Order` values in combination with `@Primary` on a single bean for each type.
|
||||
through `@Order` values in combination with `@Primary` or `@Fallback` on a single bean
|
||||
for each type.
|
||||
====
|
||||
|
||||
Even typed `Map` instances can be autowired as long as the expected key type is `String`.
|
||||
|
||||
+2
-1
@@ -27,4 +27,5 @@ with the `CustomAutowireConfigurer`
|
||||
|
||||
When multiple beans qualify as autowire candidates, the determination of a "`primary`" is
|
||||
as follows: If exactly one bean definition among the candidates has a `primary`
|
||||
attribute set to `true`, it is selected.
|
||||
attribute set to `true`, it is selected. For annotation-based configuration, see
|
||||
xref:core/beans/annotation-config/autowired-primary.adoc[Fine-tuning with `@Primary` or `@Fallback`].
|
||||
|
||||
@@ -556,18 +556,11 @@ Kotlin::
|
||||
======
|
||||
|
||||
If you do not want to rely on the default bean-naming strategy, you can provide a custom
|
||||
bean-naming strategy. First, implement the
|
||||
{spring-framework-api}/beans/factory/support/BeanNameGenerator.html[`BeanNameGenerator`]
|
||||
bean-naming strategy. First, implement either the
|
||||
{spring-framework-api}/beans/factory/support/BeanNameGenerator.html[`BeanNameGenerator`] or
|
||||
{spring-framework-api}/context/annotation/ConfigurationBeanNameGenerator.html[`ConfigurationBeanNameGenerator`]
|
||||
interface, and be sure to include a default no-arg constructor. Then, provide the fully
|
||||
qualified class name when configuring the scanner, as the following example annotation
|
||||
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
|
||||
`FullyQualifiedAnnotationBeanNameGenerator` located in package
|
||||
`org.springframework.context.annotation` can be used for such purposes.
|
||||
qualified class name when configuring the scanner, as the following examples show.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -602,6 +595,27 @@ Kotlin::
|
||||
</beans>
|
||||
----
|
||||
|
||||
[TIP]
|
||||
====
|
||||
If you run into naming conflicts due to multiple autodetected components having the same
|
||||
non-qualified class name (for example, classes with identical names but residing in
|
||||
different packages), you can configure a `BeanNameGenerator` that defaults to the
|
||||
fully-qualified class name for the generated bean name. The
|
||||
`FullyQualifiedAnnotationBeanNameGenerator` can be used for such purposes.
|
||||
|
||||
As of Spring Framework 7.0, if you encounter naming conflicts among `@Bean` methods in
|
||||
`@Configuration` classes, you can alternatively configure a
|
||||
`ConfigurationBeanNameGenerator` that generates unique bean names for `@Bean` methods.
|
||||
The `FullyQualifiedConfigurationBeanNameGenerator` can be used to generate
|
||||
fully-qualified default bean names for `@Bean` methods without an explicit `name`
|
||||
attribute — for example, `com.example.MyConfig.myBean` for an `@Bean` method named
|
||||
`myBean()` declared in `@Configuration` class `com.example.MyConfig`.
|
||||
|
||||
The `FullyQualifiedAnnotationBeanNameGenerator` and
|
||||
`FullyQualifiedConfigurationBeanNameGenerator` both reside in the
|
||||
`org.springframework.context.annotation` package.
|
||||
====
|
||||
|
||||
As a general rule, consider specifying the name with the annotation whenever other
|
||||
components may be making explicit references to it. On the other hand, the
|
||||
auto-generated names are adequate whenever the container is responsible for wiring.
|
||||
|
||||
+2
-3
@@ -50,9 +50,8 @@ XML configuration:
|
||||
|
||||
The preceding XML is more succinct. However, typos are discovered at runtime rather than
|
||||
design time, unless you use an IDE (such as https://www.jetbrains.com/idea/[IntelliJ
|
||||
IDEA] or the {spring-site-tools}[Spring Tools for Eclipse])
|
||||
that supports automatic property completion when you create bean definitions. Such IDE
|
||||
assistance is highly recommended.
|
||||
IDEA] or the {spring-site-tools}[Spring Tools]) that supports automatic property
|
||||
completion when you create bean definitions. Such IDE assistance is highly recommended.
|
||||
|
||||
You can also configure a `java.util.Properties` instance, as follows:
|
||||
|
||||
|
||||
@@ -67,6 +67,13 @@ interface, clearly indicating the post-processor nature of that bean. Otherwise,
|
||||
Since a `BeanPostProcessor` needs to be instantiated early in order to apply to the
|
||||
initialization of other beans in the context, this early type detection is critical.
|
||||
|
||||
Furthermore, when registering a `BeanPostProcessor` via an `@Bean` factory method,
|
||||
declare the method as `static` and ideally with no dependencies. Doing so avoids eager
|
||||
initialization of the configuration class and other beans, which would make them
|
||||
ineligible for full post-processing (such as auto-proxying). See the
|
||||
"BeanPostProcessor-returning `@Bean` methods" section in the
|
||||
{spring-framework-api}/context/annotation/Bean.html[`@Bean`] javadoc for details.
|
||||
|
||||
[[beans-factory-programmatically-registering-beanpostprocessors]]
|
||||
.Programmatically registering `BeanPostProcessor` instances
|
||||
NOTE: While the recommended approach for `BeanPostProcessor` registration is through
|
||||
@@ -80,7 +87,7 @@ of execution. Note also that `BeanPostProcessor` instances registered programmat
|
||||
are always processed before those registered through auto-detection, regardless of any
|
||||
explicit ordering.
|
||||
|
||||
.`BeanPostProcessor` instances and AOP auto-proxying
|
||||
.`BeanPostProcessor` instances and early initialization
|
||||
[NOTE]
|
||||
====
|
||||
Classes that implement the `BeanPostProcessor` interface are special and are treated
|
||||
@@ -90,17 +97,23 @@ of the `ApplicationContext`. Next, all `BeanPostProcessor` instances are registe
|
||||
in a sorted fashion and applied to all further beans in the container. Because AOP
|
||||
auto-proxying is implemented as a `BeanPostProcessor` itself, neither `BeanPostProcessor`
|
||||
instances nor the beans they directly reference are eligible for auto-proxying and,
|
||||
thus, do not have aspects woven into them.
|
||||
thus, do not have aspects woven into them. More generally, any bean that is instantiated
|
||||
during this early phase is not eligible for full post-processing by all
|
||||
`BeanPostProcessor` instances.
|
||||
|
||||
For any such bean, you should see an informational log message: `Bean someBean is not
|
||||
eligible for getting processed by all BeanPostProcessor interfaces (for example: not
|
||||
eligible for auto-proxying)`.
|
||||
For any such bean, you should see a WARN-level log message similar to the following.
|
||||
|
||||
If you have beans wired into your `BeanPostProcessor` by using autowiring or
|
||||
`@Resource` (which may fall back to autowiring), Spring might access unexpected beans
|
||||
when searching for type-matching dependency candidates and, therefore, make them
|
||||
ineligible for auto-proxying or other kinds of bean post-processing. For example, if you
|
||||
have a dependency annotated with `@Resource` where the field or setter name does not
|
||||
[quote]
|
||||
Bean 'someBean' of type [org.example.SomeType] is not eligible for getting processed by
|
||||
all BeanPostProcessors (for example: not eligible for auto-proxying).
|
||||
|
||||
To minimize the number of beans affected, register a `BeanPostProcessor` with a
|
||||
`static` `@Bean` method that has no dependencies (see the note above). If you have
|
||||
beans wired into your `BeanPostProcessor` by using autowiring or `@Resource` (which
|
||||
may fall back to autowiring), Spring might access unexpected beans when searching
|
||||
for type-matching dependency candidates and, therefore, make them ineligible for
|
||||
auto-proxying or other kinds of bean post-processing. For example, if you have a
|
||||
dependency annotated with `@Resource` where the field or setter name does not
|
||||
directly correspond to the declared name of a bean and no name attribute is used,
|
||||
Spring accesses other beans for matching them by type.
|
||||
====
|
||||
@@ -135,7 +148,7 @@ Java::
|
||||
}
|
||||
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) {
|
||||
System.out.println("Bean '" + beanName + "' created : " + bean.toString());
|
||||
System.out.println("Bean '" + beanName + "' created : " + bean);
|
||||
return bean;
|
||||
}
|
||||
}
|
||||
@@ -164,7 +177,48 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
The following `beans` element uses the `InstantiationTracingBeanPostProcessor`:
|
||||
You can register the `InstantiationTracingBeanPostProcessor` with Java configuration
|
||||
by using a `static` `@Bean` method (recommended to avoid eager initialization of the
|
||||
configuration class and other beans):
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
|
||||
@Bean
|
||||
public static InstantiationTracingBeanPostProcessor instantiationTracingBeanPostProcessor() {
|
||||
return new InstantiationTracingBeanPostProcessor();
|
||||
}
|
||||
|
||||
// ... other bean definitions
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
@Configuration
|
||||
class AppConfig {
|
||||
|
||||
@Bean
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun instantiationTracingBeanPostProcessor() = InstantiationTracingBeanPostProcessor()
|
||||
}
|
||||
|
||||
// ... other bean definitions
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Alternatively, the `InstantiationTracingBeanPostProcessor` can be registered via the
|
||||
`bean` element with XML configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -192,7 +246,7 @@ The following `beans` element uses the `InstantiationTracingBeanPostProcessor`:
|
||||
----
|
||||
|
||||
Notice how the `InstantiationTracingBeanPostProcessor` is merely defined. It does not
|
||||
even have a name, and, because it is a bean, it can be dependency-injected as you would any
|
||||
even have a name, and, because it is a bean, it can be dependency-injected as with any
|
||||
other bean. (The preceding configuration also defines a bean that is backed by a Groovy
|
||||
script.)
|
||||
|
||||
@@ -300,6 +354,23 @@ implement the `BeanFactoryPostProcessor` interface. It uses these beans as bean
|
||||
post-processors, at the appropriate time. You can deploy these post-processor beans as
|
||||
you would any other bean.
|
||||
|
||||
When registering a `BeanFactoryPostProcessor` via an `@Bean` factory method in a
|
||||
`@Configuration` class, declare the method as `static` to avoid lifecycle conflicts
|
||||
with annotation processing (such as `@Autowired`, `@Value`, and `@PostConstruct`) in the
|
||||
configuration class. See the "BeanFactoryPostProcessor-returning `@Bean` methods"
|
||||
section in the {spring-framework-api}/context/annotation/Bean.html[`@Bean`] javadoc
|
||||
for details and an example.
|
||||
|
||||
For any non-static `@Bean` factory method with a `BeanFactoryPostProcessor` return type,
|
||||
you should see an INFO-level log message similar to the following.
|
||||
|
||||
[quote]
|
||||
@Bean method MyConfig.myBfpp is non-static and returns an object assignable to Spring's
|
||||
BeanFactoryPostProcessor interface. This will result in a failure to process annotations
|
||||
such as @Autowired, @Resource, and @PostConstruct within the method's declaring
|
||||
@Configuration class. Add the 'static' modifier to this method to avoid these container
|
||||
lifecycle issues; see @Bean javadoc for complete details.
|
||||
|
||||
NOTE: As with ``BeanPostProcessor``s , you typically do not want to configure
|
||||
``BeanFactoryPostProcessor``s for lazy initialization. If no other bean references a
|
||||
`Bean(Factory)PostProcessor`, that post-processor will not get instantiated at all.
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
`@Bean` is a method-level annotation and a direct analog of the XML `<bean/>` element.
|
||||
The annotation supports some of the attributes offered by `<bean/>`, such as:
|
||||
|
||||
* xref:core/beans/definition.adoc#beans-beanname[name]
|
||||
* xref:core/beans/factory-nature.adoc#beans-factory-lifecycle-initializingbean[init-method]
|
||||
* xref:core/beans/factory-nature.adoc#beans-factory-lifecycle-disposablebean[destroy-method]
|
||||
* xref:core/beans/dependencies/factory-autowire.adoc[autowiring]
|
||||
* `name`.
|
||||
|
||||
You can use the `@Bean` annotation in a `@Configuration`-annotated or in a
|
||||
`@Component`-annotated class.
|
||||
@@ -17,9 +17,10 @@ You can use the `@Bean` annotation in a `@Configuration`-annotated or in a
|
||||
== Declaring a Bean
|
||||
|
||||
To declare a bean, you can annotate a method with the `@Bean` annotation. You use this
|
||||
method to register a bean definition within an `ApplicationContext` of the type
|
||||
specified as the method's return value. By default, the bean name is the same as
|
||||
the method name. The following example shows a `@Bean` method declaration:
|
||||
method to register a bean definition within an `ApplicationContext` of the type specified
|
||||
by the method's return type. By default, the bean name is the same as the method name
|
||||
(unless a different xref:#beans-java-customizing-bean-naming[bean name generator] is
|
||||
configured). The following example shows a `@Bean` method declaration:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -126,7 +127,7 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
However, this limits the visibility for advance type prediction to the specified
|
||||
However, this limits the visibility for advanced type prediction to the specified
|
||||
interface type (`TransferService`). Then, with the full type (`TransferServiceImpl`)
|
||||
known to the container only once the affected singleton bean has been instantiated.
|
||||
Non-lazy singleton beans get instantiated according to their declaration order,
|
||||
@@ -473,8 +474,15 @@ Kotlin::
|
||||
== Customizing Bean Naming
|
||||
|
||||
By default, configuration classes use a `@Bean` method's name as the name of the
|
||||
resulting bean. This functionality can be overridden, however, with the `name` attribute,
|
||||
as the following example shows:
|
||||
resulting bean. However, as of Spring Framework 7.0, you can change this default strategy
|
||||
by configuring a custom
|
||||
{spring-framework-api}/context/annotation/ConfigurationBeanNameGenerator.html[`ConfigurationBeanNameGenerator`]
|
||||
when bootstrapping the context or configuring component scanning. For example,
|
||||
{spring-framework-api}/context/annotation/FullyQualifiedConfigurationBeanNameGenerator.html[`FullyQualifiedConfigurationBeanNameGenerator`]
|
||||
can be used to generate fully-qualified default bean names for `@Bean` methods without an
|
||||
explicit `name` attribute. For individual `@Bean` methods, the default or
|
||||
generator-derived name can be overridden with the `name` attribute, as the following
|
||||
example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -505,6 +513,7 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: `@Bean("myThing")` is equivalent to `@Bean(name = "myThing")`.
|
||||
|
||||
[[beans-java-bean-aliasing]]
|
||||
== Bean Aliasing
|
||||
|
||||
+5
-5
@@ -338,11 +338,11 @@ In the preceding scenario, using `@Autowired` works well and provides the desire
|
||||
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
|
||||
you know exactly where the `@Autowired AccountRepository` bean is declared? It is not
|
||||
explicit in the code, and this may be just fine. Remember that the
|
||||
{spring-site-tools}[Spring Tools for Eclipse] provides tooling that
|
||||
can render graphs showing how everything is wired, which may be all you need. Also,
|
||||
your Java IDE can easily find all declarations and uses of the `AccountRepository` type
|
||||
and quickly show you the location of `@Bean` methods that return that type.
|
||||
explicit in the code, and this may be just fine. Note that the
|
||||
{spring-site-tools}[Spring Tools] IDE support provides tooling that can render graphs
|
||||
showing how everything is wired, which may be all you need. Also, your Java IDE can
|
||||
easily find all declarations and uses of the `AccountRepository` type and quickly show
|
||||
you the location of `@Bean` methods that return that type.
|
||||
|
||||
In cases where this ambiguity is not acceptable and you wish to have direct navigation
|
||||
from within your IDE from one `@Configuration` class to another, consider autowiring the
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
[[beans-standard-annotations]]
|
||||
= Using JSR 330 Standard Annotations
|
||||
= Using JSR-330 Standard Annotations
|
||||
|
||||
Spring offers support for JSR-330 standard annotations (Dependency Injection). Those
|
||||
annotations are scanned in the same way as the Spring annotations. To use them, you need
|
||||
to have the relevant jars in your classpath.
|
||||
Spring offers support for JSR-330 standard _Dependency Injection_ annotations which are
|
||||
available in the `jakarta.inject` package. These annotations may optionally be used as
|
||||
alternatives to Spring annotations.
|
||||
|
||||
To use them, you need to have the relevant jar in your classpath. For example, the
|
||||
`jakarta.inject` artifact is available in the standard Maven repository
|
||||
(`https://repo.maven.apache.org/maven2/jakarta/inject/jakarta.inject-api/2.0.0/`),
|
||||
|
||||
[NOTE]
|
||||
=====
|
||||
If you use Maven, the `jakarta.inject` artifact is available in the standard Maven
|
||||
repository (
|
||||
https://repo.maven.apache.org/maven2/jakarta/inject/jakarta.inject-api/2.0.0/[https://repo.maven.apache.org/maven2/jakarta/inject/jakarta.inject-api/2.0.0/]).
|
||||
You can add the following dependency to your file pom.xml:
|
||||
If you use Maven, you can add the following dependency to your `pom.xml` file.
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -26,13 +27,14 @@ You can add the following dependency to your file pom.xml:
|
||||
[[beans-inject-named]]
|
||||
== Dependency Injection with `@Inject` and `@Named`
|
||||
|
||||
Instead of `@Autowired`, you can use `@jakarta.inject.Inject` as follows:
|
||||
Instead of using `@Autowired` for dependency injection, you may optionally choose to use
|
||||
`@jakarta.inject.Inject` as follows.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
----
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
@@ -54,7 +56,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
----
|
||||
import jakarta.inject.Inject
|
||||
|
||||
@@ -72,17 +74,19 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
As with `@Autowired`, you can use `@Inject` at the field level, method level
|
||||
and constructor-argument level. Furthermore, you may declare your injection point as a
|
||||
`Provider`, allowing for on-demand access to beans of shorter scopes or lazy access to
|
||||
other beans through a `Provider.get()` call. The following example offers a variant of the
|
||||
preceding example:
|
||||
As with `@Autowired`, you can use `@Inject` at the field level, method level, and
|
||||
constructor-argument level.
|
||||
|
||||
Furthermore, as an alternative to Spring's `ObjectProvider` mechanism, you may choose to
|
||||
declare your injection point as a `jakarta.inject.Provider`, allowing for on-demand
|
||||
access to beans of shorter scopes or lazy access to other beans through a
|
||||
`Provider.get()` call. The following example offers a variant of the preceding example.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
----
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Provider;
|
||||
@@ -105,9 +109,10 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
----
|
||||
import jakarta.inject.Inject
|
||||
import jakarta.inject.Provider
|
||||
|
||||
class SimpleMovieLister {
|
||||
|
||||
@@ -123,14 +128,15 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
If you would like to use a qualified name for the dependency that should be injected,
|
||||
you should use the `@Named` annotation, as the following example shows:
|
||||
If you would like to use a qualified name for the dependency that should be injected, you
|
||||
may choose to use the `@Named` annotation as an alternative to Spring's `@Qualifier`
|
||||
support, as the following example shows.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
----
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
@@ -150,7 +156,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
----
|
||||
import jakarta.inject.Inject
|
||||
import jakarta.inject.Named
|
||||
@@ -170,12 +176,15 @@ Kotlin::
|
||||
======
|
||||
|
||||
As with `@Autowired`, `@Inject` can also be used with `java.util.Optional` or
|
||||
`@Nullable`. This is even more applicable here, since `@Inject` does not have
|
||||
a `required` attribute. The following pair of examples show how to use `@Inject` and
|
||||
`@Nullable`:
|
||||
`@Nullable`. This is even more applicable here, since `@Inject` does not have a
|
||||
`required` attribute. The following examples show how to use `@Inject` with `Optional`,
|
||||
`@Nullable`, and Kotlin's built-in support for nullable types.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
----
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Optional;
|
||||
|
||||
public class SimpleMovieLister {
|
||||
|
||||
@Inject
|
||||
@@ -189,8 +198,11 @@ a `required` attribute. The following pair of examples show how to use `@Inject`
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
----
|
||||
import jakarta.inject.Inject;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
public class SimpleMovieLister {
|
||||
|
||||
@Inject
|
||||
@@ -202,8 +214,10 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
----
|
||||
import jakarta.inject.Inject
|
||||
|
||||
class SimpleMovieLister {
|
||||
|
||||
@Inject
|
||||
@@ -214,21 +228,21 @@ Kotlin::
|
||||
|
||||
|
||||
[[beans-named]]
|
||||
== `@Named` and `@ManagedBean`: Standard Equivalents to the `@Component` Annotation
|
||||
== `@Named`: Standard Equivalent to the `@Component` Annotation
|
||||
|
||||
Instead of `@Component`, you can use `@jakarta.inject.Named` or `jakarta.annotation.ManagedBean`,
|
||||
as the following example shows:
|
||||
Instead of `@Component` or other Spring stereotype annotations, you may optionally choose
|
||||
to use `@jakarta.inject.Named`, as the following example shows.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
----
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
|
||||
@Named("movieListener") // @ManagedBean("movieListener") could be used as well
|
||||
@Named("movieListener")
|
||||
public class SimpleMovieLister {
|
||||
|
||||
private MovieFinder movieFinder;
|
||||
@@ -244,12 +258,12 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
----
|
||||
import jakarta.inject.Inject
|
||||
import jakarta.inject.Named
|
||||
|
||||
@Named("movieListener") // @ManagedBean("movieListener") could be used as well
|
||||
@Named("movieListener")
|
||||
class SimpleMovieLister {
|
||||
|
||||
@Inject
|
||||
@@ -260,14 +274,15 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
It is very common to use `@Component` without specifying a name for the component.
|
||||
`@Named` can be used in a similar fashion, as the following example shows:
|
||||
It is very common to use `@Component` or other Spring stereotype annotations without
|
||||
specifying an explicit name for the component, and `@Named` can be used in a similar
|
||||
fashion, as the following example shows.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
----
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
@@ -288,7 +303,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
----
|
||||
import jakarta.inject.Inject
|
||||
import jakarta.inject.Named
|
||||
@@ -304,14 +319,14 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
When you use `@Named` or `@ManagedBean`, you can use component scanning in the
|
||||
exact same way as when you use Spring annotations, as the following example shows:
|
||||
When you use `@Named`, you can use component scanning in the exact same way as when you
|
||||
use Spring annotations, as the following example shows.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = "org.example")
|
||||
@@ -322,7 +337,7 @@ Java::
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = ["org.example"])
|
||||
@@ -332,55 +347,106 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: In contrast to `@Component`, the JSR-330 `@Named` and the JSR-250 `@ManagedBean`
|
||||
annotations are not composable. You should use Spring's stereotype model for building
|
||||
custom component annotations.
|
||||
NOTE: In contrast to `@Component`, the JSR-330 `@Named` annotation is not composable. You
|
||||
should use Spring's stereotype model for building custom component annotations.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
If you work with legacy systems that still use `@javax.inject.Named` or
|
||||
`@javax.annotation.ManagedBean` for components (note the `javax` package namespace), you
|
||||
can explicitly configure component scanning to include those annotation types, as shown
|
||||
in the following example.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(
|
||||
basePackages = "org.example",
|
||||
includeFilters = @Filter({
|
||||
javax.inject.Named.class,
|
||||
javax.annotation.ManagedBean.class
|
||||
})
|
||||
)
|
||||
public class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan(
|
||||
basePackages = ["org.example"],
|
||||
includeFilters = [Filter([
|
||||
javax.inject.Named::class,
|
||||
javax.annotation.ManagedBean::class
|
||||
])]
|
||||
)
|
||||
class AppConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
In addition, if you would like for the `value` attributes in `@javax.inject.Named` and
|
||||
`@javax.annotation.ManagedBean` to be used as component names, you need to override the
|
||||
`isStereotypeWithNameValue(...)` method in `AnnotationBeanNameGenerator` to add explicit
|
||||
support for `javax.annotation.ManagedBean` and `javax.inject.Named` and register your
|
||||
custom `AnnotationBeanNameGenerator` via the `nameGenerator` attribute in
|
||||
`@ComponentScan`.
|
||||
====
|
||||
|
||||
|
||||
[[beans-standard-annotations-limitations]]
|
||||
== Limitations of JSR-330 Standard Annotations
|
||||
|
||||
When you work with standard annotations, you should know that some significant
|
||||
features are not available, as the following table shows:
|
||||
When you work with JSR-330 standard annotations, you should know that some significant
|
||||
features are not available, as the following table shows.
|
||||
|
||||
[[annotations-comparison]]
|
||||
.Spring component model elements versus JSR-330 variants
|
||||
.Spring component model versus JSR-330 variants
|
||||
|===
|
||||
| Spring| jakarta.inject.*| jakarta.inject restrictions / comments
|
||||
| Spring | JSR-330 | JSR-330 restrictions / comments
|
||||
|
||||
| @Autowired
|
||||
| @Inject
|
||||
| `@Inject` has no 'required' attribute. Can be used with Java 8's `Optional` instead.
|
||||
| `@Autowired`
|
||||
| `@Inject`
|
||||
| `@Inject` has no `required` attribute. Can be used with Java's `Optional` instead.
|
||||
|
||||
| @Component
|
||||
| @Named / @ManagedBean
|
||||
| `@Component`
|
||||
| `@Named`
|
||||
| JSR-330 does not provide a composable model, only a way to identify named components.
|
||||
|
||||
| @Scope("singleton")
|
||||
| @Singleton
|
||||
| `@Scope("singleton")`
|
||||
| `@Singleton`
|
||||
| The JSR-330 default scope is like Spring's `prototype`. However, in order to keep it
|
||||
consistent with Spring's general defaults, a JSR-330 bean declared in the Spring
|
||||
container is a `singleton` by default. In order to use a scope other than `singleton`,
|
||||
you should use Spring's `@Scope` annotation. `jakarta.inject` also provides a
|
||||
`jakarta.inject.Scope` annotation: however, this one is only intended to be used
|
||||
`jakarta.inject.Scope` annotation; however, this one is only intended to be used
|
||||
for creating custom annotations.
|
||||
|
||||
| @Qualifier
|
||||
| @Qualifier / @Named
|
||||
| `@Qualifier`
|
||||
| `@Qualifier` / `@Named`
|
||||
| `jakarta.inject.Qualifier` is just a meta-annotation for building custom qualifiers.
|
||||
Concrete `String` qualifiers (like Spring's `@Qualifier` with a value) can be associated
|
||||
through `jakarta.inject.Named`.
|
||||
|
||||
| @Value
|
||||
| `@Value`
|
||||
| -
|
||||
| no equivalent
|
||||
|
||||
| @Lazy
|
||||
| `@Lazy`
|
||||
| -
|
||||
| no equivalent
|
||||
|
||||
| ObjectFactory
|
||||
| Provider
|
||||
| `ObjectFactory`
|
||||
| `Provider`
|
||||
| `jakarta.inject.Provider` is a direct alternative to Spring's `ObjectFactory`,
|
||||
only with a shorter `get()` method name. It can also be used in combination with
|
||||
Spring's `@Autowired` or with non-annotated constructors and setter methods.
|
||||
|
||||
@@ -7,14 +7,14 @@ similar to the https://jakarta.ee/specifications/expression-language/[Jakarta Ex
|
||||
Language] but offers additional features, most notably method invocation and basic string
|
||||
templating functionality.
|
||||
|
||||
While there are several other Java expression languages available -- OGNL, MVEL, and JBoss
|
||||
EL, to name a few -- the Spring Expression Language was created to provide the Spring
|
||||
community with a single well supported expression language that can be used across all
|
||||
the products in the Spring portfolio. Its language features are driven by the
|
||||
requirements of the projects in the Spring portfolio, including tooling requirements
|
||||
for code completion support within the {spring-site-tools}[Spring Tools for Eclipse].
|
||||
That said, SpEL is based on a technology-agnostic API that lets other expression language
|
||||
implementations be integrated, should the need arise.
|
||||
While there are several other Java expression languages available -- OGNL, MVEL, and
|
||||
JBoss EL, to name a few -- the Spring Expression Language was created to provide the
|
||||
Spring community with a single well supported expression language that can be used across
|
||||
all the products in the Spring portfolio. Its language features are driven by the
|
||||
requirements of the projects in the Spring portfolio, including tooling requirements for
|
||||
code completion within the {spring-site-tools}[Spring Tools] IDE support. That said, SpEL
|
||||
is based on a technology-agnostic API that lets other expression language implementations
|
||||
be integrated, should the need arise.
|
||||
|
||||
While SpEL serves as the foundation for expression evaluation within the Spring
|
||||
portfolio, it is not directly tied to Spring and can be used independently. To
|
||||
|
||||
@@ -531,6 +531,7 @@ following kinds of expressions cannot be compiled.
|
||||
* Expressions relying on the conversion service
|
||||
* Expressions using custom resolvers
|
||||
* Expressions using overloaded operators
|
||||
* Expressions using `Optional` with the null-safe or Elvis operator
|
||||
* Expressions using array construction syntax
|
||||
* Expressions using selection or projection
|
||||
* Expressions using bean references
|
||||
|
||||
@@ -53,7 +53,7 @@ Kotlin::
|
||||
val trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean::class.java)
|
||||
|
||||
// uses CustomValue:::compareTo
|
||||
val trueValue = parser.parseExpression("new CustomValue(1) < new CustomValue(2)").getValue(Boolean::class.java);
|
||||
val trueValue = parser.parseExpression("new CustomValue(1) < new CustomValue(2)").getValue(Boolean::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
@@ -167,7 +167,7 @@ Kotlin::
|
||||
[CAUTION]
|
||||
====
|
||||
The syntax for the `between` operator is `<input> between {<range_begin>, <range_end>}`,
|
||||
which is effectively a shortcut for `<input> >= <range_begin> && <input> \<= <range_end>}`.
|
||||
which is effectively a shortcut for `<input> >= <range_begin> && <input> \<= <range_end>`.
|
||||
|
||||
Consequently, `1 between {1, 5}` evaluates to `true`, while `1 between {5, 1}` evaluates
|
||||
to `false`.
|
||||
@@ -312,13 +312,13 @@ Kotlin::
|
||||
|
||||
// evaluates to 'a'
|
||||
val ch = parser.parseExpression("'d' - 3")
|
||||
.getValue(Character::class.java);
|
||||
.getValue(Char::class.java)
|
||||
|
||||
// -- Repeat --
|
||||
|
||||
// evaluates to "abcabc"
|
||||
val repeated = parser.parseExpression("'abc' * 2")
|
||||
.getValue(String::class.java);
|
||||
.getValue(String::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
@@ -485,7 +485,7 @@ Kotlin::
|
||||
|
||||
// -- Operator precedence --
|
||||
|
||||
val minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Int::class.java) // -21
|
||||
val minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Int::class.java) // -21
|
||||
----
|
||||
======
|
||||
|
||||
@@ -541,32 +541,7 @@ For example, if we want to overload the `ADD` operator to allow two lists to be
|
||||
concatenated using the `+` sign, we can implement a custom `OperatorOverloader` as
|
||||
follows.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
pubic class ListConcatenation implements OperatorOverloader {
|
||||
|
||||
@Override
|
||||
public boolean overridesOperation(Operation operation, Object left, Object right) {
|
||||
return (operation == Operation.ADD &&
|
||||
left instanceof List && right instanceof List);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object operate(Operation operation, Object left, Object right) {
|
||||
if (operation == Operation.ADD &&
|
||||
left instanceof List list1 && right instanceof List list2) {
|
||||
|
||||
List result = new ArrayList(list1);
|
||||
result.addAll(list2);
|
||||
return result;
|
||||
}
|
||||
throw new UnsupportedOperationException(
|
||||
"No overload for operation %s and operands [%s] and [%s]"
|
||||
.formatted(operation, left, right));
|
||||
}
|
||||
}
|
||||
----
|
||||
include-code::./ListConcatenation[]
|
||||
|
||||
If we register `ListConcatenation` as the `OperatorOverloader` in a
|
||||
`StandardEvaluationContext`, we can then evaluate expressions like `{1, 2, 3} + {4, 5}`
|
||||
@@ -589,8 +564,8 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
StandardEvaluationContext context = StandardEvaluationContext()
|
||||
context.setOperatorOverloader(ListConcatenation())
|
||||
val context = StandardEvaluationContext()
|
||||
context.operatorOverloader = ListConcatenation()
|
||||
|
||||
// evaluates to a new list: [1, 2, 3, 4, 5]
|
||||
parser.parseExpression("{1, 2, 3} + {2 + 2, 5}").getValue(context, List::class.java)
|
||||
|
||||
@@ -6,7 +6,7 @@ are set by using the `setVariable()` method in `EvaluationContext` implementatio
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Variable names must be begin with a letter (as defined below), an underscore, or a dollar
|
||||
Variable names must begin with a letter (as defined below), an underscore, or a dollar
|
||||
sign.
|
||||
|
||||
Variable names must be composed of one or more of the following supported types of
|
||||
|
||||
@@ -183,9 +183,6 @@ By default, a retryable operation will be retried for any exception thrown: with
|
||||
3 retry attempts (`maxRetries = 3`) after an initial failure, and a delay of 1 second
|
||||
between attempts.
|
||||
|
||||
If you only need to customize the number of retry attempts, you can use the
|
||||
`RetryPolicy.withMaxRetries()` factory method as demonstrated below.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
A retryable operation will be executed at least once and retried at most `maxRetries`
|
||||
@@ -196,6 +193,9 @@ For example, if `maxRetries` is set to `4`, the retryable operation will be invo
|
||||
least once and at most 5 times.
|
||||
====
|
||||
|
||||
If you only need to customize the number of retry attempts, you can use the
|
||||
`RetryPolicy.withMaxRetries()` factory method as demonstrated below.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
var retryTemplate = new RetryTemplate(RetryPolicy.withMaxRetries(4)); // <1>
|
||||
|
||||
@@ -15,7 +15,7 @@ configuration options. You should instantiate the `SimpleJdbcInsert` in the data
|
||||
layer's initialization method. For this example, the initializing method is the
|
||||
`setDataSource` method. You do not need to subclass the `SimpleJdbcInsert` class. Instead,
|
||||
you can create a new instance and set the table name by using the `withTableName` method.
|
||||
Configuration methods for this class follow the `fluid` style that returns the instance
|
||||
Configuration methods for this class follow the `fluent` style that returns the instance
|
||||
of the `SimpleJdbcInsert`, which lets you chain all configuration methods. The following
|
||||
example uses only one configuration method (we show examples of multiple methods later):
|
||||
|
||||
@@ -349,11 +349,11 @@ parameters return the data read from the table.
|
||||
|
||||
You can declare `SimpleJdbcCall` in a manner similar to declaring `SimpleJdbcInsert`. You
|
||||
should instantiate and configure the class in the initialization method of your data-access
|
||||
layer. Compared to the `StoredProcedure` class, you need not create a subclass
|
||||
and you need not to declare parameters that can be looked up in the database metadata.
|
||||
The following example of a `SimpleJdbcCall` configuration uses the preceding stored
|
||||
procedure (the only configuration option, in addition to the `DataSource`, is the name
|
||||
of the stored procedure):
|
||||
layer. In contrast to the `StoredProcedure` class, you do not need to create a subclass,
|
||||
and you do not need to declare parameters that can be looked up in the database metadata.
|
||||
The following `SimpleJdbcCall` configuration example uses the preceding stored procedure.
|
||||
The only configuration option (other than the `DataSource`) is the name of the stored
|
||||
procedure.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
||||
@@ -14,7 +14,7 @@ As of Spring Framework 7.0, Spring requires Hibernate ORM 7.x for Spring's
|
||||
`HibernateJpaVendorAdapter`.
|
||||
|
||||
The `org.springframework.orm.jpa.hibernate` package supersedes the former `orm.hibernate5`:
|
||||
now for use with Hibernate ORM 7.1+, tightly integrated with `HibernateJpaVendorAdapter`
|
||||
now for use with Hibernate ORM 7.x, tightly integrated with `HibernateJpaVendorAdapter`
|
||||
as well as supporting Hibernate's native `SessionFactory.getCurrentSession()` style.
|
||||
====
|
||||
|
||||
|
||||
+13
-42
@@ -8,7 +8,7 @@ danger of undue coupling, because code that is meant to be used transactionally
|
||||
almost always deployed that way anyway.
|
||||
|
||||
NOTE: The standard `jakarta.transaction.Transactional` annotation is also supported as
|
||||
a drop-in replacement to Spring's own annotation. Please refer to the JTA documentation
|
||||
a drop-in replacement for Spring's own annotation. Please refer to the JTA documentation
|
||||
for more details.
|
||||
|
||||
The ease-of-use afforded by the use of the `@Transactional` annotation is best
|
||||
@@ -89,47 +89,16 @@ annotation in a `@Configuration` class. See the
|
||||
{spring-framework-api}/transaction/annotation/EnableTransactionManagement.html[javadoc]
|
||||
for full details.
|
||||
|
||||
In XML configuration, the `<tx:annotation-driven/>` tag provides similar convenience:
|
||||
The following example shows the configuration needed to enable annotation-driven transaction management:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<!-- from the file 'context.xml' -->
|
||||
<?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:aop="http://www.springframework.org/schema/aop"
|
||||
xmlns:tx="http://www.springframework.org/schema/tx"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/tx
|
||||
https://www.springframework.org/schema/tx/spring-tx.xsd
|
||||
http://www.springframework.org/schema/aop
|
||||
https://www.springframework.org/schema/aop/spring-aop.xsd">
|
||||
include-code::./AppConfig[tag=snippet,indent=0]
|
||||
|
||||
<!-- this is the service object that we want to make transactional -->
|
||||
<bean id="fooService" class="x.y.service.DefaultFooService"/>
|
||||
|
||||
<!-- enable the configuration of transactional behavior based on annotations -->
|
||||
<!-- a TransactionManager is still required -->
|
||||
<tx:annotation-driven transaction-manager="txManager"/> <1>
|
||||
|
||||
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
|
||||
<!-- (this dependency is defined somewhere else) -->
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
</bean>
|
||||
|
||||
<!-- other <bean/> definitions here -->
|
||||
|
||||
</beans>
|
||||
----
|
||||
<1> The line that makes the bean instance transactional.
|
||||
|
||||
TIP: You can omit the `transaction-manager` attribute in the `<tx:annotation-driven/>`
|
||||
tag if the bean name of the `TransactionManager` that you want to wire in has the name
|
||||
`transactionManager`. If the `TransactionManager` bean that you want to dependency-inject
|
||||
has any other name, you have to use the `transaction-manager` attribute, as in the
|
||||
preceding example.
|
||||
TIP: In programmatic configuration, the `@EnableTransactionManagement` annotation uses any
|
||||
`TransactionManager` bean in the context. In XML configuration, you can omit
|
||||
the `transaction-manager` attribute in the `<tx:annotation-driven/>` tag if the bean
|
||||
name of the `TransactionManager` that you want to wire in has the name `transactionManager`.
|
||||
If the `TransactionManager` bean has any other name, you have to use the
|
||||
`transaction-manager` attribute explicitly, as in the preceding example.
|
||||
|
||||
Reactive transactional methods use reactive return types in contrast to imperative
|
||||
programming arrangements as the following listing shows:
|
||||
@@ -234,8 +203,10 @@ on an interface, a class definition, or a method on a class. However, the mere p
|
||||
of the `@Transactional` annotation is not enough to activate the transactional behavior.
|
||||
The `@Transactional` annotation is merely metadata that can be consumed by corresponding
|
||||
runtime infrastructure which uses that metadata to configure the appropriate beans with
|
||||
transactional behavior. In the preceding example, the `<tx:annotation-driven/>` element
|
||||
switches on actual transaction management at runtime.
|
||||
transactional behavior. In the preceding examples that use programmatic configuration,
|
||||
the `@EnableTransactionManagement` annotation switches on actual transaction management
|
||||
at runtime. Whereas, in the preceding example that uses XML configuration, the
|
||||
`<tx:annotation-driven/>` element switches on actual transaction management at runtime.
|
||||
|
||||
TIP: The Spring team recommends that you annotate methods of concrete classes with the
|
||||
`@Transactional` annotation, rather than relying on annotated methods in interfaces,
|
||||
|
||||
@@ -3,21 +3,22 @@
|
||||
:page-aliases: integration/class-data-sharing.adoc
|
||||
:page-aliases: integration/cds.adoc
|
||||
|
||||
The ahead-of-time cache is a JVM feature introduced in Java 24 via the
|
||||
The ahead-of-time cache is a JVM feature introduced in Java 24 via
|
||||
https://openjdk.org/jeps/483[JEP 483] that can help reduce the startup time and memory
|
||||
footprint of Java applications. AOT cache is a natural evolution of https://docs.oracle.com/en/java/javase/17/vm/class-data-sharing.html[Class Data Sharing (CDS)].
|
||||
footprint of Java applications. AOT cache is a natural evolution of
|
||||
https://docs.oracle.com/en/java/javase/17/vm/class-data-sharing.html[Class Data Sharing (CDS)].
|
||||
Spring Framework supports both CDS and AOT cache, and it is recommended that you use the
|
||||
later if available in the JVM version your are using (Java 24+).
|
||||
latter if available in the JVM version you are using (Java 24+).
|
||||
|
||||
To use this feature, an AOT cache should be created for the particular classpath of the
|
||||
application. It is possible to create this cache on the deployed instance, or during a
|
||||
training run performed for example when packaging the application thanks to an hook-point
|
||||
training run performed for example when packaging the application thanks to a hook-point
|
||||
provided by the Spring Framework to ease such use case. Once the cache is available, users
|
||||
should opt in to use it via a JVM flag.
|
||||
|
||||
NOTE: If you are using Spring Boot, it is highly recommended to leverage its
|
||||
{spring-boot-docs-ref}/packaging/efficient.html#packaging.efficient.unpacking[executable JAR unpacking support]
|
||||
which is designed to fulfill the class loading requirements of both AOT cache and CDS.
|
||||
which is designed to fulfill the class loading requirements of both the AOT cache and CDS.
|
||||
|
||||
== Creating the cache
|
||||
|
||||
@@ -29,14 +30,22 @@ invoked; but the lifecycle has not started, and the `ContextRefreshedEvent` has
|
||||
been published.
|
||||
|
||||
To create the cache during the training run, it is possible to specify the `-Dspring.context.exit=onRefresh`
|
||||
JVM flag to start then exit your Spring application once the
|
||||
JVM flag to start and then exit your Spring application once the
|
||||
`ApplicationContext` has refreshed:
|
||||
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
AOT cache::
|
||||
|
||||
AOT cache (Java 25+)::
|
||||
+
|
||||
[source,bash,subs="verbatim,quotes"]
|
||||
----
|
||||
java -XX:AOTCacheOutput=app.aot -Dspring.context.exit=onRefresh -jar application.jar ...
|
||||
----
|
||||
|
||||
AOT cache (Java 24)::
|
||||
+
|
||||
[source,bash,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -55,6 +64,12 @@ java -XX:ArchiveClassesAtExit=app.jsa -Dspring.context.exit=onRefresh ...
|
||||
======
|
||||
--
|
||||
|
||||
NOTE: With Java 25+, AOT cache stores, among other things, the
|
||||
https://openjdk.org/jeps/515[method profiling information]. Therefore, to benefit of this capability,
|
||||
it is recommended to create an AOT cache for an application that experienced a portion of a
|
||||
production-like workflow instead of using the `-Dspring.context.exit=onRefresh` flag which designed to
|
||||
optimize only the startup of your application.
|
||||
|
||||
== Using the cache
|
||||
|
||||
Once the cache file has been created, you can use it to start your application faster:
|
||||
@@ -82,7 +97,7 @@ java -XX:SharedArchiveFile=app.jsa ...
|
||||
|
||||
Pay attention to the logs and the startup time to check if the AOT cache is used successfully.
|
||||
To figure out how effective the cache is, you can enable class loading logs by adding
|
||||
an extra attribute: `-Xlog:class+load:file=aot-cache.log`. This creates a `aot-cache.log` with
|
||||
an extra attribute: `-Xlog:class+load:file=aot-cache.log`. This creates an `aot-cache.log` with
|
||||
every attempt to load a class and its source. Classes that are loaded from the cache should have
|
||||
a "shared objects file" source, as shown in the following example:
|
||||
|
||||
@@ -95,11 +110,11 @@ a "shared objects file" source, as shown in the following example:
|
||||
[0.151s][info][class,load] org.springframework.context.MessageSource source: shared objects file
|
||||
----
|
||||
|
||||
If the AOT cache can't be enabled or if you have a large number of classes that are not loaded from
|
||||
If the AOT cache cannot 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 cache:
|
||||
|
||||
- The very same JVM must be used.
|
||||
- The classpath must be specified as a JAR or a list of JARs, and avoid the usage of directories and `*` wildcard characters.
|
||||
- The timestamps of the JARs must be preserved.
|
||||
- When using the cache, the classpath must be the same than the one used to create it, in the same order.
|
||||
Additional JARs or directories can be specified *at the end* (but won't be cached).
|
||||
- When using the cache, the classpath must be the same as the one used to create it, in the same order.
|
||||
Additional JARs or directories can be specified *at the end* (but will not be cached).
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
[[appendix]]
|
||||
= Appendix
|
||||
|
||||
|
||||
|
||||
|
||||
[[appendix.xsd-schemas]]
|
||||
== XML Schemas
|
||||
|
||||
This part of the appendix lists XML schemas related to integration technologies.
|
||||
|
||||
|
||||
|
||||
[[appendix.xsd-schemas-jee]]
|
||||
=== The `jee` Schema
|
||||
|
||||
@@ -172,7 +167,7 @@ different properties with `jee`:
|
||||
|
||||
The `<jee:local-slsb/>` element configures a reference to a local EJB Stateless Session Bean.
|
||||
|
||||
The following example shows how to configures a reference to a local EJB Stateless Session Bean
|
||||
The following example shows how to configure a reference to a local EJB Stateless Session Bean
|
||||
without `jee`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
@@ -184,7 +179,7 @@ without `jee`:
|
||||
</bean>
|
||||
----
|
||||
|
||||
The following example shows how to configures a reference to a local EJB Stateless Session Bean
|
||||
The following example shows how to configure a reference to a local EJB Stateless Session Bean
|
||||
with `jee`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
@@ -200,7 +195,7 @@ with `jee`:
|
||||
|
||||
The `<jee:local-slsb/>` element configures a reference to a local EJB Stateless Session Bean.
|
||||
|
||||
The following example shows how to configures a reference to a local EJB Stateless Session Bean
|
||||
The following example shows how to configure a reference to a local EJB Stateless Session Bean
|
||||
and a number of properties without `jee`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
@@ -215,7 +210,7 @@ and a number of properties without `jee`:
|
||||
</bean>
|
||||
----
|
||||
|
||||
The following example shows how to configures a reference to a local EJB Stateless Session Bean
|
||||
The following example shows how to configure a reference to a local EJB Stateless Session Bean
|
||||
and a number of properties with `jee`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
@@ -234,7 +229,7 @@ and a number of properties with `jee`:
|
||||
|
||||
The `<jee:remote-slsb/>` element configures a reference to a `remote` EJB Stateless Session Bean.
|
||||
|
||||
The following example shows how to configures a reference to a remote EJB Stateless Session Bean
|
||||
The following example shows how to configure a reference to a remote EJB Stateless Session Bean
|
||||
without `jee`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
@@ -251,7 +246,7 @@ without `jee`:
|
||||
</bean>
|
||||
----
|
||||
|
||||
The following example shows how to configures a reference to a remote EJB Stateless Session Bean
|
||||
The following example shows how to configure a reference to a remote EJB Stateless Session Bean
|
||||
with `jee`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
@@ -313,7 +308,7 @@ xref:integration/jmx/naming.adoc#jmx-context-mbeanexport[Configuring Annotation-
|
||||
=== The `cache` Schema
|
||||
|
||||
You can use the `cache` elements to enable support for Spring's `@CacheEvict`, `@CachePut`,
|
||||
and `@Caching` annotations. It it also supports declarative XML-based caching. See
|
||||
and `@Caching` annotations. The `cache` schema also supports declarative XML-based caching. See
|
||||
xref:integration/cache/annotations.adoc#cache-annotation-enable[Enabling Caching Annotations] and
|
||||
xref:integration/cache/declarative-xml.adoc[Declarative XML-based Caching] for details.
|
||||
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ xref:integration/cache/store-configuration.adoc#cache-store-configuration-jsr107
|
||||
[[cache-store-configuration-caffeine]]
|
||||
== Caffeine Cache
|
||||
|
||||
Caffeine is a Java 8 rewrite of Guava's cache, and its implementation is located in the
|
||||
Caffeine is a rewrite of Guava's cache, and its implementation is located in the
|
||||
`org.springframework.cache.caffeine` package and provides access to several features
|
||||
of Caffeine.
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ behind the scenes for each annotated method, by using a `JmsListenerContainerFac
|
||||
Such a container is not registered against the application context but can be easily
|
||||
located for management purposes by using the `JmsListenerEndpointRegistry` bean.
|
||||
|
||||
TIP: `@JmsListener` is a repeatable annotation on Java 8, so you can associate
|
||||
TIP: `@JmsListener` is a repeatable annotation, so you can associate
|
||||
several JMS destinations with the same method by adding additional `@JmsListener`
|
||||
declarations to it.
|
||||
|
||||
|
||||
@@ -466,7 +466,7 @@ synchronous, asynchronous, and streaming scenarios.
|
||||
* Non-blocking I/O
|
||||
* Reactive Streams back pressure
|
||||
* High concurrency with fewer hardware resources
|
||||
* Functional-style, fluent API that takes advantage of Java 8 lambdas
|
||||
* Functional-style, fluent API that takes advantage of lambda expressions
|
||||
* Synchronous and asynchronous interactions
|
||||
* Streaming up to or streaming down from a server
|
||||
|
||||
|
||||
@@ -533,8 +533,7 @@ that returns a value:
|
||||
----
|
||||
|
||||
TIP: `@Async` methods may not only declare a regular `java.util.concurrent.Future` return
|
||||
type but also Spring's `org.springframework.util.concurrent.ListenableFuture` or, as of
|
||||
Spring 4.2, JDK 8's `java.util.concurrent.CompletableFuture`, for richer interaction with
|
||||
type but also `java.util.concurrent.CompletableFuture`, for richer interaction with
|
||||
the asynchronous task and for immediate composition with further processing steps.
|
||||
|
||||
You can not use `@Async` in conjunction with lifecycle callbacks such as `@PostConstruct`.
|
||||
|
||||
@@ -3,14 +3,16 @@
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
The Spring Framework supports various Kotlin constructs, such as instantiating Kotlin classes
|
||||
through primary constructors, immutable classes data binding, and function optional parameters
|
||||
with default values.
|
||||
through primary constructors, data binding for immutable classes, and optional parameters
|
||||
with default values for functions.
|
||||
|
||||
Kotlin parameter names are recognized through a dedicated `KotlinReflectionParameterNameDiscoverer`,
|
||||
which allows finding interface method parameter names without requiring the Java 8 `-parameters`
|
||||
compiler flag to be enabled during compilation. (For completeness, we nevertheless recommend
|
||||
running the Kotlin compiler with its `-java-parameters` flag for standard Java parameter exposure.)
|
||||
which allows finding interface method parameter names without requiring the Java `-parameters`
|
||||
compiler flag to be enabled during compilation.
|
||||
|
||||
TIP: For completeness, we nevertheless recommend running the Kotlin compiler with its
|
||||
`-java-parameters` flag for standard Java parameter exposure.
|
||||
|
||||
You can declare configuration classes as
|
||||
{kotlin-docs}/nested-classes.html[top level or nested but not inner],
|
||||
since the later requires a reference to the outer class.
|
||||
since the latter requires a reference to the outer class.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[[coroutines]]
|
||||
= Coroutines
|
||||
|
||||
Kotlin {kotlin-docs}/coroutines-overview.html[Coroutines] are Kotlin
|
||||
lightweight threads allowing to write non-blocking code in an imperative way. On language side,
|
||||
Kotlin {kotlin-docs}/coroutines-overview.html[Coroutines] are instances of
|
||||
suspendable computations allowing to write non-blocking code in an imperative way. On language side,
|
||||
suspending functions provides an abstraction for asynchronous operations while on library side
|
||||
{kotlin-github-org}/kotlinx.coroutines[kotlinx.coroutines] provides functions like
|
||||
{kotlin-coroutines-api}/kotlinx-coroutines-core/kotlinx.coroutines/async.html[`async { }`]
|
||||
|
||||
@@ -319,9 +319,17 @@ progresses.
|
||||
== Testing
|
||||
|
||||
This section addresses testing with the combination of Kotlin and Spring Framework.
|
||||
The recommended testing framework is https://junit.org/junit5/[JUnit] along with
|
||||
The recommended testing framework is https://junit.org/[JUnit] along with
|
||||
https://mockk.io/[Mockk] for mocking.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
Kotlin lets you specify meaningful test function names between backticks (```).
|
||||
|
||||
For a concrete example, see the `+++`Find all users on HTML page`()+++` test function later
|
||||
in this section.
|
||||
====
|
||||
|
||||
NOTE: If you are using Spring Boot, see
|
||||
{spring-boot-docs-ref}/features/kotlin.html#features.kotlin.testing[this related documentation].
|
||||
|
||||
@@ -352,7 +360,6 @@ file with a `spring.test.constructor.autowire.mode = all` property.
|
||||
[[per_class-lifecycle]]
|
||||
=== `PER_CLASS` Lifecycle
|
||||
|
||||
Kotlin lets you specify meaningful test function names between backticks (```).
|
||||
With JUnit Jupiter, Kotlin test classes can use the `@TestInstance(TestInstance.Lifecycle.PER_CLASS)`
|
||||
annotation to enable single instantiation of test classes, which allows the use of `@BeforeAll`
|
||||
and `@AfterAll` annotations on non-static methods, which is a good fit for Kotlin.
|
||||
|
||||
@@ -237,8 +237,8 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
RSocketStrategies strategies = RSocketStrategies.builder()
|
||||
.encoders(encoders -> encoders.add(new Jackson2CborEncoder()))
|
||||
.decoders(decoders -> decoders.add(new Jackson2CborDecoder()))
|
||||
.encoders(encoders -> encoders.add(new JacksonCborEncoder()))
|
||||
.decoders(decoders -> decoders.add(new JacksonCborDecoder()))
|
||||
.build();
|
||||
|
||||
RSocketRequester requester = RSocketRequester.builder()
|
||||
@@ -251,8 +251,8 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val strategies = RSocketStrategies.builder()
|
||||
.encoders { it.add(Jackson2CborEncoder()) }
|
||||
.decoders { it.add(Jackson2CborDecoder()) }
|
||||
.encoders { it.add(JacksonCborEncoder()) }
|
||||
.decoders { it.add(JacksonCborDecoder()) }
|
||||
.build()
|
||||
|
||||
val requester = RSocketRequester.builder()
|
||||
@@ -681,8 +681,8 @@ Java::
|
||||
@Bean
|
||||
public RSocketStrategies rsocketStrategies() {
|
||||
return RSocketStrategies.builder()
|
||||
.encoders(encoders -> encoders.add(new Jackson2CborEncoder()))
|
||||
.decoders(decoders -> decoders.add(new Jackson2CborDecoder()))
|
||||
.encoders(encoders -> encoders.add(new JacksonCborEncoder()))
|
||||
.decoders(decoders -> decoders.add(new JacksonCborDecoder()))
|
||||
.routeMatcher(new PathPatternRouteMatcher())
|
||||
.build();
|
||||
}
|
||||
@@ -703,8 +703,8 @@ Kotlin::
|
||||
|
||||
@Bean
|
||||
fun rsocketStrategies() = RSocketStrategies.builder()
|
||||
.encoders { it.add(Jackson2CborEncoder()) }
|
||||
.decoders { it.add(Jackson2CborDecoder()) }
|
||||
.encoders { it.add(JacksonCborEncoder()) }
|
||||
.decoders { it.add(JacksonCborDecoder()) }
|
||||
.routeMatcher(PathPatternRouteMatcher())
|
||||
.build()
|
||||
}
|
||||
|
||||
+43
-1
@@ -5,6 +5,7 @@ The following annotations are supported when used in conjunction with the
|
||||
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-extension[`SpringExtension`]
|
||||
and JUnit Jupiter (that is, the programming model in JUnit):
|
||||
|
||||
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-springextensionconfig[`@SpringExtensionConfig`]
|
||||
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-junit-jupiter-springjunitconfig[`@SpringJUnitConfig`]
|
||||
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-junit-jupiter-springjunitwebconfig[`@SpringJUnitWebConfig`]
|
||||
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-testconstructor[`@TestConstructor`]
|
||||
@@ -14,6 +15,47 @@ and JUnit Jupiter (that is, the programming model in JUnit):
|
||||
* xref:testing/annotations/integration-spring/annotation-disabledinaotmode.adoc[`@DisabledInAotMode`]
|
||||
|
||||
|
||||
[[integration-testing-annotations-springextensionconfig]]
|
||||
== `@SpringExtensionConfig`
|
||||
|
||||
`@SpringExtensionConfig` is a type-level annotation that can be used to configure the
|
||||
behavior of the `SpringExtension`.
|
||||
|
||||
As of Spring Framework 7.0, the `SpringExtension` is configured to use a test-method
|
||||
scoped `ExtensionContext`, which enables consistent dependency injection into fields and
|
||||
constructors from the `ApplicationContext` for the current test method in a `@Nested`
|
||||
test class hierarchy. However, if a third-party `TestExecutionListener` is not compatible
|
||||
with the semantics associated with a test-method scoped extension context — or if a
|
||||
developer wishes to switch to test-class scoped semantics — the `SpringExtension` can be
|
||||
configured to use a test-class scoped `ExtensionContext` by annotating a top-level test
|
||||
class with `@SpringExtensionConfig(useTestClassScopedExtensionContext = true)`.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
If your top-level test class is configured to use JUnit Jupiter's
|
||||
`@TestInstance(Lifecycle.PER_CLASS)` semantics, the `SpringExtension` will always use a
|
||||
test-class scoped `ExtensionContext`, and there is no need to declare
|
||||
`@SpringExtensionConfig(useTestClassScopedExtensionContext = true)`.
|
||||
====
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
This annotation is currently only applicable to `@Nested` test class hierarchies and
|
||||
should be applied to the top-level enclosing class of a `@Nested` test class hierarchy.
|
||||
|
||||
Consequently, there is no need to declare this annotation on a test class that does not
|
||||
contain `@Nested` test classes.
|
||||
====
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration[`@NestedTestConfiguration`]
|
||||
does not apply to this annotation.
|
||||
|
||||
`@SpringExtensionConfig` will always be detected within a `@Nested` test class hierarchy,
|
||||
effectively disregarding any `@NestedTestConfiguration(OVERRIDE)` declarations.
|
||||
====
|
||||
|
||||
[[integration-testing-annotations-junit-jupiter-springjunitconfig]]
|
||||
== `@SpringJUnitConfig`
|
||||
|
||||
@@ -187,7 +229,7 @@ default mode may be set via the
|
||||
xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism.
|
||||
|
||||
The default mode may also be configured as a
|
||||
https://junit.org/junit5/docs/current/user-guide/#running-tests-config-params[JUnit Platform configuration parameter].
|
||||
https://docs.junit.org/current/running-tests/configuration-parameters.html[JUnit Platform configuration parameter].
|
||||
|
||||
If the `spring.test.constructor.autowire.mode` property is not set, test class
|
||||
constructors will not be automatically autowired.
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
= Meta-Annotation Support for Testing
|
||||
|
||||
You can use most test-related annotations as
|
||||
xref:core/beans/classpath-scanning.adoc#beans-meta-annotations[meta-annotations] to create custom composed
|
||||
annotations and reduce configuration duplication across a test suite.
|
||||
xref:core/beans/classpath-scanning.adoc#beans-meta-annotations[meta-annotations] to
|
||||
create custom composed annotations and reduce configuration duplication across a test
|
||||
suite.
|
||||
|
||||
You can use each of the following as a meta-annotation in conjunction with the
|
||||
xref:testing/testcontext-framework.adoc[TestContext framework].
|
||||
For example, you can use each of the following as a meta-annotation in conjunction with
|
||||
the xref:testing/testcontext-framework.adoc[TestContext framework].
|
||||
|
||||
* `@BootstrapWith`
|
||||
* `@ContextConfiguration`
|
||||
@@ -37,111 +38,7 @@ xref:testing/testcontext-framework.adoc[TestContext framework].
|
||||
* `@EnabledIf` _(only supported on JUnit Jupiter)_
|
||||
* `@DisabledIf` _(only supported on JUnit Jupiter)_
|
||||
|
||||
Consider the following example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
public class OrderRepositoryTests { }
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
public class UserRepositoryTests { }
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@RunWith(SpringRunner::class)
|
||||
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
class OrderRepositoryTests { }
|
||||
|
||||
@RunWith(SpringRunner::class)
|
||||
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
class UserRepositoryTests { }
|
||||
----
|
||||
======
|
||||
|
||||
If we discover that we are repeating the preceding configuration across our JUnit 4-based
|
||||
test suite, we can reduce the duplication by introducing a custom composed annotation
|
||||
that centralizes the common test configuration for Spring, as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
public @interface TransactionalDevTestConfig { }
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Target(AnnotationTarget.TYPE)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
annotation class TransactionalDevTestConfig { }
|
||||
----
|
||||
======
|
||||
|
||||
Then we can use our custom `@TransactionalDevTestConfig` annotation to simplify the
|
||||
configuration of individual JUnit 4 based test classes, as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@RunWith(SpringRunner.class)
|
||||
@TransactionalDevTestConfig
|
||||
public class OrderRepositoryTests { }
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@TransactionalDevTestConfig
|
||||
public class UserRepositoryTests { }
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@RunWith(SpringRunner::class)
|
||||
@TransactionalDevTestConfig
|
||||
class OrderRepositoryTests
|
||||
|
||||
@RunWith(SpringRunner::class)
|
||||
@TransactionalDevTestConfig
|
||||
class UserRepositoryTests
|
||||
----
|
||||
======
|
||||
|
||||
If we write tests that use JUnit Jupiter, we can reduce code duplication even further,
|
||||
since annotations in JUnit Jupiter can also be used as meta-annotations. Consider the
|
||||
following example:
|
||||
Consider the following test classes that use the `SpringExtension` with JUnit Jupiter:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -150,13 +47,13 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
|
||||
@ContextConfiguration(classes = {AppConfig.class, TestDataAccessConfig.class})
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
class OrderRepositoryTests { }
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
|
||||
@ContextConfiguration(classes = {AppConfig.class, TestDataAccessConfig.class})
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
class UserRepositoryTests { }
|
||||
@@ -167,23 +64,22 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@ExtendWith(SpringExtension::class)
|
||||
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
|
||||
@ContextConfiguration(classes = [AppConfig::class, TestDataAccessConfig::class])
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
class OrderRepositoryTests { }
|
||||
|
||||
@ExtendWith(SpringExtension::class)
|
||||
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
|
||||
@ContextConfiguration(classes = [AppConfig::class, TestDataAccessConfig::class])
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
class UserRepositoryTests { }
|
||||
----
|
||||
======
|
||||
|
||||
If we discover that we are repeating the preceding configuration across our JUnit
|
||||
Jupiter-based test suite, we can reduce the duplication by introducing a custom composed
|
||||
annotation that centralizes the common test configuration for Spring and JUnit Jupiter,
|
||||
as follows:
|
||||
If we discover that we are repeating the preceding configuration across our test suite,
|
||||
we can reduce the duplication by introducing a custom composed annotation that
|
||||
centralizes the common test configuration for Spring and JUnit Jupiter, as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -194,7 +90,7 @@ Java::
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
|
||||
@ContextConfiguration(classes = {AppConfig.class, TestDataAccessConfig.class})
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
public @interface TransactionalDevTestConfig { }
|
||||
@@ -207,7 +103,7 @@ Kotlin::
|
||||
@Target(AnnotationTarget.TYPE)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@ExtendWith(SpringExtension::class)
|
||||
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
|
||||
@ContextConfiguration(classes = [AppConfig::class, TestDataAccessConfig::class])
|
||||
@ActiveProfiles("dev")
|
||||
@Transactional
|
||||
annotation class TransactionalDevTestConfig { }
|
||||
|
||||
+1
-2
@@ -4,8 +4,7 @@
|
||||
`@AfterTransaction` indicates that the annotated `void` method should be run after a
|
||||
transaction is ended, for test methods that have been configured to run within a
|
||||
transaction by using Spring's `@Transactional` annotation. `@AfterTransaction` methods
|
||||
are not required to be `public` and may be declared on Java 8-based interface default
|
||||
methods.
|
||||
are not required to be `public` and may be declared on interface default methods.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
||||
+1
-2
@@ -4,8 +4,7 @@
|
||||
`@BeforeTransaction` indicates that the annotated `void` method should be run before a
|
||||
transaction is started, for test methods that have been configured to run within a
|
||||
transaction by using Spring's `@Transactional` annotation. `@BeforeTransaction` methods
|
||||
are not required to be `public` and may be declared on Java 8-based interface default
|
||||
methods.
|
||||
are not required to be `public` and may be declared on interface default methods.
|
||||
|
||||
The following example shows how to use the `@BeforeTransaction` annotation:
|
||||
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
`@SqlGroup` is a container annotation that aggregates several `@Sql` annotations. You can
|
||||
use `@SqlGroup` natively to declare several nested `@Sql` annotations, or you can use it
|
||||
in conjunction with Java 8's support for repeatable annotations, where `@Sql` can be
|
||||
in conjunction with Java's support for repeatable annotations, where `@Sql` can be
|
||||
declared several times on the same class or method, implicitly generating this container
|
||||
annotation. The following example shows how to declare an SQL group:
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ and can be used anywhere in the Spring Framework.
|
||||
* `@Qualifier`
|
||||
* `@Value`
|
||||
* `@Resource` (jakarta.annotation) if JSR-250 is present
|
||||
* `@ManagedBean` (jakarta.annotation) if JSR-250 is present
|
||||
* `@Inject` (jakarta.inject) if JSR-330 is present
|
||||
* `@Named` (jakarta.inject) if JSR-330 is present
|
||||
* `@PersistenceContext` (jakarta.persistence) if JPA is present
|
||||
|
||||
@@ -5,24 +5,25 @@ It is important to be able to perform some integration testing without requiring
|
||||
deployment to your application server or connecting to other enterprise infrastructure.
|
||||
Doing so lets you test things such as:
|
||||
|
||||
* The correct wiring of your Spring IoC container contexts.
|
||||
* Data access using JDBC or an ORM tool. This can include such things as the correctness
|
||||
of SQL statements, Hibernate queries, JPA entity mappings, and so forth.
|
||||
* The correct wiring of your Spring components.
|
||||
* Data access using JDBC or an ORM tool.
|
||||
** This can include such things as the correctness of SQL statements, Hibernate queries,
|
||||
JPA entity mappings, and so forth.
|
||||
|
||||
The Spring Framework provides first-class support for integration testing in the
|
||||
`spring-test` module. The name of the actual JAR file might include the release version
|
||||
and might also be in the long `org.springframework.test` form, depending on where you get
|
||||
it from (see the xref:core/beans/dependencies.adoc[section on Dependency Management]
|
||||
for an explanation). This library includes the `org.springframework.test` package, which
|
||||
`spring-test` module. The name of the actual JAR file might include the release version,
|
||||
depending on where you get it from (see the
|
||||
{spring-framework-wiki}/Spring-Framework-Artifacts[Spring Framework Artifacts] wiki page
|
||||
for details). This library includes the `org.springframework.test` package, which
|
||||
contains valuable classes for integration testing with a Spring container. This testing
|
||||
does not rely on an application server or other deployment environment. Such tests are
|
||||
slower to run than unit tests but much faster than the equivalent Selenium tests or
|
||||
remote tests that rely on deployment to an application server.
|
||||
|
||||
Unit and integration testing support is provided in the form of the annotation-driven
|
||||
xref:testing/testcontext-framework.adoc[Spring TestContext Framework]. The TestContext framework is
|
||||
agnostic of the actual testing framework in use, which allows instrumentation of tests
|
||||
in various environments, including JUnit, TestNG, and others.
|
||||
xref:testing/testcontext-framework.adoc[Spring TestContext Framework]. The TestContext
|
||||
framework is agnostic of the actual testing framework in use, which allows
|
||||
instrumentation of tests in various environments, including JUnit, TestNG, and others.
|
||||
|
||||
The following section provides an overview of the high-level goals of Spring's
|
||||
integration support, and the rest of this chapter then focuses on dedicated topics:
|
||||
|
||||
@@ -184,7 +184,10 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
|
||||
standaloneSetup(SimpleController())
|
||||
.alwaysExpect<StandaloneMockMvcBuilder>(status().isOk())
|
||||
.alwaysExpect<StandaloneMockMvcBuilder>(content().contentType("application/json;charset=UTF-8"))
|
||||
.build()
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
|
||||
mockMvc = standaloneSetup(PersonController()).addFilters<StandaloneMockMvcBuilder>(CharacterEncodingFilter()).build()
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -159,7 +159,18 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
|
||||
class MyWebTests {
|
||||
|
||||
lateinit var mockMvc: MockMvc
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
mockMvc = standaloneSetup(AccountController())
|
||||
.defaultRequest<StandaloneMockMvcBuilder>(get("/")
|
||||
.contextPath("/app").servletPath("/main")
|
||||
.accept(MediaType.APPLICATION_JSON)).build()
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -25,7 +25,13 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
|
||||
// static import of MockMvcBuilders.standaloneSetup
|
||||
|
||||
val mockMvc = standaloneSetup(MusicController())
|
||||
.defaultRequest<StandaloneMockMvcBuilder>(get("/").accept(MediaType.APPLICATION_JSON))
|
||||
.alwaysExpect<StandaloneMockMvcBuilder>(status().isOk())
|
||||
.alwaysExpect<StandaloneMockMvcBuilder>(content().contentType("application/json;charset=UTF-8"))
|
||||
.build()
|
||||
----
|
||||
======
|
||||
|
||||
@@ -53,7 +59,13 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
|
||||
// static import of SharedHttpSessionConfigurer.sharedHttpSession
|
||||
|
||||
val mockMvc = MockMvcBuilders.standaloneSetup(TestController())
|
||||
.apply<StandaloneMockMvcBuilder>(sharedHttpSession())
|
||||
.build()
|
||||
|
||||
// Use mockMvc to perform requests...
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ use https://www.gebish.org/[Geb] to make our tests even Groovy-er.
|
||||
== Why Geb and MockMvc?
|
||||
|
||||
Geb is backed by WebDriver, so it offers many of the
|
||||
xref:testing/mockmvc/htmlunit/webdriver.adoc#spring-mvc-test-server-htmlunit-webdriver-why[same benefits] that we get from
|
||||
WebDriver. However, Geb makes things even easier by taking care of some of the
|
||||
boilerplate code for us.
|
||||
xref:testing/mockmvc/htmlunit/webdriver.adoc#mockmvc-server-htmlunit-webdriver-why[same benefits]
|
||||
that we get from WebDriver. However, Geb makes things even easier by taking care of some
|
||||
of the boilerplate code for us.
|
||||
|
||||
[[mockmvc-server-htmlunit-geb-setup]]
|
||||
== MockMvc and Geb Setup
|
||||
@@ -28,7 +28,8 @@ def setup() {
|
||||
----
|
||||
|
||||
NOTE: This is a simple example of using `MockMvcHtmlUnitDriverBuilder`. For more advanced
|
||||
usage, see xref:testing/mockmvc/htmlunit/webdriver.adoc#spring-mvc-test-server-htmlunit-webdriver-advanced-builder[Advanced `MockMvcHtmlUnitDriverBuilder`].
|
||||
usage, see
|
||||
xref:testing/mockmvc/htmlunit/webdriver.adoc#mockmvc-server-htmlunit-webdriver-advanced-builder[Advanced `MockMvcHtmlUnitDriverBuilder`].
|
||||
|
||||
This ensures that any URL referencing `localhost` as the server is directed to our
|
||||
`MockMvc` instance without the need for a real HTTP connection. Any other URL is
|
||||
@@ -62,10 +63,10 @@ forwarded to the current page object. This removes a lot of the boilerplate code
|
||||
needed when using WebDriver directly.
|
||||
|
||||
As with direct WebDriver usage, this improves on the design of our
|
||||
xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-usage[HtmlUnit test] by using the Page Object
|
||||
Pattern. As mentioned previously, we can use the Page Object Pattern with HtmlUnit and
|
||||
WebDriver, but it is even easier with Geb. Consider our new Groovy-based
|
||||
`CreateMessagePage` implementation:
|
||||
xref:testing/mockmvc/htmlunit/mah.adoc#mockmvc-server-htmlunit-mah-usage[HtmlUnit test]
|
||||
by using the Page Object Pattern. As mentioned previously, we can use the Page Object
|
||||
Pattern with HtmlUnit and WebDriver, but it is even easier with Geb. Consider our new
|
||||
Groovy-based `CreateMessagePage` implementation:
|
||||
|
||||
[source,groovy]
|
||||
----
|
||||
|
||||
@@ -7,8 +7,7 @@ to use the raw HtmlUnit libraries.
|
||||
[[mockmvc-server-htmlunit-mah-setup]]
|
||||
== MockMvc and HtmlUnit Setup
|
||||
|
||||
First, make sure that you have included a test dependency on
|
||||
`org.htmlunit:htmlunit`.
|
||||
First, make sure that you have included a test dependency on `org.htmlunit:htmlunit`.
|
||||
|
||||
We can easily create an HtmlUnit `WebClient` that integrates with MockMvc by using the
|
||||
`MockMvcWebClientBuilder`, as follows:
|
||||
@@ -45,7 +44,7 @@ Kotlin::
|
||||
======
|
||||
|
||||
NOTE: This is a simple example of using `MockMvcWebClientBuilder`. For advanced usage,
|
||||
see xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-advanced-builder[Advanced `MockMvcWebClientBuilder`].
|
||||
see <<mockmvc-server-htmlunit-mah-advanced-builder>>.
|
||||
|
||||
This ensures that any URL that references `localhost` as the server is directed to our
|
||||
`MockMvc` instance without the need for a real HTTP connection. Any other URL is
|
||||
@@ -77,7 +76,7 @@ Kotlin::
|
||||
======
|
||||
|
||||
NOTE: The default context path is `""`. Alternatively, we can specify the context path,
|
||||
as described in xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-advanced-builder[Advanced `MockMvcWebClientBuilder`].
|
||||
as described in <<mockmvc-server-htmlunit-mah-advanced-builder>>.
|
||||
|
||||
Once we have a reference to the `HtmlPage`, we can then fill out the form and submit it
|
||||
to create a message, as the following example shows:
|
||||
@@ -144,10 +143,10 @@ Kotlin::
|
||||
======
|
||||
|
||||
The preceding code improves on our
|
||||
xref:testing/mockmvc/htmlunit/why.adoc#spring-mvc-test-server-htmlunit-mock-mvc-test[MockMvc test] in a number of ways.
|
||||
First, we no longer have to explicitly verify our form and then create a request that
|
||||
looks like the form. Instead, we request the form, fill it out, and submit it, thereby
|
||||
significantly reducing the overhead.
|
||||
xref:testing/mockmvc/htmlunit/why.adoc#mockmvc-server-htmlunit-why[MockMvc test] in a
|
||||
number of ways. First, we no longer have to explicitly verify our form and then create a
|
||||
request that looks like the form. Instead, we request the form, fill it out, and submit
|
||||
it, thereby significantly reducing the overhead.
|
||||
|
||||
Another important factor is that https://htmlunit.sourceforge.io/javascript.html[HtmlUnit
|
||||
uses the Mozilla Rhino engine] to evaluate JavaScript. This means that we can also test
|
||||
@@ -267,7 +266,19 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
|
||||
val mockMvc = MockMvcBuilders
|
||||
.webAppContextSetup(context)
|
||||
.apply<DefaultMockMvcBuilder>(springSecurity())
|
||||
.build()
|
||||
|
||||
webClient = MockMvcWebClientBuilder
|
||||
.mockMvcSetup(mockMvc)
|
||||
// for illustration only - defaults to ""
|
||||
.contextPath("")
|
||||
// By default MockMvc is used for localhost only;
|
||||
// the following will use MockMvc for example.com and example.org as well
|
||||
.useMockMvcForHosts("example.com", "example.org")
|
||||
.build()
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -203,7 +203,7 @@ Kotlin::
|
||||
======
|
||||
|
||||
NOTE: This is a simple example of using `MockMvcHtmlUnitDriverBuilder`. For more advanced
|
||||
usage, see xref:testing/mockmvc/htmlunit/webdriver.adoc#spring-mvc-test-server-htmlunit-webdriver-advanced-builder[Advanced `MockMvcHtmlUnitDriverBuilder`].
|
||||
usage, see <<mockmvc-server-htmlunit-webdriver-advanced-builder>>.
|
||||
|
||||
The preceding example ensures that any URL that references `localhost` as the server is
|
||||
directed to our `MockMvc` instance without the need for a real HTTP connection. Any other
|
||||
@@ -259,10 +259,11 @@ Kotlin::
|
||||
======
|
||||
--
|
||||
|
||||
This improves on the design of our xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-usage[HtmlUnit test]
|
||||
This improves on the design of our
|
||||
xref:testing/mockmvc/htmlunit/mah.adoc#mockmvc-server-htmlunit-mah-usage[HtmlUnit test]
|
||||
by leveraging the Page Object Pattern. As we mentioned in
|
||||
xref:testing/mockmvc/htmlunit/webdriver.adoc#mockmvc-server-htmlunit-webdriver-why[Why WebDriver and MockMvc?], we can use the Page Object Pattern
|
||||
with HtmlUnit, but it is much easier with WebDriver. Consider the following
|
||||
<<mockmvc-server-htmlunit-webdriver-why>>, we can use the Page Object Pattern with
|
||||
HtmlUnit, but it is much easier with WebDriver. Consider the following
|
||||
`CreateMessagePage` implementation:
|
||||
|
||||
--
|
||||
@@ -562,7 +563,19 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
|
||||
val mockMvc: MockMvc = MockMvcBuilders
|
||||
.webAppContextSetup(context)
|
||||
.apply<DefaultMockMvcBuilder>(springSecurity())
|
||||
.build()
|
||||
|
||||
driver = MockMvcHtmlUnitDriverBuilder
|
||||
.mockMvcSetup(mockMvc)
|
||||
// for illustration only - defaults to ""
|
||||
.contextPath("")
|
||||
// By default MockMvc is used for localhost only;
|
||||
// the following will use MockMvc for example.com and example.org as well
|
||||
.useMockMvcForHosts("example.com", "example.org")
|
||||
.build()
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ assume our form looks like the following snippet:
|
||||
</form>
|
||||
----
|
||||
|
||||
How do we ensure that our form produce the correct request to create a new message? A
|
||||
How do we ensure that our form produces the correct request to create a new message? A
|
||||
naive attempt might resemble the following:
|
||||
|
||||
[tabs]
|
||||
@@ -154,7 +154,7 @@ validation.
|
||||
[[mockmvc-server-htmlunit-why-integration]]
|
||||
== Integration Testing to the Rescue?
|
||||
|
||||
To resolve the issues mentioned earlier, we could perform end-to-end integration testing,
|
||||
To resolve the issues mentioned above, we could perform end-to-end integration testing,
|
||||
but this has some drawbacks. Consider testing the view that lets us page through the
|
||||
messages. We might need the following tests:
|
||||
|
||||
@@ -171,7 +171,7 @@ leads to a number of additional challenges:
|
||||
* Testing can become slow, since each test would need to ensure that the database is in
|
||||
the correct state.
|
||||
* Since our database needs to be in a specific state, we cannot run tests in parallel.
|
||||
* Performing assertions on such items as auto-generated IDs, timestamps, and others can
|
||||
* Performing assertions on items such as auto-generated IDs, timestamps, and others can
|
||||
be difficult.
|
||||
|
||||
These challenges do not mean that we should abandon end-to-end integration testing
|
||||
|
||||
@@ -11,7 +11,7 @@ https://testng.org/[TestNG] ::
|
||||
testing, distributed testing, and other features. Supported in the
|
||||
xref:testing/testcontext-framework.adoc[Spring TestContext Framework].
|
||||
{assertj-docs}[AssertJ] ::
|
||||
"Fluent assertions for Java", including support for Java 8 lambdas, streams, and
|
||||
"Fluent assertions for Java", including support for lambda expressions, streams, and
|
||||
numerous other features. Supported in Spring's
|
||||
xref:testing/mockmvc/assertj.adoc[MockMvc testing support].
|
||||
https://en.wikipedia.org/wiki/Mock_Object[Mock Objects] ::
|
||||
|
||||
@@ -10,7 +10,7 @@ applications without a running server via MockMvc.
|
||||
|
||||
|
||||
|
||||
[[resttestclient-setup]]
|
||||
[[resttestclient.setup]]
|
||||
== Setup
|
||||
|
||||
To set up a `RestTestClient` you need to choose a server setup to bind to. This can be one
|
||||
@@ -18,7 +18,7 @@ of several MockMvc setup choices, or a connection to a live server.
|
||||
|
||||
|
||||
|
||||
[[resttestclient-controller-config]]
|
||||
[[resttestclient.controller-config]]
|
||||
=== Bind to Controller
|
||||
|
||||
This setup allows you to test specific controller(s) via mock request and response objects,
|
||||
@@ -42,55 +42,17 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
[[resttestclient-context-config]]
|
||||
[[resttestclient.context-config]]
|
||||
=== Bind to `ApplicationContext`
|
||||
|
||||
This setup allows you to load Spring configuration with Spring MVC
|
||||
infrastructure and controller declarations and use it to handle requests via mock request
|
||||
and response objects, without a running server.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig(WebConfig.class) // <1>
|
||||
class MyTests {
|
||||
include-code::./RestClientContextTests[indent=0]
|
||||
|
||||
RestTestClient client;
|
||||
|
||||
@BeforeEach
|
||||
void setUp(ApplicationContext context) { // <2>
|
||||
client = RestTestClient.bindToApplicationContext(context).build(); // <3>
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Specify the configuration to load
|
||||
<2> Inject the configuration
|
||||
<3> Create the `RestTestClient`
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig(WebConfig::class) // <1>
|
||||
class MyTests {
|
||||
|
||||
lateinit var client: RestTestClient
|
||||
|
||||
@BeforeEach
|
||||
fun setUp(context: ApplicationContext) { // <2>
|
||||
client = RestTestClient.bindToApplicationContext(context).build() // <3>
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Specify the configuration to load
|
||||
<2> Inject the configuration
|
||||
<3> Create the `RestTestClient`
|
||||
======
|
||||
|
||||
[[resttestclient-fn-config]]
|
||||
[[resttestclient.fn-config]]
|
||||
=== Bind to Router Function
|
||||
|
||||
This setup allows you to test xref:web/webmvc-functional.adoc[functional endpoints] via
|
||||
@@ -115,7 +77,7 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
[[resttestclient-server-config]]
|
||||
[[resttestclient.server-config]]
|
||||
=== Bind to Server
|
||||
|
||||
This setup connects to a running server to perform full, end-to-end HTTP tests:
|
||||
@@ -139,7 +101,7 @@ Kotlin::
|
||||
|
||||
|
||||
|
||||
[[resttestclient-client-config]]
|
||||
[[resttestclient.client-config]]
|
||||
=== Client Config
|
||||
|
||||
In addition to the server setup options described earlier, you can also configure client
|
||||
@@ -170,7 +132,7 @@ Kotlin::
|
||||
|
||||
|
||||
|
||||
[[resttestclient-tests]]
|
||||
[[resttestclient.tests]]
|
||||
== Writing Tests
|
||||
|
||||
xref:integration/rest-clients.adoc#rest-restclient[`RestClient`] and `RestTestClient` have
|
||||
@@ -182,70 +144,22 @@ provides two alternative ways to verify the response:
|
||||
|
||||
|
||||
|
||||
[[resttestclient-workflow]]
|
||||
[[resttestclient.workflow]]
|
||||
=== Built-in Assertions
|
||||
|
||||
To use the built-in assertions, remain in the workflow after the call to `exchange()`, and
|
||||
use one of the expectation methods. For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
client.get().uri("/persons/1")
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectHeader().contentType(MediaType.APPLICATION_JSON);
|
||||
----
|
||||
include-code::./RestClientWorkflowTests[tag=test,indent=0]
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
client.get().uri("/persons/1")
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectHeader().contentType(MediaType.APPLICATION_JSON)
|
||||
----
|
||||
======
|
||||
|
||||
If you would like for all expectations to be asserted even if one of them fails, you can
|
||||
use `expectAll(..)` instead of multiple chained `expect*(..)` calls. This feature is
|
||||
similar to the _soft assertions_ support in AssertJ and the `assertAll()` support in
|
||||
JUnit Jupiter.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
client.get().uri("/persons/1")
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.exchange()
|
||||
.expectAll(
|
||||
spec -> spec.expectStatus().isOk(),
|
||||
spec -> spec.expectHeader().contentType(MediaType.APPLICATION_JSON)
|
||||
);
|
||||
----
|
||||
include-code::./RestClientWorkflowTests[tag=soft-assertions,indent=0]
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
client.get().uri("/persons/1")
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.exchange()
|
||||
.expectAll(
|
||||
{ spec -> spec.expectStatus().isOk() },
|
||||
{ spec -> spec.expectHeader().contentType(MediaType.APPLICATION_JSON) }
|
||||
)
|
||||
----
|
||||
======
|
||||
|
||||
You can then choose to decode the response body through one of the following:
|
||||
|
||||
@@ -256,124 +170,34 @@ You can then choose to decode the response body through one of the following:
|
||||
If the built-in assertions are insufficient, you can consume the object instead and
|
||||
perform any other assertions:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
client.get().uri("/persons/1")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody(Person.class)
|
||||
.consumeWith(result -> {
|
||||
// custom assertions (for example, AssertJ)...
|
||||
});
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
client.get().uri("/persons/1")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody<Person>()
|
||||
.consumeWith {
|
||||
// custom assertions (for example, AssertJ)...
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./RestClientWorkflowTests[tag=consume,indent=0]
|
||||
|
||||
Or you can exit the workflow and obtain a `EntityExchangeResult`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
EntityExchangeResult<Person> result = client.get().uri("/persons/1")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody(Person.class)
|
||||
.returnResult();
|
||||
----
|
||||
include-code::./RestClientWorkflowTests[tag=result,indent=0]
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val result = client.get().uri("/persons/1")
|
||||
.exchange()
|
||||
.expectStatus().isOk
|
||||
.expectBody<Person>()
|
||||
.returnResult()
|
||||
----
|
||||
======
|
||||
|
||||
TIP: When you need to decode to a target type with generics, look for the overloaded methods
|
||||
that accept {spring-framework-api}/core/ParameterizedTypeReference.html[`ParameterizedTypeReference`]
|
||||
instead of `Class<T>`.
|
||||
|
||||
|
||||
[[resttestclient-no-content]]
|
||||
[[resttestclient.no-content]]
|
||||
==== No Content
|
||||
|
||||
If the response is not expected to have content, you can assert that as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
client.post().uri("/persons")
|
||||
.body(person)
|
||||
.exchange()
|
||||
.expectStatus().isCreated()
|
||||
.expectBody().isEmpty();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
client.post().uri("/persons")
|
||||
.body(person)
|
||||
.exchange()
|
||||
.expectStatus().isCreated()
|
||||
.expectBody().isEmpty()
|
||||
----
|
||||
======
|
||||
include-code::./NoContentTests[tag=emptyBody,indent=0]
|
||||
|
||||
If you want to ignore the response content, the following releases the content without any assertions:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
client.get().uri("/persons/123")
|
||||
.exchange()
|
||||
.expectStatus().isNotFound()
|
||||
.expectBody(Void.class);
|
||||
----
|
||||
include-code::./NoContentTests[tag=ignoreBody,indent=0]
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
client.get().uri("/persons/123")
|
||||
.exchange()
|
||||
.expectStatus().isNotFound
|
||||
.expectBody<Unit>()
|
||||
----
|
||||
======
|
||||
NOTE: Consuming the response body (for example, with `expectBody`) is required if your tests are running with
|
||||
leak detection for pooled buffers. Without that, the tool will report buffers being leaked.
|
||||
|
||||
|
||||
[[resttestclient-json]]
|
||||
[[resttestclient.json]]
|
||||
==== JSON Content
|
||||
|
||||
You can use `expectBody()` without a target type to perform assertions on the raw
|
||||
@@ -381,126 +205,25 @@ content rather than through higher level Object(s).
|
||||
|
||||
To verify the full JSON content with https://jsonassert.skyscreamer.org[JSONAssert]:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
client.get().uri("/persons/1")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody()
|
||||
.json("{\"name\":\"Jane\"}")
|
||||
----
|
||||
include-code::./JsonTests[tag=jsonBody,indent=0]
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
client.get().uri("/persons/1")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody()
|
||||
.json("{\"name\":\"Jane\"}")
|
||||
----
|
||||
======
|
||||
|
||||
To verify JSON content with https://github.com/jayway/JsonPath[JSONPath]:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
client.get().uri("/persons")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody()
|
||||
.jsonPath("$[0].name").isEqualTo("Jane")
|
||||
.jsonPath("$[1].name").isEqualTo("Jason");
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
client.get().uri("/persons")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody()
|
||||
.jsonPath("$[0].name").isEqualTo("Jane")
|
||||
.jsonPath("$[1].name").isEqualTo("Jason")
|
||||
----
|
||||
======
|
||||
include-code::./JsonTests[tag=jsonPath,indent=0]
|
||||
|
||||
|
||||
|
||||
[[resttestclient-assertj]]
|
||||
[[resttestclient.assertj]]
|
||||
=== AssertJ Integration
|
||||
|
||||
`RestTestClientResponse` is the main entry point for the AssertJ integration.
|
||||
It is an `AssertProvider` that wraps the `ResponseSpec` of an exchange in order to enable
|
||||
use of `assertThat()` statements. For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
ResponseSpec spec = client.get().uri("/persons").exchange();
|
||||
include-code::./AssertJTests[tag=withSpec,indent=0]
|
||||
|
||||
RestTestClientResponse response = RestTestClientResponse.from(spec);
|
||||
assertThat(response).hasStatusOk();
|
||||
assertThat(response).hasContentTypeCompatibleWith(MediaType.TEXT_PLAIN);
|
||||
// ...
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val spec = client.get().uri("/persons").exchange()
|
||||
|
||||
val response = RestTestClientResponse.from(spec)
|
||||
assertThat(response).hasStatusOk()
|
||||
assertThat(response).hasContentTypeCompatibleWith(MediaType.TEXT_PLAIN)
|
||||
// ...
|
||||
----
|
||||
======
|
||||
|
||||
You can also use the built-in workflow first, and then obtain an `ExchangeResult` to wrap
|
||||
and continue with AssertJ. For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
ExchangeResult result = client.get().uri("/persons").exchange()
|
||||
. // ...
|
||||
.returnResult();
|
||||
|
||||
RestTestClientResponse response = RestTestClientResponse.from(result);
|
||||
assertThat(response).hasStatusOk();
|
||||
assertThat(response).hasContentTypeCompatibleWith(MediaType.TEXT_PLAIN);
|
||||
// ...
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val result = client.get().uri("/persons").exchange()
|
||||
. // ...
|
||||
.returnResult()
|
||||
|
||||
val response = RestTestClientResponse.from(spec)
|
||||
assertThat(response).hasStatusOk()
|
||||
assertThat(response).hasContentTypeCompatibleWith(MediaType.TEXT_PLAIN)
|
||||
// ...
|
||||
----
|
||||
======
|
||||
include-code::./AssertJTests[tag=withResult,indent=0]
|
||||
|
||||
+15
-15
@@ -49,7 +49,6 @@ Kotlin::
|
||||
<1> Injecting the `ApplicationContext`.
|
||||
======
|
||||
|
||||
|
||||
Similarly, if your test is configured to load a `WebApplicationContext`, you can inject
|
||||
the web application context into your test, as follows:
|
||||
|
||||
@@ -87,7 +86,6 @@ Kotlin::
|
||||
<2> Injecting the `WebApplicationContext`.
|
||||
======
|
||||
|
||||
|
||||
Dependency injection by using `@Autowired` is provided by the
|
||||
`DependencyInjectionTestExecutionListener`, which is configured by default
|
||||
(see xref:testing/testcontext-framework/fixture-di.adoc[Dependency Injection of Test Fixtures]).
|
||||
@@ -96,22 +94,24 @@ Dependency injection by using `@Autowired` is provided by the
|
||||
Test classes that use the TestContext framework do not need to extend any particular
|
||||
class or implement a specific interface to configure their application context. Instead,
|
||||
configuration is achieved by declaring the `@ContextConfiguration` annotation at the
|
||||
class level. If your test class does not explicitly declare application context resource
|
||||
locations or component classes, the configured `ContextLoader` determines how to load a
|
||||
context from a default location or default configuration classes. In addition to context
|
||||
resource locations and component classes, an application context can also be configured
|
||||
through application context initializers.
|
||||
class level. If your test class does not explicitly declare component classes or resource
|
||||
locations, the configured `ContextLoader` determines how to load a context from _default_
|
||||
configuration classes or a _default_ location. In addition to component classes and
|
||||
context resource locations, an application context can also be configured through
|
||||
xref:testing/testcontext-framework/ctx-management/context-customizers.adoc[context customizers]
|
||||
or xref:testing/testcontext-framework/ctx-management/initializers.adoc[context initializers].
|
||||
|
||||
The following sections explain how to use Spring's `@ContextConfiguration` annotation to
|
||||
configure a test `ApplicationContext` by using XML configuration files, Groovy scripts,
|
||||
component classes (typically `@Configuration` classes), or context initializers.
|
||||
Alternatively, you can implement and configure your own custom `SmartContextLoader` for
|
||||
advanced use cases.
|
||||
The following sections explain how to use `@ContextConfiguration` and related annotations
|
||||
to configure a test `ApplicationContext` by using component classes (typically
|
||||
`@Configuration` classes), XML configuration files, Groovy scripts, context customizers,
|
||||
or context initializers. Alternatively, you can implement and configure your own custom
|
||||
`SmartContextLoader` for advanced use cases.
|
||||
|
||||
* xref:testing/testcontext-framework/ctx-management/xml.adoc[Context Configuration with XML resources]
|
||||
* xref:testing/testcontext-framework/ctx-management/groovy.adoc[Context Configuration with Groovy Scripts]
|
||||
* xref:testing/testcontext-framework/ctx-management/javaconfig.adoc[Context Configuration with Component Classes]
|
||||
* xref:testing/testcontext-framework/ctx-management/mixed-config.adoc[Mixing XML, Groovy Scripts, and Component Classes]
|
||||
* xref:testing/testcontext-framework/ctx-management/xml.adoc[Context Configuration with XML Resources]
|
||||
* xref:testing/testcontext-framework/ctx-management/groovy.adoc[Context Configuration with Groovy Scripts]
|
||||
* xref:testing/testcontext-framework/ctx-management/default-config.adoc[Default Context Configuration]
|
||||
* xref:testing/testcontext-framework/ctx-management/mixed-config.adoc[Mixing Component Classes, XML, and Groovy Scripts]
|
||||
* xref:testing/testcontext-framework/ctx-management/context-customizers.adoc[Context Configuration with Context Customizers]
|
||||
* xref:testing/testcontext-framework/ctx-management/initializers.adoc[Context Configuration with Context Initializers]
|
||||
* xref:testing/testcontext-framework/ctx-management/inheritance.adoc[Context Configuration Inheritance]
|
||||
|
||||
-11
@@ -62,17 +62,6 @@ script by setting a JVM system property named `spring.test.context.cache.maxSize
|
||||
alternative, you can set the same property via the
|
||||
xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism.
|
||||
|
||||
As of Spring Framework 7.0, an application context stored in the context cache will be
|
||||
_paused_ when it is no longer actively in use and automatically _restarted_ the next time
|
||||
the context is retrieved from the cache. Specifically, the latter will restart all
|
||||
auto-startup beans in the application context, effectively restoring the lifecycle state.
|
||||
This ensures that background processes within the context are not actively running while
|
||||
the context is not used by tests. For example, JMS listener containers, scheduled tasks,
|
||||
and any other components in the context that implement `Lifecycle` or `SmartLifecycle`
|
||||
will be in a "stopped" state until the context is used again by a test. Note, however,
|
||||
that `SmartLifecycle` components can opt out of pausing by returning `false` from
|
||||
`SmartLifecycle#isPauseable()`.
|
||||
|
||||
Since having a large number of application contexts loaded within a given test suite can
|
||||
cause the suite to take an unnecessarily long time to run, it is often beneficial to
|
||||
know exactly how many contexts have been loaded and cached. To view the statistics for
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
[[testcontext-ctx-management-pausing]]
|
||||
= Context Pausing
|
||||
|
||||
As of Spring Framework 7.0, an `ApplicationContext` stored in the context cache (see
|
||||
xref:testing/testcontext-framework/ctx-management/caching.adoc[Context Caching]) may be
|
||||
_paused_ when it is no longer actively in use and automatically _restarted_ the next time
|
||||
the context is retrieved from the cache. Specifically, the latter will restart all
|
||||
auto-startup beans in the application context, effectively restoring the lifecycle state.
|
||||
This ensures that background processes within the context are not actively running while
|
||||
the context is not used by tests. For example, JMS listener containers, scheduled tasks,
|
||||
and any other components in the context that implement `Lifecycle` or `SmartLifecycle`
|
||||
will be in a "stopped" state until the context is used again by a test. Note, however,
|
||||
that `SmartLifecycle` components can opt out of pausing by returning `false` from
|
||||
`SmartLifecycle#isPauseable()`.
|
||||
|
||||
You can control whether inactive application contexts should be paused by setting the
|
||||
`PauseMode` to one of the following supported values.
|
||||
|
||||
`ALWAYS` :: Always pause inactive application contexts.
|
||||
`ON_CONTEXT_SWITCH` :: Only pause inactive application contexts if the next context
|
||||
retrieved from the context cache is a different context.
|
||||
`NEVER` :: Never pause inactive application contexts, effectively disabling the pausing
|
||||
feature of the context cache.
|
||||
|
||||
The `PauseMode` defaults to `ON_CONTEXT_SWITCH`, but it can be changed from the command
|
||||
line or a build script by setting a JVM system property named
|
||||
`spring.test.context.cache.pause` to one of the supported values (case insensitive). As
|
||||
an alternative, you can set the property via the
|
||||
xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism.
|
||||
|
||||
For example, if you want inactive application contexts to always be paused, you can
|
||||
switch from the default `ON_CONTEXT_SWITCH` mode to `ALWAYS` by setting the
|
||||
`spring.test.context.cache.pause` system property to `always`.
|
||||
|
||||
```shell
|
||||
-Dspring.test.context.cache.pause=always
|
||||
```
|
||||
Similarly, if you encounter issues with `Lifecycle` components that cannot or should not
|
||||
opt out of pausing, or if you discover that your test suite runs more slowly due to the
|
||||
pausing and restarting of application contexts, you can disable the pausing feature by
|
||||
setting the `spring.test.context.cache.pause` system property to `never`.
|
||||
|
||||
```shell
|
||||
-Dspring.test.context.cache.pause=never
|
||||
```
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
[[testcontext-ctx-management-default-config]]
|
||||
= Default Context Configuration
|
||||
|
||||
As explained in the sections on
|
||||
xref:testing/testcontext-framework/ctx-management/javaconfig.adoc[component classes],
|
||||
xref:testing/testcontext-framework/ctx-management/xml.adoc[XML resources], and
|
||||
xref:testing/testcontext-framework/ctx-management/groovy.adoc[Groovy scripts], the
|
||||
TestContext framework will attempt to locate _default_ context configuration if you do
|
||||
not explicitly specify `@Configuration` classes, XML configuration files, or Groovy
|
||||
scripts from which the test's `ApplicationContext` should be loaded.
|
||||
|
||||
However, due to a bug in the detection algorithm, default context configuration for a
|
||||
superclass or enclosing class is currently ignored if the type hierarchy or enclosing
|
||||
class hierarchy (for `@Nested` test classes) does not declare `@ContextConfiguration`.
|
||||
|
||||
Beginning with Spring Framework 7.1, the TestContext framework will reliably detect
|
||||
**all** _default_ context configuration within a type hierarchy or enclosing class
|
||||
hierarchy above a given test class in such scenarios. Consequently, test suites may
|
||||
encounter issues after the upgrade to 7.1. For example, if a static nested
|
||||
`@Configuration` class in a superclass or enclosing class is ignored due to the
|
||||
aforementioned bug, after the bug has been fixed in 7.1 that `@Configuration` class will
|
||||
no longer be ignored, which may lead to unexpected beans in the resulting
|
||||
`ApplicationContext` our outright failures in tests.
|
||||
|
||||
In the interim, the TestContext framework logs a warning whenever it encounters _default_
|
||||
context configuration that is currently ignored — for example, a `@Configuration` class
|
||||
or XML configuration file. The remainder of this section provides guidance on how to
|
||||
address such issues if you encounter warnings in your test suite.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
Annotating the affected subclass or `@Nested` class with `@ContextConfiguration` allows
|
||||
you to take matters into your own hands and specify which classes in the hierarchy are
|
||||
actually intended to contribute context configuration.
|
||||
====
|
||||
|
||||
If you do not want static nested `@Configuration` classes to be processed, you can:
|
||||
|
||||
- Remove the `@Configuration` declaration.
|
||||
- Apply `@ContextConfiguration` only where you actually want such classes to be processed.
|
||||
- Move the static nested `@Configuration` classes to standalone top-level classes so that
|
||||
they cannot be accidentally interpreted as _default_ configuration classes.
|
||||
|
||||
Similarly, if you encounter issues with _default_ XML configuration files or Groovy
|
||||
scripts being detected and you do not want them to be processed, you can:
|
||||
|
||||
- Apply `@ContextConfiguration` only where you actually want such resources to be
|
||||
processed.
|
||||
- Rename the resource files to something that does not match the default naming
|
||||
convention (such as `*-context.xml` for XML configuration) so that they cannot be
|
||||
accidentally interpreted as _default_ configuration files.
|
||||
- Move the affected resource files to a different package or filesystem location within
|
||||
your project.
|
||||
+21
-22
@@ -1,33 +1,32 @@
|
||||
[[testcontext-ctx-management-mixed-config]]
|
||||
= Mixing XML, Groovy Scripts, and Component Classes
|
||||
= Mixing Component Classes, XML, and Groovy Scripts
|
||||
|
||||
It may sometimes be desirable to mix XML configuration files, Groovy scripts, and
|
||||
component classes (typically `@Configuration` classes) to configure an
|
||||
`ApplicationContext` for your tests. For example, if you use XML configuration in
|
||||
production, you may decide that you want to use `@Configuration` classes to configure
|
||||
It may sometimes be desirable to mix component classes (typically `@Configuration`
|
||||
classes), XML configuration files, or Groovy scripts to configure an `ApplicationContext`
|
||||
for your tests. For example, if you use XML configuration in production for legacy
|
||||
reasons, you may decide that you want to use `@Configuration` classes to configure
|
||||
specific Spring-managed components for your tests, or vice versa.
|
||||
|
||||
Furthermore, some third-party frameworks (such as Spring Boot) provide first-class
|
||||
support for loading an `ApplicationContext` from different types of resources
|
||||
simultaneously (for example, XML configuration files, Groovy scripts, and
|
||||
`@Configuration` classes). The Spring Framework, historically, has not supported this for
|
||||
standard deployments. Consequently, most of the `SmartContextLoader` implementations that
|
||||
the Spring Framework delivers in the `spring-test` module support only one resource type
|
||||
for each test context. However, this does not mean that you cannot use both. One
|
||||
exception to the general rule is that the `GenericGroovyXmlContextLoader` and
|
||||
simultaneously (for example, `@Configuration` classes, XML configuration files, and
|
||||
Groovy scripts). The Spring Framework, historically, has not supported this for standard
|
||||
deployments. Consequently, most of the `SmartContextLoader` implementations that the
|
||||
Spring Framework delivers in the `spring-test` module support only one resource type for
|
||||
each test context. However, this does not mean that you cannot use a mixture of resource
|
||||
types. One exception to the general rule is that the `GenericGroovyXmlContextLoader` and
|
||||
`GenericGroovyXmlWebContextLoader` support both XML configuration files and Groovy
|
||||
scripts simultaneously. Furthermore, third-party frameworks may choose to support the
|
||||
declaration of both `locations` and `classes` through `@ContextConfiguration`, and, with
|
||||
declaration of both `classes` and `locations` through `@ContextConfiguration`, and, with
|
||||
the standard testing support in the TestContext framework, you have the following options.
|
||||
|
||||
If you want to use resource locations (for example, XML or Groovy) and `@Configuration`
|
||||
classes to configure your tests, you must pick one as the entry point, and that one must
|
||||
include or import the other. For example, in XML or Groovy scripts, you can include
|
||||
`@Configuration` classes by using component scanning or defining them as normal Spring
|
||||
beans, whereas, in a `@Configuration` class, you can use `@ImportResource` to import XML
|
||||
configuration files or Groovy scripts. Note that this behavior is semantically equivalent
|
||||
If you want to use `@Configuration` classes and resource locations (for example, XML or
|
||||
Groovy) to configure your tests, you must pick one as the entry point, and that one must
|
||||
import or include the other. For example, in a `@Configuration` class, you can use
|
||||
`@ImportResource` to import XML configuration files or Groovy scripts; whereas, in XML or
|
||||
Groovy scripts, you can include `@Configuration` classes by using component scanning or
|
||||
defining them as normal Spring beans. Note that this behavior is semantically equivalent
|
||||
to how you configure your application in production: In production configuration, you
|
||||
define either a set of XML or Groovy resource locations or a set of `@Configuration`
|
||||
classes from which your production `ApplicationContext` is loaded, but you still have the
|
||||
freedom to include or import the other type of configuration.
|
||||
|
||||
define either a set of `@Configuration` classes or a set of XML or Groovy resource
|
||||
locations from which your production `ApplicationContext` is loaded, but you still have
|
||||
the freedom to import or include the other type of configuration.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
[[testcontext-ctx-management-xml]]
|
||||
= Context Configuration with XML resources
|
||||
= Context Configuration with XML Resources
|
||||
|
||||
To load an `ApplicationContext` for your tests by using XML configuration files, annotate
|
||||
your test class with `@ContextConfiguration` and configure the `locations` attribute with
|
||||
|
||||
+22
-8
@@ -8,12 +8,15 @@ in JUnit and TestNG.
|
||||
[[testcontext-junit-jupiter-extension]]
|
||||
== SpringExtension for JUnit Jupiter
|
||||
|
||||
The Spring TestContext Framework offers full integration with the JUnit Jupiter testing
|
||||
framework, originally introduced in JUnit 5. By annotating test classes with
|
||||
`@ExtendWith(SpringExtension.class)`, you can implement standard JUnit Jupiter-based unit
|
||||
and integration tests and simultaneously reap the benefits of the TestContext framework,
|
||||
such as support for loading application contexts, dependency injection of test instances,
|
||||
transactional test method execution, and so on.
|
||||
The `SpringExtension` integrates the Spring TestContext Framework into the JUnit Jupiter
|
||||
testing framework.
|
||||
|
||||
NOTE: As of Spring Framework 7.0, the `SpringExtension` requires JUnit Jupiter 6.0 or higher.
|
||||
|
||||
By annotating test classes with `@ExtendWith(SpringExtension.class)`, you can implement
|
||||
standard JUnit Jupiter-based unit and integration tests and simultaneously reap the
|
||||
benefits of the TestContext framework, such as support for loading application contexts,
|
||||
dependency injection of test instances, transactional test method execution, and so on.
|
||||
|
||||
Furthermore, thanks to the rich extension API in JUnit Jupiter, Spring provides the
|
||||
following features above and beyond the feature set that Spring supports for JUnit 4 and
|
||||
@@ -22,7 +25,7 @@ TestNG:
|
||||
* Dependency injection for test constructors, test methods, and test lifecycle callback
|
||||
methods. See xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-di[Dependency
|
||||
Injection with the `SpringExtension`] for further details.
|
||||
* Powerful support for link:https://junit.org/junit5/docs/current/user-guide/#extensions-conditions[conditional
|
||||
* Powerful support for link:https://docs.junit.org/current/extensions/conditional-test-execution.html[conditional
|
||||
test execution] based on SpEL expressions, environment variables, system properties,
|
||||
and so on. See the documentation for `@EnabledIf` and `@DisabledIf` in
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc[Spring JUnit Jupiter Testing Annotations]
|
||||
@@ -160,7 +163,7 @@ for further details.
|
||||
=== Dependency Injection with the `SpringExtension`
|
||||
|
||||
The `SpringExtension` implements the
|
||||
link:https://junit.org/junit5/docs/current/user-guide/#extensions-parameter-resolution[`ParameterResolver`]
|
||||
link:https://docs.junit.org/current/extensions/parameter-resolution.html[`ParameterResolver`]
|
||||
extension API from JUnit Jupiter, which lets Spring provide dependency injection for test
|
||||
constructors, test methods, and test lifecycle callback methods.
|
||||
|
||||
@@ -389,6 +392,17 @@ any of its subclasses and nested classes. Thus, you may annotate a top-level tes
|
||||
with `@NestedTestConfiguration`, and that will apply to all of its nested test classes
|
||||
recursively.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
As of Spring Framework 7.0, the `SpringExtension` uses a test-method scoped
|
||||
`ExtensionContext` within `@Nested` test class hierarchies by default. However, the
|
||||
`SpringExtension` can be configured to use a test-class scoped `ExtensionContext`.
|
||||
|
||||
See the documentation for
|
||||
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-springextensionconfig[`@SpringExtensionConfig`]
|
||||
for details.
|
||||
====
|
||||
|
||||
[TIP]
|
||||
====
|
||||
If you are developing a component that integrates with the Spring TestContext Framework
|
||||
|
||||
@@ -362,7 +362,7 @@ of `PlatformTransactionManager` within the test's `ApplicationContext`, you can
|
||||
qualifier by using `@Transactional("myTxMgr")` or `@Transactional(transactionManager =
|
||||
"myTxMgr")`, or `TransactionManagementConfigurer` can be implemented by an
|
||||
`@Configuration` class. Consult the
|
||||
{spring-framework-api}/test/context/transaction/TestContextTransactionUtils.html#retrieveTransactionManager-org.springframework.test.context.TestContext-java.lang.String-[javadoc
|
||||
{spring-framework-api}/test/context/transaction/TestContextTransactionUtils.html#retrieveTransactionManager(org.springframework.test.context.TestContext,java.lang.String)[javadoc
|
||||
for `TestContextTransactionUtils.retrieveTransactionManager()`] for details on the
|
||||
algorithm used to look up a transaction manager in the test's `ApplicationContext`.
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ infrastructure and controller declarations and use it to handle requests via moc
|
||||
and response objects, without a running server.
|
||||
|
||||
For WebFlux, use the following where the Spring `ApplicationContext` is passed to
|
||||
{spring-framework-api}/web/server/adapter/WebHttpHandlerBuilder.html#applicationContext-org.springframework.context.ApplicationContext-[WebHttpHandlerBuilder]
|
||||
{spring-framework-api}/web/server/adapter/WebHttpHandlerBuilder.html#applicationContext(org.springframework.context.ApplicationContext)[WebHttpHandlerBuilder]
|
||||
to create the xref:web/webflux/reactive-spring.adoc#webflux-web-handler-api[WebHandler chain] to handle
|
||||
requests:
|
||||
|
||||
|
||||
@@ -1,186 +0,0 @@
|
||||
[[web-integration]]
|
||||
= Other Web Frameworks
|
||||
|
||||
This chapter details Spring's integration with third-party web frameworks.
|
||||
|
||||
One of the core value propositions of the Spring Framework is that of enabling
|
||||
_choice_. In a general sense, Spring does not force you to use or buy into any
|
||||
particular architecture, technology, or methodology (although it certainly recommends
|
||||
some over others). This freedom to pick and choose the architecture, technology, or
|
||||
methodology that is most relevant to a developer and their development team is
|
||||
arguably most evident in the web area, where Spring provides its own web frameworks
|
||||
(xref:web/webmvc.adoc#mvc[Spring MVC] and xref:web/webflux.adoc#webflux[Spring WebFlux])
|
||||
while, at the same time, supporting integration with a number of popular third-party
|
||||
web frameworks.
|
||||
|
||||
|
||||
[[web-integration-common]]
|
||||
== Common Configuration
|
||||
|
||||
Before diving into the integration specifics of each supported web framework, let us
|
||||
first take a look at common Spring configuration that is not specific to any one web
|
||||
framework. (This section is equally applicable to Spring's own web framework variants.)
|
||||
|
||||
One of the concepts (for want of a better word) espoused by Spring's lightweight
|
||||
application model is that of a layered architecture. Remember that in a "classic"
|
||||
layered architecture, the web layer is but one of many layers. It serves as one of the
|
||||
entry points into a server-side application, and it delegates to service objects
|
||||
(facades) that are defined in a service layer to satisfy business-specific (and
|
||||
presentation-technology agnostic) use cases. In Spring, these service objects, any other
|
||||
business-specific objects, data-access objects, and others exist in a distinct "business
|
||||
context", which contains no web or presentation layer objects (presentation objects,
|
||||
such as Spring MVC controllers, are typically configured in a distinct "presentation
|
||||
context"). This section details how you can configure a Spring container (a
|
||||
`WebApplicationContext`) that contains all of the 'business beans' in your application.
|
||||
|
||||
Moving on to specifics, all you need to do is declare a
|
||||
{spring-framework-api}/web/context/ContextLoaderListener.html[`ContextLoaderListener`]
|
||||
in the standard Jakarta EE servlet `web.xml` file of your web application and add a
|
||||
`contextConfigLocation` `<context-param/>` section (in the same file) that defines which
|
||||
set of Spring XML configuration files to load.
|
||||
|
||||
Consider the following `<listener/>` configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<listener>
|
||||
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
|
||||
</listener>
|
||||
----
|
||||
|
||||
Further consider the following `<context-param/>` configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<context-param>
|
||||
<param-name>contextConfigLocation</param-name>
|
||||
<param-value>/WEB-INF/applicationContext*.xml</param-value>
|
||||
</context-param>
|
||||
----
|
||||
|
||||
If you do not specify the `contextConfigLocation` context parameter, the
|
||||
`ContextLoaderListener` looks for a file called `/WEB-INF/applicationContext.xml` to
|
||||
load. Once the context files are loaded, Spring creates a
|
||||
{spring-framework-api}/web/context/WebApplicationContext.html[`WebApplicationContext`]
|
||||
object based on the bean definitions and stores it in the `ServletContext` of the web
|
||||
application.
|
||||
|
||||
All Java web frameworks are built on top of the Servlet API, so you can use the
|
||||
following code snippet to get access to this "business context" `ApplicationContext`
|
||||
created by the `ContextLoaderListener`.
|
||||
|
||||
The following example shows how to get the `WebApplicationContext`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);
|
||||
----
|
||||
|
||||
The
|
||||
{spring-framework-api}/web/context/support/WebApplicationContextUtils.html[`WebApplicationContextUtils`]
|
||||
class is for convenience, so you need not remember the name of the `ServletContext`
|
||||
attribute. Its `getWebApplicationContext()` method returns `null` if an object
|
||||
does not exist under the `WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE`
|
||||
key. Rather than risk getting `NullPointerExceptions` in your application, it is better
|
||||
to use the `getRequiredWebApplicationContext()` method. This method throws an exception
|
||||
when the `ApplicationContext` is missing.
|
||||
|
||||
Once you have a reference to the `WebApplicationContext`, you can retrieve beans by their
|
||||
name or type. Most developers retrieve beans by name and then cast them to one of their
|
||||
implemented interfaces.
|
||||
|
||||
Fortunately, most of the frameworks in this section have simpler ways of looking up beans.
|
||||
Not only do they make it easy to get beans from a Spring container, but they also let you
|
||||
use dependency injection on their controllers. Each web framework section has more detail
|
||||
on its specific integration strategies.
|
||||
|
||||
|
||||
[[jsf]]
|
||||
== JSF
|
||||
|
||||
JavaServer Faces (JSF) is the JCP's standard component-based, event-driven web
|
||||
user interface framework. It is an official part of the Jakarta EE umbrella but also
|
||||
individually usable, for example, through embedding Mojarra or MyFaces within Tomcat.
|
||||
|
||||
Please note that recent versions of JSF became closely tied to CDI infrastructure
|
||||
in application servers, with some new JSF functionality only working in such an
|
||||
environment. Spring's JSF support is not actively evolved anymore and primarily
|
||||
exists for migration purposes when modernizing older JSF-based applications.
|
||||
|
||||
The key element in Spring's JSF integration is the JSF `ELResolver` mechanism.
|
||||
|
||||
[[jsf-springbeanfaceselresolver]]
|
||||
=== Spring Bean Resolver
|
||||
|
||||
`SpringBeanFacesELResolver` is a JSF compliant `ELResolver` implementation,
|
||||
integrating with the standard Unified EL as used by JSF and JSP. It delegates to
|
||||
Spring's "business context" `WebApplicationContext` first and then to the
|
||||
default resolver of the underlying JSF implementation.
|
||||
|
||||
Configuration-wise, you can define `SpringBeanFacesELResolver` in your JSF
|
||||
`faces-context.xml` file, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<faces-config>
|
||||
<application>
|
||||
<el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
|
||||
...
|
||||
</application>
|
||||
</faces-config>
|
||||
----
|
||||
|
||||
[[jsf-facescontextutils]]
|
||||
=== Using `FacesContextUtils`
|
||||
|
||||
A custom `ELResolver` works well when mapping your properties to beans in
|
||||
`faces-config.xml`, but, at times, you may need to explicitly grab a bean.
|
||||
The {spring-framework-api}/web/jsf/FacesContextUtils.html[`FacesContextUtils`]
|
||||
class makes this easy. It is similar to `WebApplicationContextUtils`, except that
|
||||
it takes a `FacesContext` parameter rather than a `ServletContext` parameter.
|
||||
|
||||
The following example shows how to use `FacesContextUtils`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
ApplicationContext ctx = FacesContextUtils.getWebApplicationContext(FacesContext.getCurrentInstance());
|
||||
----
|
||||
|
||||
|
||||
[[struts]]
|
||||
== Apache Struts
|
||||
|
||||
Invented by Craig McClanahan, https://struts.apache.org[Struts] is an open-source project
|
||||
hosted by the Apache Software Foundation. Struts 1.x greatly simplified the
|
||||
JSP/Servlet programming paradigm and won over many developers who were using proprietary
|
||||
frameworks. It simplified the programming model; it was open source; and it had a large
|
||||
community, which let the project grow and become popular among Java web developers.
|
||||
|
||||
As a successor to the original Struts 1.x, check out Struts 2.x or more recent versions
|
||||
as well as the Struts-provided
|
||||
https://struts.apache.org/plugins/spring/[Spring Plugin] for built-in Spring integration.
|
||||
|
||||
|
||||
[[tapestry]]
|
||||
== Apache Tapestry
|
||||
|
||||
https://tapestry.apache.org/[Tapestry] is a "Component oriented framework for creating
|
||||
dynamic, robust, highly scalable web applications in Java."
|
||||
|
||||
While Spring has its own xref:web/webmvc.adoc#mvc[powerful web layer], there are a number of unique
|
||||
advantages to building an enterprise Java application by using a combination of Tapestry
|
||||
for the web user interface and the Spring container for the lower layers.
|
||||
|
||||
For more information, see Tapestry's dedicated
|
||||
https://tapestry.apache.org/integrating-with-spring-framework.html[integration module for Spring].
|
||||
|
||||
|
||||
[[web-integration-resources]]
|
||||
== Further Resources
|
||||
|
||||
The following links go to further resources about the various web frameworks described in
|
||||
this chapter.
|
||||
|
||||
* The https://www.oracle.com/java/technologies/javaserverfaces.html[JSF] homepage
|
||||
* The https://struts.apache.org/[Struts] homepage
|
||||
* The https://tapestry.apache.org/[Tapestry] homepage
|
||||
@@ -39,12 +39,12 @@ required CORS response headers set.
|
||||
|
||||
In order to enable cross-origin requests (that is, the `Origin` header is present and
|
||||
differs from the host of the request), you need to have some explicitly declared CORS
|
||||
configuration. If no matching CORS configuration is found, preflight requests are
|
||||
rejected. No CORS headers are added to the responses of simple and actual CORS requests
|
||||
and, consequently, browsers reject them.
|
||||
configuration. If no matching CORS configuration is found, no CORS headers are added to
|
||||
the responses to preflight, simple and actual CORS requests and, consequently, browsers
|
||||
reject them.
|
||||
|
||||
Each `HandlerMapping` can be
|
||||
{spring-framework-api}/web/reactive/handler/AbstractHandlerMapping.html#setCorsConfigurations-java.util.Map-[configured]
|
||||
{spring-framework-api}/web/reactive/handler/AbstractHandlerMapping.html#setCorsConfigurations(java.util.Map)[configured]
|
||||
individually with URL pattern-based `CorsConfiguration` mappings. In most cases, applications
|
||||
use the WebFlux Java configuration to declare such mappings, which results in a single,
|
||||
global map passed to all `HandlerMapping` implementations.
|
||||
@@ -57,7 +57,7 @@ class- or method-level `@CrossOrigin` annotations (other handlers can implement
|
||||
The rules for combining global and local configuration are generally additive -- for example,
|
||||
all global and all local origins. For those attributes where only a single value can be
|
||||
accepted, such as `allowCredentials` and `maxAge`, the local overrides the global value. See
|
||||
{spring-framework-api}/web/cors/CorsConfiguration.html#combine-org.springframework.web.cors.CorsConfiguration-[`CorsConfiguration#combine(CorsConfiguration)`]
|
||||
{spring-framework-api}/web/cors/CorsConfiguration.html#combine(org.springframework.web.cors.CorsConfiguration)[`CorsConfiguration#combine(CorsConfiguration)`]
|
||||
for more details.
|
||||
|
||||
[TIP]
|
||||
@@ -305,7 +305,6 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
@@ -328,7 +327,6 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
override fun addCorsMappings(registry: CorsRegistry) {
|
||||
|
||||
@@ -13,9 +13,9 @@ the same xref:web/webflux/reactive-spring.adoc[Reactive Core] foundation.
|
||||
== Overview
|
||||
[.small]#xref:web/webmvc-functional.adoc#webmvc-fn-overview[See equivalent in the Servlet stack]#
|
||||
|
||||
In WebFlux.fn, an HTTP request is handled with a `HandlerFunction`: a function that takes
|
||||
In WebFlux.fn, an HTTP request is handled with a `HandlerFunction`: a function that takes a
|
||||
`ServerRequest` and returns a delayed `ServerResponse` (i.e. `Mono<ServerResponse>`).
|
||||
Both the request and the response object have immutable contracts that offer JDK 8-friendly
|
||||
Both the request and the response object have immutable contracts that offer convenient
|
||||
access to the HTTP request and response.
|
||||
`HandlerFunction` is the equivalent of the body of a `@RequestMapping` method in the
|
||||
annotation-based programming model.
|
||||
@@ -117,13 +117,13 @@ Most applications can run through the WebFlux Java configuration, see xref:web/w
|
||||
== HandlerFunction
|
||||
[.small]#xref:web/webmvc-functional.adoc#webmvc-fn-handler-functions[See equivalent in the Servlet stack]#
|
||||
|
||||
`ServerRequest` and `ServerResponse` are immutable interfaces that offer JDK 8-friendly
|
||||
`ServerRequest` and `ServerResponse` are immutable interfaces that offer convenient
|
||||
access to the HTTP request and response.
|
||||
Both request and response provide {reactive-streams-site}[Reactive Streams] back pressure
|
||||
against the body streams.
|
||||
The request body is represented with a Reactor `Flux` or `Mono`.
|
||||
The response body is represented with any Reactive Streams `Publisher`, including `Flux` and `Mono`.
|
||||
For more on that, see xref:web-reactive.adoc#webflux-reactive-libraries[Reactive Libraries].
|
||||
For more on that, see xref:web/webflux-reactive-libraries.adoc[Reactive Libraries].
|
||||
|
||||
[[webflux-fn-request]]
|
||||
=== ServerRequest
|
||||
@@ -235,87 +235,14 @@ val map = request.awaitMultipartData()
|
||||
|
||||
The following example shows how to access multipart data, one at a time, in streaming fashion:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
Flux<PartEvent> allPartEvents = request.bodyToFlux(PartEvent.class);
|
||||
allPartsEvents.windowUntil(PartEvent::isLast)
|
||||
.concatMap(p -> p.switchOnFirst((signal, partEvents) -> {
|
||||
if (signal.hasValue()) {
|
||||
PartEvent event = signal.get();
|
||||
if (event instanceof FormPartEvent formEvent) {
|
||||
String value = formEvent.value();
|
||||
// handle form field
|
||||
}
|
||||
else if (event instanceof FilePartEvent fileEvent) {
|
||||
String filename = fileEvent.filename();
|
||||
Flux<DataBuffer> contents = partEvents.map(PartEvent::content);
|
||||
// handle file upload
|
||||
}
|
||||
else {
|
||||
return Mono.error(new RuntimeException("Unexpected event: " + event));
|
||||
}
|
||||
}
|
||||
else {
|
||||
return partEvents; // either complete or error signal
|
||||
}
|
||||
}));
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val parts = request.bodyToFlux<PartEvent>()
|
||||
allPartsEvents.windowUntil(PartEvent::isLast)
|
||||
.concatMap {
|
||||
it.switchOnFirst { signal, partEvents ->
|
||||
if (signal.hasValue()) {
|
||||
val event = signal.get()
|
||||
if (event is FormPartEvent) {
|
||||
val value: String = event.value();
|
||||
// handle form field
|
||||
} else if (event is FilePartEvent) {
|
||||
val filename: String = event.filename();
|
||||
val contents: Flux<DataBuffer> = partEvents.map(PartEvent::content);
|
||||
// handle file upload
|
||||
} else {
|
||||
return Mono.error(RuntimeException("Unexpected event: " + event));
|
||||
}
|
||||
} else {
|
||||
return partEvents; // either complete or error signal
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./PartEventHandler[tag=snippet,indent=0]
|
||||
|
||||
NOTE: The body contents of the `PartEvent` objects must be completely consumed, relayed, or released to avoid memory leaks.
|
||||
|
||||
The following shows how to bind request parameters, including an optional `DataBinder` customization:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java]
|
||||
----
|
||||
Pet pet = request.bind(Pet.class, dataBinder -> dataBinder.setAllowedFields("name"));
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin]
|
||||
----
|
||||
val pet = request.bind(Pet::class.java, {dataBinder -> dataBinder.setAllowedFields("name")})
|
||||
----
|
||||
======
|
||||
|
||||
The following shows how to bind request parameters, URI variables, or headers via `DataBinder`,
|
||||
and also shows how to customize the `DataBinder`:
|
||||
|
||||
include-code::./RequestHandler[tag=snippet,indent=0]
|
||||
|
||||
[[webflux-fn-response]]
|
||||
=== ServerResponse
|
||||
@@ -325,24 +252,7 @@ a `build` method to create it. You can use the builder to set the response statu
|
||||
headers, or to provide a body. The following example creates a 200 (OK) response with JSON
|
||||
content:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java]
|
||||
----
|
||||
Mono<Person> person = ...
|
||||
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person, Person.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin]
|
||||
----
|
||||
val person: Person = ...
|
||||
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(person)
|
||||
----
|
||||
======
|
||||
include-code::./ResponseHandler[tag=snippet,indent=0]
|
||||
|
||||
The following example shows how to build a 201 (CREATED) response with a `Location` header and no body:
|
||||
|
||||
@@ -353,7 +263,7 @@ Java::
|
||||
[source,java]
|
||||
----
|
||||
URI location = ...
|
||||
ServerResponse.created(location).build();
|
||||
return ServerResponse.created(location).build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
@@ -361,7 +271,7 @@ Kotlin::
|
||||
[source,kotlin]
|
||||
----
|
||||
val location: URI = ...
|
||||
ServerResponse.created(location).build()
|
||||
return ServerResponse.created(location).build()
|
||||
----
|
||||
======
|
||||
|
||||
@@ -374,14 +284,14 @@ Java::
|
||||
+
|
||||
[source,java]
|
||||
----
|
||||
ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView.class).body(...);
|
||||
return ServerResponse.ok().hint(JacksonCodecSupport.JSON_VIEW_HINT, MyJacksonView.class).body(...);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin]
|
||||
----
|
||||
ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView::class.java).body(...)
|
||||
return ServerResponse.ok().hint(JacksonCodecSupport.JSON_VIEW_HINT, MyJacksonView::class.java).body(...)
|
||||
----
|
||||
======
|
||||
|
||||
@@ -416,87 +326,7 @@ Therefore, it is useful to group related handler functions together into a handl
|
||||
has a similar role as `@Controller` in an annotation-based application.
|
||||
For example, the following class exposes a reactive `Person` repository:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON;
|
||||
import static org.springframework.web.reactive.function.server.ServerResponse.ok;
|
||||
|
||||
public class PersonHandler {
|
||||
|
||||
private final PersonRepository repository;
|
||||
|
||||
public PersonHandler(PersonRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public Mono<ServerResponse> listPeople(ServerRequest request) { // <1>
|
||||
Flux<Person> people = repository.allPeople();
|
||||
return ok().contentType(APPLICATION_JSON).body(people, Person.class);
|
||||
}
|
||||
|
||||
public Mono<ServerResponse> createPerson(ServerRequest request) { // <2>
|
||||
Mono<Person> person = request.bodyToMono(Person.class);
|
||||
return ok().build(repository.savePerson(person));
|
||||
}
|
||||
|
||||
public Mono<ServerResponse> getPerson(ServerRequest request) { // <3>
|
||||
int personId = Integer.valueOf(request.pathVariable("id"));
|
||||
return repository.getPerson(personId)
|
||||
.flatMap(person -> ok().contentType(APPLICATION_JSON).bodyValue(person))
|
||||
.switchIfEmpty(ServerResponse.notFound().build());
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> `listPeople` is a handler function that returns all `Person` objects found in the repository as
|
||||
JSON.
|
||||
<2> `createPerson` is a handler function that stores a new `Person` contained in the request body.
|
||||
Note that `PersonRepository.savePerson(Person)` returns `Mono<Void>`: an empty `Mono` that emits
|
||||
a completion signal when the person has been read from the request and stored. So we use the
|
||||
`build(Publisher<Void>)` method to send a response when that completion signal is received (that is,
|
||||
when the `Person` has been saved).
|
||||
<3> `getPerson` is a handler function that returns a single person, identified by the `id` path
|
||||
variable. We retrieve that `Person` from the repository and create a JSON response, if it is
|
||||
found. If it is not found, we use `switchIfEmpty(Mono<T>)` to return a 404 Not Found response.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
class PersonHandler(private val repository: PersonRepository) {
|
||||
|
||||
suspend fun listPeople(request: ServerRequest): ServerResponse { // <1>
|
||||
val people: Flow<Person> = repository.allPeople()
|
||||
return ok().contentType(APPLICATION_JSON).bodyAndAwait(people);
|
||||
}
|
||||
|
||||
suspend fun createPerson(request: ServerRequest): ServerResponse { // <2>
|
||||
val person = request.awaitBody<Person>()
|
||||
repository.savePerson(person)
|
||||
return ok().buildAndAwait()
|
||||
}
|
||||
|
||||
suspend fun getPerson(request: ServerRequest): ServerResponse { // <3>
|
||||
val personId = request.pathVariable("id").toInt()
|
||||
return repository.getPerson(personId)?.let { ok().contentType(APPLICATION_JSON).bodyValueAndAwait(it) }
|
||||
?: ServerResponse.notFound().buildAndAwait()
|
||||
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> `listPeople` is a handler function that returns all `Person` objects found in the repository as
|
||||
JSON.
|
||||
<2> `createPerson` is a handler function that stores a new `Person` contained in the request body.
|
||||
Note that `PersonRepository.savePerson(Person)` is a suspending function with no return type.
|
||||
<3> `getPerson` is a handler function that returns a single person, identified by the `id` path
|
||||
variable. We retrieve that `Person` from the repository and create a JSON response, if it is
|
||||
found. If it is not found, we return a 404 Not Found response.
|
||||
======
|
||||
--
|
||||
include-code::./PersonHandler[tag=snippet,indent=0]
|
||||
|
||||
[[webflux-fn-handler-validation]]
|
||||
=== Validation
|
||||
@@ -505,66 +335,7 @@ A functional endpoint can use Spring's xref:web/webmvc/mvc-config/validation.ado
|
||||
apply validation to the request body. For example, given a custom Spring
|
||||
xref:web/webmvc/mvc-config/validation.adoc[Validator] implementation for a `Person`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public class PersonHandler {
|
||||
|
||||
private final Validator validator = new PersonValidator(); // <1>
|
||||
|
||||
// ...
|
||||
|
||||
public Mono<ServerResponse> createPerson(ServerRequest request) {
|
||||
Mono<Person> person = request.bodyToMono(Person.class).doOnNext(this::validate); // <2>
|
||||
return ok().build(repository.savePerson(person));
|
||||
}
|
||||
|
||||
private void validate(Person person) {
|
||||
Errors errors = new BeanPropertyBindingResult(person, "person");
|
||||
validator.validate(person, errors);
|
||||
if (errors.hasErrors()) {
|
||||
throw new ServerWebInputException(errors.toString()); // <3>
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Create `Validator` instance.
|
||||
<2> Apply validation.
|
||||
<3> Raise exception for a 400 response.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
class PersonHandler(private val repository: PersonRepository) {
|
||||
|
||||
private val validator = PersonValidator() // <1>
|
||||
|
||||
// ...
|
||||
|
||||
suspend fun createPerson(request: ServerRequest): ServerResponse {
|
||||
val person = request.awaitBody<Person>()
|
||||
validate(person) // <2>
|
||||
repository.savePerson(person)
|
||||
return ok().buildAndAwait()
|
||||
}
|
||||
|
||||
private fun validate(person: Person) {
|
||||
val errors: Errors = BeanPropertyBindingResult(person, "person");
|
||||
validator.validate(person, errors);
|
||||
if (errors.hasErrors()) {
|
||||
throw ServerWebInputException(errors.toString()) // <3>
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Create `Validator` instance.
|
||||
<2> Apply validation.
|
||||
<3> Raise exception for a 400 response.
|
||||
======
|
||||
include-code::./PersonHandler[tag=snippet,indent=0]
|
||||
|
||||
Handlers can also use the standard bean validation API (JSR-303) by creating and injecting
|
||||
a global `Validator` instance based on `LocalValidatorFactoryBean`.
|
||||
@@ -602,28 +373,7 @@ path, headers, xref:#api-version[API version], and more.
|
||||
|
||||
The following example uses an `Accept` header, request predicate:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
RouterFunction<ServerResponse> route = RouterFunctions.route()
|
||||
.GET("/hello-world", accept(MediaType.TEXT_PLAIN),
|
||||
request -> ServerResponse.ok().bodyValue("Hello World")).build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val route = coRouter {
|
||||
GET("/hello-world", accept(TEXT_PLAIN)) {
|
||||
ServerResponse.ok().bodyValueAndAwait("Hello World")
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./RouterConfiguration[tag=snippet,indent=0]
|
||||
|
||||
You can compose multiple request predicates together by using:
|
||||
|
||||
@@ -658,61 +408,7 @@ There are also other ways to compose multiple router functions together:
|
||||
|
||||
The following example shows the composition of four routes:
|
||||
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON;
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
|
||||
|
||||
PersonRepository repository = ...
|
||||
PersonHandler handler = new PersonHandler(repository);
|
||||
|
||||
RouterFunction<ServerResponse> otherRoute = ...
|
||||
|
||||
RouterFunction<ServerResponse> route = route()
|
||||
.GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) // <1>
|
||||
.GET("/person", accept(APPLICATION_JSON), handler::listPeople) // <2>
|
||||
.POST("/person", handler::createPerson) // <3>
|
||||
.add(otherRoute) // <4>
|
||||
.build();
|
||||
----
|
||||
<1> pass:q[`GET /person/{id}`] with an `Accept` header that matches JSON is routed to
|
||||
`PersonHandler.getPerson`
|
||||
<2> `GET /person` with an `Accept` header that matches JSON is routed to
|
||||
`PersonHandler.listPeople`
|
||||
<3> `POST /person` with no additional predicates is mapped to
|
||||
`PersonHandler.createPerson`, and
|
||||
<4> `otherRoute` is a router function that is created elsewhere, and added to the route built.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import org.springframework.http.MediaType.APPLICATION_JSON
|
||||
|
||||
val repository: PersonRepository = ...
|
||||
val handler = PersonHandler(repository);
|
||||
|
||||
val otherRoute: RouterFunction<ServerResponse> = coRouter { }
|
||||
|
||||
val route = coRouter {
|
||||
GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) // <1>
|
||||
GET("/person", accept(APPLICATION_JSON), handler::listPeople) // <2>
|
||||
POST("/person", handler::createPerson) // <3>
|
||||
}.and(otherRoute) // <4>
|
||||
----
|
||||
<1> pass:q[`GET /person/{id}`] with an `Accept` header that matches JSON is routed to
|
||||
`PersonHandler.getPerson`
|
||||
<2> `GET /person` with an `Accept` header that matches JSON is routed to
|
||||
`PersonHandler.listPeople`
|
||||
<3> `POST /person` with no additional predicates is mapped to
|
||||
`PersonHandler.createPerson`, and
|
||||
<4> `otherRoute` is a router function that is created elsewhere, and added to the route built.
|
||||
======
|
||||
include-code::./RouterConfiguration[tag=snippet,indent=0]
|
||||
|
||||
[[nested-routes]]
|
||||
=== Nested Routes
|
||||
@@ -810,7 +506,7 @@ Java::
|
||||
----
|
||||
RouterFunction<ServerResponse> route = RouterFunctions.route()
|
||||
.GET("/hello-world", version("1.2"),
|
||||
request -> ServerResponse.ok().body("Hello World")).build();
|
||||
request -> ServerResponse.ok().bodyValue("Hello World")).build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
@@ -943,7 +639,6 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
@Bean
|
||||
@@ -980,7 +675,6 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
@Bean
|
||||
@@ -1048,9 +742,9 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val route = router {
|
||||
"/person".nest {
|
||||
("/person" and accept(APPLICATION_JSON)).nest {
|
||||
GET("/{id}", handler::getPerson)
|
||||
GET("", handler::listPeople)
|
||||
GET(handler::listPeople)
|
||||
before { // <1>
|
||||
ServerRequest.from(it)
|
||||
.header("X-RequestHeader", "Value").build()
|
||||
@@ -1077,54 +771,7 @@ Now we can add a simple security filter to our route, assuming that we have a `S
|
||||
can determine whether a particular path is allowed.
|
||||
The following example shows how to do so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
SecurityManager securityManager = ...
|
||||
|
||||
RouterFunction<ServerResponse> route = route()
|
||||
.path("/person", b1 -> b1
|
||||
.nest(accept(APPLICATION_JSON), b2 -> b2
|
||||
.GET("/{id}", handler::getPerson)
|
||||
.GET(handler::listPeople))
|
||||
.POST(handler::createPerson))
|
||||
.filter((request, next) -> {
|
||||
if (securityManager.allowAccessTo(request.path())) {
|
||||
return next.handle(request);
|
||||
}
|
||||
else {
|
||||
return ServerResponse.status(UNAUTHORIZED).build();
|
||||
}
|
||||
})
|
||||
.build();
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
val securityManager: SecurityManager = ...
|
||||
|
||||
val route = router {
|
||||
("/person" and accept(APPLICATION_JSON)).nest {
|
||||
GET("/{id}", handler::getPerson)
|
||||
GET("", handler::listPeople)
|
||||
POST(handler::createPerson)
|
||||
filter { request, next ->
|
||||
if (securityManager.allowAccessTo(request.path())) {
|
||||
next(request)
|
||||
}
|
||||
else {
|
||||
status(UNAUTHORIZED).build();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./RouterConfiguration[tag=snippet,indent=0]
|
||||
|
||||
The preceding example demonstrates that invoking the `next.handle(ServerRequest)` is optional.
|
||||
We only let the handler function be run when access is allowed.
|
||||
|
||||
@@ -49,10 +49,9 @@ This strategy resolves the API version from a request. The WebFlux config provid
|
||||
options to resolve from a header, query parameter, media type parameter,
|
||||
or from the URL path. You can also use a custom `ApiVersionResolver`.
|
||||
|
||||
NOTE: The path resolver always resolves the version from the specified path segment, or
|
||||
raises `InvalidApiVersionException` otherwise, and therefore it cannot yield to other
|
||||
resolvers.
|
||||
|
||||
The path resolver selects the version from a path segment specified by index, or
|
||||
raises `InvalidApiVersionException`, and therefore never results in `null` (no version)
|
||||
unless it is configured with a `Predicate<RequestPath>` to determine if a path is versioned.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -56,8 +56,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
public class WebConfiguration implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureViewResolvers(ViewResolverRegistry registry) {
|
||||
@@ -80,8 +79,7 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
class WebConfiguration : WebFluxConfigurer {
|
||||
|
||||
override fun configureViewResolvers(registry: ViewResolverRegistry) {
|
||||
registry.freeMarker()
|
||||
@@ -119,8 +117,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
public class WebConfiguration implements WebFluxConfigurer {
|
||||
|
||||
// ...
|
||||
|
||||
@@ -142,8 +139,7 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
class WebConfiguration : WebFluxConfigurer {
|
||||
|
||||
// ...
|
||||
|
||||
@@ -210,13 +206,8 @@ The following table shows the templating libraries that we have tested on differ
|
||||
[%header]
|
||||
|===
|
||||
|Scripting Library |Scripting Engine
|
||||
|https://handlebarsjs.com/[Handlebars] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|https://mustache.github.io/[Mustache] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|https://react.dev/[React] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|https://ejs.co/[EJS] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
||||
|https://docs.ruby-lang.org/en/master/ERB.html[ERB] |https://www.jruby.org[JRuby]
|
||||
|https://docs.python.org/2/library/string.html#template-strings[String templates] |https://www.jython.org/[Jython]
|
||||
|https://github.com/sdeleuze/kotlin-script-templating[Kotlin Script templating] |{kotlin-site}[Kotlin]
|
||||
|===
|
||||
|
||||
TIP: The basic rule for integrating any other script engine is that it must implement the
|
||||
@@ -228,17 +219,8 @@ TIP: The basic rule for integrating any other script engine is that it must impl
|
||||
|
||||
You need to have the script engine on your classpath, the details of which vary by script engine:
|
||||
|
||||
* The https://openjdk.java.net/projects/nashorn/[Nashorn] JavaScript engine is provided with
|
||||
Java 8+. Using the latest update release available is highly recommended.
|
||||
* https://www.jruby.org[JRuby] should be added as a dependency for Ruby support.
|
||||
* https://www.jython.org[Jython] should be added as a dependency for Python support.
|
||||
* `org.jetbrains.kotlin:kotlin-script-util` dependency and a `META-INF/services/javax.script.ScriptEngineFactory`
|
||||
file containing a `org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory`
|
||||
line should be added for Kotlin script support. See
|
||||
https://github.com/sdeleuze/kotlin-script-templating[this example] for more detail.
|
||||
|
||||
You need to have the script templating library. One way to do that for JavaScript is
|
||||
through https://www.webjars.org/[WebJars].
|
||||
|
||||
[[webflux-view-script-integrate]]
|
||||
=== Script Templates
|
||||
@@ -246,7 +228,7 @@ through https://www.webjars.org/[WebJars].
|
||||
|
||||
You can declare a `ScriptTemplateConfigurer` bean to specify the script engine to use,
|
||||
the script files to load, what function to call to render templates, and so on.
|
||||
The following example uses Mustache templates and the Nashorn JavaScript engine:
|
||||
The following example uses the Jython Python engine:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -255,8 +237,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
public class WebConfiguration implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureViewResolvers(ViewResolverRegistry registry) {
|
||||
@@ -266,9 +247,8 @@ Java::
|
||||
@Bean
|
||||
public ScriptTemplateConfigurer configurer() {
|
||||
ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
|
||||
configurer.setEngineName("nashorn");
|
||||
configurer.setScripts("mustache.js");
|
||||
configurer.setRenderObject("Mustache");
|
||||
configurer.setEngineName("jython");
|
||||
configurer.setScripts("render.py");
|
||||
configurer.setRenderFunction("render");
|
||||
return configurer;
|
||||
}
|
||||
@@ -280,8 +260,7 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
class WebConfiguration : WebFluxConfigurer {
|
||||
|
||||
override fun configureViewResolvers(registry: ViewResolverRegistry) {
|
||||
registry.scriptTemplate()
|
||||
@@ -289,9 +268,8 @@ Kotlin::
|
||||
|
||||
@Bean
|
||||
fun configurer() = ScriptTemplateConfigurer().apply {
|
||||
engineName = "nashorn"
|
||||
setScripts("mustache.js")
|
||||
renderObject = "Mustache"
|
||||
engineName = "jython"
|
||||
setScripts("render.py")
|
||||
renderFunction = "render"
|
||||
}
|
||||
}
|
||||
@@ -305,94 +283,7 @@ The `render` function is called with the following parameters:
|
||||
* `RenderingContext renderingContext`: The
|
||||
{spring-framework-api}/web/servlet/view/script/RenderingContext.html[`RenderingContext`]
|
||||
that gives access to the application context, the locale, the template loader, and the
|
||||
URL (since 5.0)
|
||||
|
||||
`Mustache.render()` is natively compatible with this signature, so you can call it directly.
|
||||
|
||||
If your templating technology requires some customization, you can provide a script that
|
||||
implements a custom render function. For example, https://handlebarsjs.com[Handlerbars]
|
||||
needs to compile templates before using them and requires a
|
||||
https://en.wikipedia.org/wiki/Polyfill[polyfill] in order to emulate some
|
||||
browser facilities not available in the server-side script engine.
|
||||
The following example shows how to set a custom render function:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureViewResolvers(ViewResolverRegistry registry) {
|
||||
registry.scriptTemplate();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ScriptTemplateConfigurer configurer() {
|
||||
ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
|
||||
configurer.setEngineName("nashorn");
|
||||
configurer.setScripts("polyfill.js", "handlebars.js", "render.js");
|
||||
configurer.setRenderFunction("render");
|
||||
configurer.setSharedEngine(false);
|
||||
return configurer;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
override fun configureViewResolvers(registry: ViewResolverRegistry) {
|
||||
registry.scriptTemplate()
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun configurer() = ScriptTemplateConfigurer().apply {
|
||||
engineName = "nashorn"
|
||||
setScripts("polyfill.js", "handlebars.js", "render.js")
|
||||
renderFunction = "render"
|
||||
isSharedEngine = false
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: Setting the `sharedEngine` property to `false` is required when using non-thread-safe
|
||||
script engines with templating libraries not designed for concurrency, such as Handlebars or
|
||||
React running on Nashorn. In that case, Java SE 8 update 60 is required, due to
|
||||
https://bugs.openjdk.java.net/browse/JDK-8076099[this bug], but it is generally
|
||||
recommended to use a recent Java SE patch release in any case.
|
||||
|
||||
`polyfill.js` defines only the `window` object needed by Handlebars to run properly,
|
||||
as the following snippet shows:
|
||||
|
||||
[source,javascript,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
var window = {};
|
||||
----
|
||||
|
||||
This basic `render.js` implementation compiles the template before using it. A production
|
||||
ready implementation should also store and reused cached templates or pre-compiled templates.
|
||||
This can be done on the script side, as well as any customization you need (managing
|
||||
template engine configuration for example).
|
||||
The following example shows how compile a template:
|
||||
|
||||
[source,javascript,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
function render(template, model) {
|
||||
var compiledTemplate = Handlebars.compile(template);
|
||||
return compiledTemplate(model);
|
||||
}
|
||||
----
|
||||
URL
|
||||
|
||||
Check out the Spring Framework unit tests,
|
||||
{spring-framework-code}/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/script[Java], and
|
||||
@@ -482,7 +373,7 @@ purposes, it is useful to be able to alternate between rendering a model with an
|
||||
or as other formats (such as JSON or XML), depending on the content type requested by the client.
|
||||
To support doing so, Spring WebFlux provides the `HttpMessageWriterView`, which you can use to
|
||||
plug in any of the available xref:web/webflux/reactive-spring.adoc#webflux-codecs[Codecs] from
|
||||
`spring-web`, such as `Jackson2JsonEncoder`, `Jackson2SmileEncoder`, or `Jaxb2XmlEncoder`.
|
||||
`spring-web`, such as `JacksonJsonEncoder`, `JacksonSmileEncoder`, or `Jaxb2XmlEncoder`.
|
||||
|
||||
Unlike other view technologies, `HttpMessageWriterView` does not require a `ViewResolver` but is
|
||||
instead xref:web/webflux/config.adoc#webflux-config-view-resolvers[configured] as a default view.
|
||||
|
||||
@@ -18,8 +18,8 @@ You can also use `WebClient.builder()` with further options:
|
||||
* `exchangeStrategies`: HTTP message reader/writer customizations.
|
||||
* `clientConnector`: HTTP client library settings.
|
||||
* `apiVersionInserter`: to insert API version values in the request
|
||||
* `observationRegistry`: the registry to use for enabling xref:integration/observability.adoc#http-client.webclient[Observability support].
|
||||
* `observationConvention`: xref:integration/observability.adoc#config[an optional, custom convention to extract metadata] for recorded observations.
|
||||
* `observationRegistry`: the registry to use for enabling xref:integration/observability.adoc#observability.http-client.webclient[Observability support].
|
||||
* `observationConvention`: xref:integration/observability.adoc#observability.config[an optional, custom convention to extract metadata] for recorded observations.
|
||||
|
||||
For example:
|
||||
|
||||
|
||||
@@ -32,9 +32,9 @@ any `@RequestMapping` method to render an RFC 9457 response. This is processed a
|
||||
- The `status` property of `ProblemDetail` determines the HTTP status.
|
||||
- The `instance` property of `ProblemDetail` is set from the current URL path, if not
|
||||
already set.
|
||||
- For content negotiation, the Jackson `HttpMessageConverter` prefers
|
||||
"application/problem+json" over "application/json" when rendering a `ProblemDetail`,
|
||||
and also falls back on it if no compatible media type is found.
|
||||
- The Jackson JSON and XML message converters use "application/problem+json" or
|
||||
"application/problem+xml" respectively as the producible media types for `ProblemDetail`
|
||||
to ensure they are favored for content negotiation.
|
||||
|
||||
To enable RFC 9457 responses for Spring WebFlux exceptions and for any
|
||||
`ErrorResponseException`, extend `ResponseEntityExceptionHandler` and declare it as an
|
||||
@@ -66,6 +66,13 @@ from an existing `ProblemDetail`. This could be done centrally, for example, fro
|
||||
`@ControllerAdvice` such as `ResponseEntityExceptionHandler` that re-creates the
|
||||
`ProblemDetail` of an exception into a subclass with the additional non-standard fields.
|
||||
|
||||
TIP: In Spring Boot, the `spring.webflux.problemdetails.enabled` property autoconfigures
|
||||
a `ResponseEntityExceptionHandler` that handles built-in exceptions with problem details.
|
||||
In that case, you may prefer to create another `@ControllerAdvice` instead of extending
|
||||
`ResponseEntityExceptionHandler` if you want to take over the handling of a specific
|
||||
built-in exception. You'll need to ensure your handler is ordered ahead of the one
|
||||
configured by Spring Boot whose order is 0.
|
||||
|
||||
|
||||
[[webflux-ann-rest-exceptions-i18n]]
|
||||
== Customization and i18n
|
||||
@@ -135,6 +142,10 @@ Message codes and arguments for each error are also resolved via `MessageSource`
|
||||
| `+{0}+` the list of global errors, `+{1}+` the list of field errors.
|
||||
Message codes and arguments for each error are also resolved via `MessageSource`.
|
||||
|
||||
| `NoResourceFoundException`
|
||||
| (default)
|
||||
| `+{0}+` the request path (or portion of) used to find a resource
|
||||
|
||||
|===
|
||||
|
||||
NOTE: Unlike other exceptions, the message arguments for
|
||||
|
||||
@@ -251,7 +251,8 @@ Kotlin::
|
||||
======
|
||||
|
||||
TIP: If you need to have a `LocalValidatorFactoryBean` injected somewhere, create a bean and
|
||||
mark it with `@Primary` in order to avoid conflict with the one declared in the MVC config.
|
||||
mark it with `@Primary`, or mark the one declared in the MVC config with `@Fallback`, in
|
||||
order to avoid conflict.
|
||||
|
||||
|
||||
[[webflux-config-content-negotiation]]
|
||||
@@ -334,19 +335,8 @@ Kotlin::
|
||||
`ServerCodecConfigurer` provides a set of default readers and writers. You can use it to add
|
||||
more readers and writers, customize the default ones, or replace the default ones completely.
|
||||
|
||||
For Jackson JSON and XML, consider using
|
||||
{spring-framework-api}/http/converter/json/Jackson2ObjectMapperBuilder.html[`Jackson2ObjectMapperBuilder`],
|
||||
which customizes Jackson's default properties with the following ones:
|
||||
|
||||
* {jackson-docs}/jackson-databind/javadoc/2.6/com/fasterxml/jackson/databind/DeserializationFeature.html#FAIL_ON_UNKNOWN_PROPERTIES[`DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES`] is disabled.
|
||||
* {jackson-docs}/jackson-databind/javadoc/2.6/com/fasterxml/jackson/databind/MapperFeature.html#DEFAULT_VIEW_INCLUSION[`MapperFeature.DEFAULT_VIEW_INCLUSION`] is disabled.
|
||||
|
||||
It also automatically registers the following well-known modules if they are detected on the classpath:
|
||||
|
||||
* {jackson-github-org}/jackson-datatype-jsr310[`jackson-datatype-jsr310`]: Support for Java 8 Date and Time API types.
|
||||
* {jackson-github-org}/jackson-datatype-jdk8[`jackson-datatype-jdk8`]: Support for other Java 8 types, such as `Optional`.
|
||||
* {jackson-github-org}/jackson-module-kotlin[`jackson-module-kotlin`]: Support for Kotlin classes and data classes.
|
||||
|
||||
For Jackson, consider using a Jackson format-specific builder like `JsonMapper.Builder` to configure Jackson's default
|
||||
properties.
|
||||
|
||||
[[webflux-config-view-resolvers]]
|
||||
== View Resolvers
|
||||
@@ -489,7 +479,7 @@ Java::
|
||||
public void configureViewResolvers(ViewResolverRegistry registry) {
|
||||
registry.freeMarker();
|
||||
|
||||
Jackson2JsonEncoder encoder = new Jackson2JsonEncoder();
|
||||
JacksonJsonEncoder encoder = new JacksonJsonEncoder();
|
||||
registry.defaultViews(new HttpMessageWriterView(encoder));
|
||||
}
|
||||
|
||||
@@ -508,7 +498,7 @@ Kotlin::
|
||||
override fun configureViewResolvers(registry: ViewResolverRegistry) {
|
||||
registry.freeMarker()
|
||||
|
||||
val encoder = Jackson2JsonEncoder()
|
||||
val encoder = JacksonJsonEncoder()
|
||||
registry.defaultViews(HttpMessageWriterView(encoder))
|
||||
}
|
||||
|
||||
@@ -701,7 +691,7 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim"]
|
||||
----
|
||||
@Configuration
|
||||
class WebConfiguration : WebMvcConfigurer {
|
||||
class WebConfiguration : WebFluxConfigurer {
|
||||
|
||||
override fun configureApiVersioning(configurer: ApiVersionConfigurer) {
|
||||
configurer.useRequestHeader("API-Version")
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
The following table shows the supported controller method arguments.
|
||||
|
||||
Reactive types (Reactor, RxJava, xref:web-reactive.adoc#webflux-reactive-libraries[or other]) are
|
||||
Reactive types (Reactor, RxJava, xref:web/webflux-reactive-libraries.adoc[or other]) are
|
||||
supported on arguments that require blocking I/O (for example, reading the request body) to
|
||||
be resolved. This is marked in the Description column. Reactive types are not expected
|
||||
on arguments that do not require blocking.
|
||||
@@ -114,6 +114,6 @@ and others) and is equivalent to `required=false`.
|
||||
| Any other argument
|
||||
| If a method argument is not matched to any of the above, it is, by default, resolved as
|
||||
a `@RequestParam` if it is a simple type, as determined by
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty],
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty],
|
||||
or as a `@ModelAttribute`, otherwise.
|
||||
|===
|
||||
|
||||
+1
-1
@@ -205,7 +205,7 @@ controller method xref:web/webmvc/mvc-controller/ann-validation.adoc[Validation]
|
||||
|
||||
TIP: Using `@ModelAttribute` is optional. By default, any argument that is not a simple
|
||||
value type as determined by
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty]
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty]
|
||||
_AND_ that is not resolved by any other argument resolver is treated as an implicit `@ModelAttribute`.
|
||||
|
||||
WARNING: When compiling to a native image with GraalVM, the implicit `@ModelAttribute`
|
||||
|
||||
+8
-88
@@ -41,8 +41,8 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
class MyForm(
|
||||
val name: String,
|
||||
val file: FilePart)
|
||||
private val name: String,
|
||||
private val file: FilePart)
|
||||
|
||||
@Controller
|
||||
class FileUploadController {
|
||||
@@ -103,8 +103,8 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@PostMapping("/")
|
||||
fun handle(@RequestPart("meta-data") Part metadata, // <1>
|
||||
@RequestPart("file-data") FilePart file): String { // <2>
|
||||
fun handle(@RequestPart("meta-data") metadata: Part, // <1>
|
||||
@RequestPart("file-data") file: FilePart): String { // <2>
|
||||
// ...
|
||||
}
|
||||
----
|
||||
@@ -169,8 +169,8 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@PostMapping("/")
|
||||
fun handle(@Valid @RequestPart("meta-data") metadata: MetaData): String {
|
||||
// ...
|
||||
fun handle(@Valid @RequestPart("meta-data") metadata: Mono<MetaData>): String {
|
||||
// use one of the onError* operators...
|
||||
}
|
||||
----
|
||||
======
|
||||
@@ -202,7 +202,7 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@PostMapping("/")
|
||||
fun handle(@RequestBody parts: MultiValueMap<String, Part>): String { // <1>
|
||||
fun handle(@RequestBody parts: Mono<MultiValueMap<String, Part>>): String { // <1>
|
||||
// ...
|
||||
}
|
||||
----
|
||||
@@ -227,87 +227,7 @@ when uploading. If the file is large enough to be split across multiple buffers,
|
||||
|
||||
For example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@PostMapping("/")
|
||||
public void handle(@RequestBody Flux<PartEvent> allPartsEvents) { <1>
|
||||
allPartsEvents.windowUntil(PartEvent::isLast) <2>
|
||||
.concatMap(p -> p.switchOnFirst((signal, partEvents) -> { <3>
|
||||
if (signal.hasValue()) {
|
||||
PartEvent event = signal.get();
|
||||
if (event instanceof FormPartEvent formEvent) { <4>
|
||||
String value = formEvent.value();
|
||||
// handle form field
|
||||
}
|
||||
else if (event instanceof FilePartEvent fileEvent) { <5>
|
||||
String filename = fileEvent.filename();
|
||||
Flux<DataBuffer> contents = partEvents.map(PartEvent::content); <6>
|
||||
// handle file upload
|
||||
}
|
||||
else {
|
||||
return Mono.error(new RuntimeException("Unexpected event: " + event));
|
||||
}
|
||||
}
|
||||
else {
|
||||
return partEvents; // either complete or error signal
|
||||
}
|
||||
}));
|
||||
}
|
||||
----
|
||||
<1> Using `@RequestBody`.
|
||||
<2> The final `PartEvent` for a particular part will have `isLast()` set to `true`, and can be
|
||||
followed by additional events belonging to subsequent parts.
|
||||
This makes the `isLast` property suitable as a predicate for the `Flux::windowUntil` operator, to
|
||||
split events from all parts into windows that each belong to a single part.
|
||||
<3> The `Flux::switchOnFirst` operator allows you to see whether you are handling a form field or
|
||||
file upload.
|
||||
<4> Handling the form field.
|
||||
<5> Handling the file upload.
|
||||
<6> The body contents must be completely consumed, relayed, or released to avoid memory leaks.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@PostMapping("/")
|
||||
fun handle(@RequestBody allPartsEvents: Flux<PartEvent>) = { // <1>
|
||||
allPartsEvents.windowUntil(PartEvent::isLast) <2>
|
||||
.concatMap {
|
||||
it.switchOnFirst { signal, partEvents -> <3>
|
||||
if (signal.hasValue()) {
|
||||
val event = signal.get()
|
||||
if (event is FormPartEvent) { <4>
|
||||
val value: String = event.value();
|
||||
// handle form field
|
||||
} else if (event is FilePartEvent) { <5>
|
||||
val filename: String = event.filename();
|
||||
val contents: Flux<DataBuffer> = partEvents.map(PartEvent::content); <6>
|
||||
// handle file upload
|
||||
} else {
|
||||
return Mono.error(RuntimeException("Unexpected event: " + event));
|
||||
}
|
||||
} else {
|
||||
return partEvents; // either complete or error signal
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Using `@RequestBody`.
|
||||
<2> The final `PartEvent` for a particular part will have `isLast()` set to `true`, and can be
|
||||
followed by additional events belonging to subsequent parts.
|
||||
This makes the `isLast` property suitable as a predicate for the `Flux::windowUntil` operator, to
|
||||
split events from all parts into windows that each belong to a single part.
|
||||
<3> The `Flux::switchOnFirst` operator allows you to see whether you are handling a form field or
|
||||
file upload.
|
||||
<4> Handling the form field.
|
||||
<5> Handling the file upload.
|
||||
<6> The body contents must be completely consumed, relayed, or released to avoid memory leaks.
|
||||
======
|
||||
include-code::./PartEventController[tag=snippet,indent=0]
|
||||
|
||||
Received part events can also be relayed to another service by using the `WebClient`.
|
||||
See xref:web/webflux-webclient/client-body.adoc#webflux-client-body-multipart[Multipart Data].
|
||||
|
||||
+1
-1
@@ -74,6 +74,6 @@ When a `@RequestParam` annotation is declared on a `Map<String, String>` or
|
||||
|
||||
Note that use of `@RequestParam` is optional -- for example, to set its attributes. By
|
||||
default, any argument that is a simple value type (as determined by
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty])
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty])
|
||||
and is not resolved by any other argument resolver is treated as if it were annotated
|
||||
with `@RequestParam`.
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
WebFlux supports using a single value xref:web-reactive.adoc#webflux-reactive-libraries[reactive type] to
|
||||
WebFlux supports using a single value xref:web/webflux-reactive-libraries.adoc[reactive type] to
|
||||
produce the `ResponseEntity` asynchronously, and/or single and multi-value reactive types
|
||||
for the body. This allows a variety of async responses with `ResponseEntity` as follows:
|
||||
|
||||
|
||||
+1
-1
@@ -87,6 +87,6 @@ Reactor provides a dedicated operator for that, `Flux#collectList()`.
|
||||
| Other return values
|
||||
| If a return value remains unresolved in any other way, it is treated as a model
|
||||
attribute, unless it is a simple type as determined by
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty],
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty],
|
||||
in which case it remains unresolved.
|
||||
|===
|
||||
|
||||
@@ -393,9 +393,10 @@ is used to map requests.
|
||||
Once API versioning is enabled, you can begin to map requests with versions.
|
||||
The `@RequestMapping` `version` attribute supports the following:
|
||||
|
||||
- No value -- matches any version
|
||||
- Fixed version ("1.2") -- matches the given version only
|
||||
- Baseline version ("1.2+") -- matches the given version and above
|
||||
- Baseline version ("1.2+") -- matches the given and
|
||||
xref:web/webflux/config.adoc#webflux-config-api-version[supported versions] above
|
||||
- No value -- matches any version, but is superseded by a more specific version match
|
||||
|
||||
If multiple controller methods have a version less than or equal to the request version,
|
||||
the highest of those, and closest to the request version, is the one considered,
|
||||
@@ -443,6 +444,9 @@ For request with version `"1.3"`:
|
||||
- (3) matches as it matches 1.2 and above, and is *chosen* as the highest match
|
||||
- (4) is higher and does not match
|
||||
|
||||
NOTE: Version 1.3 must be present in the mappings, or be
|
||||
xref:web/webflux/config.adoc#webflux-config-api-version[configured as supported].
|
||||
|
||||
For request with version `"1.5"`:
|
||||
|
||||
- (1) matches as it matches any version
|
||||
@@ -454,9 +458,10 @@ A request with version `"1.6"` does not have a match. (1) and (3) do match, but
|
||||
superseded by (4), which allows only a strict match, and therefore does not match.
|
||||
In this scenario, a `NotAcceptableApiVersionException` results in a 400 response.
|
||||
|
||||
NOTE: The above assumes the request version is a
|
||||
xref:web/webflux/config.adoc#webflux-config-api-version["supported" version],
|
||||
or otherwise it would fail.
|
||||
Controller methods without a version are intended to support clients created before a
|
||||
versioned alternative was introduced. Therefore, even though an unversioned controller
|
||||
method is considered a match for any version, it is in fact given the lowest priority,
|
||||
and is effectively superseded by any alternative controller method with a version.
|
||||
|
||||
See xref:web/webflux-versioning.adoc[API Versioning] for more details on underlying
|
||||
infrastructure and support for API Versioning.
|
||||
|
||||
@@ -207,7 +207,7 @@ was not provided (for example, model attribute was returned) or an async return
|
||||
view resolution scenarios. Explore the options in your IDE with code completion.
|
||||
* `Model`, `Map`: Extra model attributes to be added to the model for the request.
|
||||
* Any other: Any other return value (except for simple types, as determined by
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty])
|
||||
{spring-framework-api}/beans/BeanUtils.html#isSimpleProperty(java.lang.Class)[BeanUtils#isSimpleProperty])
|
||||
is treated as a model attribute to be added to the model. The attribute name is derived
|
||||
from the class name by using {spring-framework-api}/core/Conventions.html[conventions],
|
||||
unless a handler method `@ModelAttribute` annotation is present.
|
||||
|
||||
@@ -58,7 +58,7 @@ If a publisher cannot slow down, it has to decide whether to buffer, drop, or fa
|
||||
Reactive Streams plays an important role for interoperability. It is of interest to libraries
|
||||
and infrastructure components but less useful as an application API, because it is too
|
||||
low-level. Applications need a higher-level and richer, functional API to
|
||||
compose async logic -- similar to the Java 8 `Stream` API but not only for collections.
|
||||
compose async logic -- similar to the Java `Stream` API but not only for collections.
|
||||
This is the role that reactive libraries play.
|
||||
|
||||
{reactor-github-org}/reactor[Reactor] is the reactive library of choice for
|
||||
@@ -77,7 +77,7 @@ as input, adapts it to a Reactor type internally, uses that, and returns either
|
||||
`Flux` or a `Mono` as output. So, you can pass any `Publisher` as input and you can apply
|
||||
operations on the output, but you need to adapt the output for use with another reactive library.
|
||||
Whenever feasible (for example, annotated controllers), WebFlux adapts transparently to the use
|
||||
of RxJava or another reactive library. See xref:web-reactive.adoc#webflux-reactive-libraries[Reactive Libraries] for more details.
|
||||
of RxJava or another reactive library. See xref:web/webflux-reactive-libraries.adoc[Reactive Libraries] for more details.
|
||||
|
||||
NOTE: In addition to Reactive APIs, WebFlux can also be used with
|
||||
xref:languages/kotlin/coroutines.adoc[Coroutines] APIs in Kotlin which provides a more imperative style of programming.
|
||||
@@ -131,7 +131,7 @@ execution model benefits as others in this space and also provides a choice of s
|
||||
(annotated controllers and functional web endpoints), and a choice of reactive libraries
|
||||
(Reactor, RxJava, or other).
|
||||
|
||||
* If you are interested in a lightweight, functional web framework for use with Java 8 lambdas
|
||||
* If you are interested in a lightweight, functional web framework for use with Java
|
||||
or Kotlin, you can use the Spring WebFlux functional web endpoints. That can also be a good choice
|
||||
for smaller applications or microservices with less complex requirements that can benefit
|
||||
from greater transparency and control.
|
||||
@@ -148,7 +148,7 @@ RxJava to perform blocking calls on a separate thread but you would not be makin
|
||||
most of a non-blocking web stack.
|
||||
|
||||
* If you have a Spring MVC application with calls to remote services, try the reactive `WebClient`.
|
||||
You can return reactive types (Reactor, RxJava, xref:web-reactive.adoc#webflux-reactive-libraries[or other])
|
||||
You can return reactive types (Reactor, RxJava, xref:web/webflux-reactive-libraries.adoc[or other])
|
||||
directly from Spring MVC controller methods. The greater the latency per call or the
|
||||
interdependency among calls, the more dramatic the benefits. Spring MVC controllers
|
||||
can call other reactive components too.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user