mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
526 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b4355d9e80 | |||
| e64243d8a1 | |||
| 1109892134 | |||
| 6868ff7dbe | |||
| a8be11ebaf | |||
| 9bc7229a1c | |||
| dfc10c1a8d | |||
| 4d6947d14f | |||
| bbb593db48 | |||
| 8df21109f2 | |||
| b2134ee71f | |||
| f42e886f03 | |||
| de4db3812a | |||
| 440a259b71 | |||
| 9eae0b6f6f | |||
| 6174055910 | |||
| 8a53525209 | |||
| e31ce359a1 | |||
| 1fd6ded7a0 | |||
| 667004e5fa | |||
| ba74de997a | |||
| 011e398355 | |||
| 295a9565a3 | |||
| b07ff1c2d4 | |||
| b336bbe539 | |||
| bb33a3efa3 | |||
| 3956a36837 | |||
| 056757b493 | |||
| 79c5fec1be | |||
| 5c09435816 | |||
| 7271358ea4 | |||
| 689782cbad | |||
| 39bd530461 | |||
| dce3f71b7d | |||
| 2df90e32c0 | |||
| f53da04717 | |||
| 7077809561 | |||
| 6ded25ba28 | |||
| 124b38450b | |||
| e78e802647 | |||
| 18e31fcfba | |||
| b07217ab67 | |||
| aa7e84c89f | |||
| 722701a451 | |||
| bd1c7b379f | |||
| 6af19244a5 | |||
| 328dd71f6e | |||
| e9d16da633 | |||
| bae12e739d | |||
| d04883f839 | |||
| 9f55296049 | |||
| 4591a67641 | |||
| 85855ec793 | |||
| 9797bc0acd | |||
| 9d3374b28d | |||
| 09086fc648 | |||
| 128e90064e | |||
| 2f6d142ad7 | |||
| 16e3973118 | |||
| 5c37d07ad3 | |||
| 12891d1975 | |||
| 7557967f9e | |||
| 4ba14ca58c | |||
| 1ca941ba9a | |||
| b78d371746 | |||
| 17a94fb110 | |||
| 2fcae65853 | |||
| b0a1a11612 | |||
| 7a0fe7d14f | |||
| ccdaed594e | |||
| c41b0140cd | |||
| ceffda7874 | |||
| 49f9b40fba | |||
| e49d2da443 | |||
| e6f2f86f9f | |||
| d59991fcc9 | |||
| cf46f391d7 | |||
| 1a29fbda48 | |||
| 6d63abd4e9 | |||
| 2b4c7d09b0 | |||
| e81fcc34de | |||
| 61138698c6 | |||
| 11a1858e79 | |||
| 3548e872b9 | |||
| 68fcf81c4d | |||
| 305686dbf7 | |||
| 3fff3b8a6d | |||
| ace2f0a3e5 | |||
| 1a573d6e3c | |||
| bd9ae6bd2a | |||
| bb7a8006c5 | |||
| a09f454dfc | |||
| ba56c1a8f4 | |||
| cf1d274baa | |||
| 9107f7b592 | |||
| 55a090602b | |||
| 1d7cb4fc13 | |||
| 84992536c5 | |||
| ff49b0b683 | |||
| 56c4d2d4ca | |||
| 0c739016d4 | |||
| 174d0e4576 | |||
| 262ce16bb4 | |||
| 66b3a94376 | |||
| e8f71ab5ef | |||
| 2a846c9594 | |||
| 7695be0079 | |||
| dba4881318 | |||
| e44a3eb39a | |||
| 16ec08aec8 | |||
| 03984bacf4 | |||
| 462c2bd538 | |||
| 7f29f0e663 | |||
| 3898482d3f | |||
| bb7ce21076 | |||
| 49bd8333e8 | |||
| fd4dee7ce3 | |||
| ec037b1ec0 | |||
| 68c1e2ac92 | |||
| e997e16cd1 | |||
| 819a7c86c1 | |||
| fce0389e23 | |||
| 9c1346d240 | |||
| fb6e86551a | |||
| 13ba770428 | |||
| 3c4d535723 | |||
| a4d99d6201 | |||
| 3923150dad | |||
| 8eb24458f8 | |||
| 0db2c7d40c | |||
| 35dea59ce4 | |||
| 8810913f30 | |||
| 83ab71725f | |||
| cda7e98643 | |||
| dfe6dc1ee5 | |||
| e92809d470 | |||
| ecf75f2f40 | |||
| f477c1653d | |||
| 1cea1fe962 | |||
| 8b07f93620 | |||
| a9ecf90524 | |||
| 7536777a18 | |||
| 088d53adb4 | |||
| 1b18928bf0 | |||
| 8c2b44bd6c | |||
| ecc7ddd96b | |||
| 2df8ea94e0 | |||
| 121be15004 | |||
| 323e52b5a9 | |||
| 7405e20690 | |||
| ed994dcd97 | |||
| 4c3b435d23 | |||
| 4620d864dd | |||
| 042b78f609 | |||
| d4030a87e2 | |||
| cfe2db0581 | |||
| 7a98e210da | |||
| 38bdf0cd27 | |||
| 53afe27109 | |||
| c9f12aa804 | |||
| b9e43d05bd | |||
| a853e94810 | |||
| d80de043ce | |||
| 7c5b6f1e1c | |||
| f5b5f9a639 | |||
| 83cdd58ac8 | |||
| 52c187bf64 | |||
| a6b6d19545 | |||
| 9b58df8857 | |||
| 1cc767e90b | |||
| db3c2b39b1 | |||
| 36cd069c6e | |||
| 5574f45cb1 | |||
| 34d6dd9b62 | |||
| 1167b30b3f | |||
| 5499878de0 | |||
| a970fc16aa | |||
| 86b04b7b69 | |||
| 683733a682 | |||
| 673e2b0dd2 | |||
| 35fede1382 | |||
| 564eb77a52 | |||
| 11fe2987d0 | |||
| fc075d110e | |||
| 8a89248b16 | |||
| 7f21443a1b | |||
| 4189f8b477 | |||
| f85752a956 | |||
| 3302bc46f8 | |||
| 90423a98b0 | |||
| 4763877259 | |||
| e9dc6be51c | |||
| d888432dca | |||
| 6863b904b5 | |||
| 016a4c4260 | |||
| 1115be581a | |||
| d83be7c33a | |||
| b3e888279e | |||
| 92472a6b62 | |||
| 233f755416 | |||
| b026680e1c | |||
| e47cc19964 | |||
| a257697775 | |||
| 15b1310470 | |||
| 001ccca5e6 | |||
| ee60eb7207 | |||
| 62405560af | |||
| a8de8ac0da | |||
| cecebd0ef1 | |||
| aac4dbf420 | |||
| 7fedb9c1d4 | |||
| eda7af764b | |||
| 82bc4ff71d | |||
| 1763334180 | |||
| ffd7b93dde | |||
| 356d5c2cf2 | |||
| 4a95b804e9 | |||
| 46c13adfa8 | |||
| 113c101bed | |||
| 886ca7f2db | |||
| c463b937b8 | |||
| ef05b82920 | |||
| 863ccd81d5 | |||
| 4783c321d9 | |||
| 0dfcb44821 | |||
| f6602c2f68 | |||
| 8c3d5621db | |||
| 12a6a84829 | |||
| 309f5bebdb | |||
| 1bd9848d42 | |||
| 1ab3d89f10 | |||
| 6662ecc8cc | |||
| 31578c4170 | |||
| 2ed281f6a8 | |||
| eb15b26abe | |||
| 5f6df35ec4 | |||
| 65df3097e0 | |||
| c6b060a2eb | |||
| 4b0446fbef | |||
| 94e3b54581 | |||
| 80a84945ef | |||
| 6873427aa7 | |||
| 3b1d14a64d | |||
| 75e2e2c3c7 | |||
| c4fe18ca79 | |||
| 38a41b6d01 | |||
| b8c51a2767 | |||
| 9cfece0d54 | |||
| 4920086225 | |||
| 83c020eea5 | |||
| 9f77d5ff1f | |||
| 9ddbf800b9 | |||
| 00ee0ab99c | |||
| 6a81de95ab | |||
| 2c749ebdb7 | |||
| 7e71001896 | |||
| caf84ffe7d | |||
| 448d6c6ebf | |||
| 05d8604012 | |||
| 46327d6e9b | |||
| 19f647ab8c | |||
| c71bfc7aed | |||
| 2f59701148 | |||
| 69bfb64dfd | |||
| 380c9e318c | |||
| e2216ddc32 | |||
| 5be36ce8ce | |||
| 2f8ff7eb41 | |||
| 8b64e2735e | |||
| 8299a617b9 | |||
| 4c988146bc | |||
| 9d9383aaeb | |||
| 435cb0c7d6 | |||
| 928a3c7184 | |||
| f52f83349c | |||
| c0039fb624 | |||
| b9c1aec62f | |||
| c02251672e | |||
| 9b17928143 | |||
| ae9abc8545 | |||
| d28c0396c9 | |||
| 6be811119e | |||
| 87da67165a | |||
| 6888137cb6 | |||
| b8c3f03ec0 | |||
| 92ede84ea3 | |||
| 8c03d55905 | |||
| dc98a7960a | |||
| 4d0f1729cb | |||
| c85e56aac9 | |||
| f2a18e5920 | |||
| 2561a60968 | |||
| 39bb751546 | |||
| be5542a7a7 | |||
| ec48c47886 | |||
| bce7d87151 | |||
| 87f1961c3e | |||
| 52f325db82 | |||
| 262a3130d4 | |||
| df1ed0a4dc | |||
| 483985b173 | |||
| b85a76f983 | |||
| 63770ba516 | |||
| f55c234eb4 | |||
| a73a819c82 | |||
| 82941caa00 | |||
| 4b9be5aaf8 | |||
| 819a13f22d | |||
| 808d1b6413 | |||
| 27c4f0e29d | |||
| 6a0c5ddf68 | |||
| b9efa91ecc | |||
| 3f7d3cbbd3 | |||
| c970a6052a | |||
| 396f04a3b9 | |||
| 7a71d7c3a8 | |||
| 774231db1e | |||
| e8788c7e3e | |||
| 1b7ccbce2a | |||
| e184860ca4 | |||
| a3594e7207 | |||
| 96f7d50abf | |||
| 2697aea15f | |||
| 284f6057cc | |||
| 68833e43aa | |||
| 2cdc406e71 | |||
| e8e722fb59 | |||
| 292a3a4895 | |||
| 5e9b07b56d | |||
| e2ea87fe4f | |||
| 8bba4f641f | |||
| bc499df6ce | |||
| 2eede7ad1c | |||
| 4345acd7b9 | |||
| 7b1cdb5efb | |||
| 69ef885b68 | |||
| 57c0d95007 | |||
| f136e27ccf | |||
| 99bcd3a845 | |||
| e26c161ae9 | |||
| 087641c508 | |||
| bab8a8300b | |||
| b6de2b0611 | |||
| 69b7624629 | |||
| 333dfd1577 | |||
| 067bbce1d3 | |||
| 48308aee26 | |||
| e0521fc1a1 | |||
| 08d7a38546 | |||
| a2bc1ded73 | |||
| eb9ee9fafa | |||
| f025d60b92 | |||
| 5aab947b6e | |||
| 6c86914709 | |||
| b332920b17 | |||
| 5fba926ab6 | |||
| a442c180f1 | |||
| 7417bd0ac1 | |||
| 73b24b6f7b | |||
| abccba22f1 | |||
| 4fa33dfece | |||
| 9516d9b822 | |||
| be11e73d2c | |||
| 4747ab67e6 | |||
| c245e20e3f | |||
| 185537d469 | |||
| 92f1f55957 | |||
| b4a2cdf028 | |||
| 8b741f3a46 | |||
| 1c37e7092e | |||
| f24f9a4ba4 | |||
| cf49a33ea4 | |||
| f368147f9d | |||
| cf90beec0a | |||
| fbc759077c | |||
| 0c6f5d7d29 | |||
| 1e0ef99b0c | |||
| 528578d138 | |||
| 950d0b5164 | |||
| bccd9e9f11 | |||
| bf06d74879 | |||
| 5b8e436c98 | |||
| 373763723e | |||
| 41a9db376d | |||
| 62d9600cdd | |||
| ef06bf4e74 | |||
| 6d2d10cffe | |||
| b3586560c1 | |||
| 61545fabfc | |||
| 0e85d950b3 | |||
| bc5d771a06 | |||
| fcb8aed03f | |||
| 4b9f1732f1 | |||
| 2e6046a655 | |||
| 32db1a1680 | |||
| 4fd368b1af | |||
| f094c46172 | |||
| 7f4da52cc5 | |||
| e9946937e7 | |||
| ca909483f1 | |||
| c75887c20d | |||
| dd094b5a17 | |||
| af83a152dc | |||
| aa7793a593 | |||
| bf80485cc3 | |||
| 8a8df90a46 | |||
| c8009dc67d | |||
| 5cbb5d4d70 | |||
| 30d249c3a7 | |||
| 34f74026f7 | |||
| 65553f55d7 | |||
| 43ff6d9711 | |||
| 4773ffc72c | |||
| 2dbad8de41 | |||
| 5e549da75f | |||
| 3db1b94465 | |||
| c5eefaa5f3 | |||
| 7a6ba43402 | |||
| 01a15cf0e7 | |||
| 0ebbe024f2 | |||
| 429833cae6 | |||
| 2d5943352f | |||
| 9fe69e2cc4 | |||
| cb633832a9 | |||
| 88a5b4efd3 | |||
| 1ef7e70950 | |||
| d45e6ec197 | |||
| c4b100ac0c | |||
| fd8823819f | |||
| 3256bf436a | |||
| 11db31a8ee | |||
| 41f8ac6b59 | |||
| 3d19d78461 | |||
| 1c435c0f47 | |||
| 2d4baec2b1 | |||
| 83517d05d0 | |||
| 2546f63824 | |||
| cd25a64ac0 | |||
| 78f28fda54 | |||
| 810da11bb5 | |||
| 5044b70a40 | |||
| ff28d2d47a | |||
| 3edb256298 | |||
| 2015a93823 | |||
| b904753a25 | |||
| 0e50fe4f6a | |||
| f8fd6da10b | |||
| 6d28ac6b2c | |||
| 5e62ae19a9 | |||
| b8cdef6d29 | |||
| c2822a6b23 | |||
| 086d7081f8 | |||
| db196321a6 | |||
| 75e57475ca | |||
| 58020ff0eb | |||
| edce3029a2 | |||
| 3eb5b8263b | |||
| 5e939ccb4b | |||
| da75840712 | |||
| 162e533393 | |||
| 2b9010c2a2 | |||
| 078d683f47 | |||
| 3f3341bb1d | |||
| 342369355d | |||
| 5de03f6bb1 | |||
| d41d674c4f | |||
| 5ac0b8e22e | |||
| 949432ce8b | |||
| 15c6d3449b | |||
| f243abbce3 | |||
| ba312f6c7c | |||
| 0759129c14 | |||
| 71362c953c | |||
| fec2ed5540 | |||
| 989eb37fb7 | |||
| bd81abe58d | |||
| fa01e9c566 | |||
| 431d726dc6 | |||
| 89b2a6500e | |||
| a0cc6419f4 | |||
| 5fa9460bf6 | |||
| 5d492689b7 | |||
| 4f260a4511 | |||
| 28273b9309 | |||
| c213724a47 | |||
| b6e6b015e8 | |||
| ab33d715a2 | |||
| 80f63d89d1 | |||
| 81ea35c726 | |||
| 186f909c96 | |||
| 1fd0b8730b | |||
| 1164ac3079 | |||
| b027cf110d | |||
| 98676746f3 | |||
| 64f93d5755 | |||
| 102cc72fd3 | |||
| 5e08a88219 | |||
| 3b65506c13 | |||
| f5ff84ab7b | |||
| 50061dae35 | |||
| 74c48d8132 | |||
| ff5529bbae | |||
| 082a8cfae3 | |||
| ba4105d4f0 | |||
| f548da8a8d | |||
| 4b3e192ca1 | |||
| c28cbfd582 | |||
| 1fb1801285 | |||
| a7547cd311 | |||
| e69ae513b9 | |||
| 8d3d0e7ae5 | |||
| 8d4a8cbbe5 | |||
| 409bf5f889 | |||
| 00831a06ea | |||
| e81fe79e2b | |||
| e34189a793 | |||
| cea5d9e3d1 | |||
| def2d033a2 | |||
| 7f8102a4cd | |||
| e57464dac8 | |||
| 2528fbe3b2 | |||
| 600399cc96 | |||
| c0d16eafb3 | |||
| a4cea65ace | |||
| 6e17a05419 | |||
| 2522a73111 |
@@ -31,7 +31,7 @@ runs:
|
||||
${{ inputs.java-early-access == 'true' && format('{0}-ea', inputs.java-version) || inputs.java-version }}
|
||||
${{ inputs.java-toolchain == 'true' && '17' || '' }}
|
||||
- name: Set Up Gradle
|
||||
uses: gradle/actions/setup-gradle@cc4fc85e6b35bafd578d5ffbc76a5518407e1af0 # v4.2.1
|
||||
uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
|
||||
with:
|
||||
cache-read-only: false
|
||||
develocity-access-key: ${{ inputs.develocity-access-key }}
|
||||
|
||||
@@ -20,7 +20,7 @@ runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Set Up JFrog CLI
|
||||
uses: jfrog/setup-jfrog-cli@dff217c085c17666e8849ebdbf29c8fe5e3995e6 # v4.5.2
|
||||
uses: jfrog/setup-jfrog-cli@f748a0599171a192a2668afee8d0497f7c1069df # v4.5.6
|
||||
env:
|
||||
JF_ENV_SPRING: ${{ inputs.jfrog-cli-config-token }}
|
||||
- name: Download Release Artifacts
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
require:
|
||||
members: false
|
||||
|
||||
@@ -2,7 +2,7 @@ name: Build and Deploy Snapshot
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 6.2.x
|
||||
- main
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
|
||||
/**/framework-api-*-docs.zip::zip.type=docs
|
||||
/**/framework-api-*-schema.zip::zip.type=schema
|
||||
build-name: 'spring-framework-6.2.x'
|
||||
build-name: 'spring-framework-7.0.x'
|
||||
folder: 'deployment-repository'
|
||||
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
repository: 'libs-snapshot-local'
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 6.2.x
|
||||
schedule:
|
||||
- cron: '30 9 * * *'
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
name: Release Milestone
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v7.0.0-M[1-9]
|
||||
- v7.0.0-RC[1-9]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
build-and-stage-release:
|
||||
name: Build and Stage Release
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v4
|
||||
- name: Build and Publish
|
||||
id: build-and-publish
|
||||
uses: ./.github/actions/build
|
||||
with:
|
||||
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
|
||||
publish: true
|
||||
- name: Stage Release
|
||||
uses: spring-io/artifactory-deploy-action@26bbe925a75f4f863e1e529e85be2d0093cac116 # v0.0.1
|
||||
with:
|
||||
artifact-properties: |
|
||||
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
|
||||
/**/framework-api-*-docs.zip::zip.type=docs
|
||||
/**/framework-api-*-schema.zip::zip.type=schema
|
||||
build-name: ${{ format('spring-framework-{0}', steps.build-and-publish.outputs.version)}}
|
||||
folder: 'deployment-repository'
|
||||
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
repository: 'libs-staging-local'
|
||||
signing-key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
signing-passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||
uri: 'https://repo.spring.io'
|
||||
username: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
outputs:
|
||||
version: ${{ steps.build-and-publish.outputs.version }}
|
||||
verify:
|
||||
name: Verify
|
||||
needs: build-and-stage-release
|
||||
uses: ./.github/workflows/verify.yml
|
||||
secrets:
|
||||
google-chat-webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
|
||||
repository-password: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
repository-username: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
|
||||
with:
|
||||
staging: true
|
||||
version: ${{ needs.build-and-stage-release.outputs.version }}
|
||||
sync-to-maven-central:
|
||||
name: Sync to Maven Central
|
||||
needs:
|
||||
- build-and-stage-release
|
||||
- verify
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Sync to Maven Central
|
||||
uses: ./.github/actions/sync-to-maven-central
|
||||
with:
|
||||
jfrog-cli-config-token: ${{ secrets.JF_ARTIFACTORY_SPRING }}
|
||||
ossrh-s01-staging-profile: ${{ secrets.OSSRH_S01_STAGING_PROFILE }}
|
||||
ossrh-s01-token-password: ${{ secrets.OSSRH_S01_TOKEN_PASSWORD }}
|
||||
ossrh-s01-token-username: ${{ secrets.OSSRH_S01_TOKEN_USERNAME }}
|
||||
spring-framework-version: ${{ needs.build-and-stage-release.outputs.version }}
|
||||
promote-release:
|
||||
name: Promote Release
|
||||
needs:
|
||||
- build-and-stage-release
|
||||
- sync-to-maven-central
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up JFrog CLI
|
||||
uses: jfrog/setup-jfrog-cli@dff217c085c17666e8849ebdbf29c8fe5e3995e6 # v4.5.2
|
||||
env:
|
||||
JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }}
|
||||
- name: Promote build
|
||||
run: jfrog rt build-promote ${{ format('spring-framework-{0}', needs.build-and-stage-release.outputs.version)}} ${{ github.run_number }} libs-milestone-local
|
||||
create-github-release:
|
||||
name: Create GitHub Release
|
||||
needs:
|
||||
- build-and-stage-release
|
||||
- promote-release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Create GitHub Release
|
||||
uses: ./.github/actions/create-github-release
|
||||
with:
|
||||
milestone: ${{ needs.build-and-stage-release.outputs.version }}
|
||||
pre-release: true
|
||||
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
|
||||
@@ -2,7 +2,7 @@ name: Release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v6.2.[0-9]+
|
||||
- v7.0.[0-9]+
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
@@ -20,7 +20,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@dc1913008c0599f0c4b1fdafb6ff3c502b3565ea # v0.0.2
|
||||
with:
|
||||
artifact-properties: |
|
||||
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
|
||||
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
distribution: 'liberica'
|
||||
java-version: 17
|
||||
- name: Set Up Gradle
|
||||
uses: gradle/actions/setup-gradle@cc4fc85e6b35bafd578d5ffbc76a5518407e1af0 # v4.2.1
|
||||
uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
|
||||
with:
|
||||
cache-read-only: false
|
||||
- name: Configure Gradle Properties
|
||||
|
||||
@@ -53,3 +53,4 @@ atlassian-ide-plugin.xml
|
||||
cached-antora-playbook.yml
|
||||
|
||||
node_modules
|
||||
/.kotlin/
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# Enable auto-env through the sdkman_auto_env config
|
||||
# Add key=value pairs of SDKs to use below
|
||||
java=17.0.13-librca
|
||||
java=23.0.1-librca
|
||||
|
||||
+5
-9
@@ -9,7 +9,7 @@ plugins {
|
||||
id 'io.github.goooler.shadow' version '8.1.8' apply false
|
||||
id 'me.champeau.jmh' version '0.7.2' apply false
|
||||
id 'me.champeau.mrjar' version '0.1.1'
|
||||
id "net.ltgt.errorprone" version "3.1.0" apply false
|
||||
id "net.ltgt.errorprone" version "4.1.0" apply false
|
||||
}
|
||||
|
||||
ext {
|
||||
@@ -80,18 +80,13 @@ configure([rootProject] + javaProjects) { project ->
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-suite-engine")
|
||||
testRuntimeOnly("org.apache.logging.log4j:log4j-core")
|
||||
testRuntimeOnly("org.apache.logging.log4j:log4j-jul")
|
||||
testRuntimeOnly("org.apache.logging.log4j:log4j-slf4j2-impl")
|
||||
// JSR-305 only used for non-required meta-annotations
|
||||
compileOnly("com.google.code.findbugs:jsr305")
|
||||
testCompileOnly("com.google.code.findbugs:jsr305")
|
||||
}
|
||||
|
||||
ext.javadocLinks = [
|
||||
"https://docs.oracle.com/en/java/javase/17/docs/api/",
|
||||
"https://jakarta.ee/specifications/platform/9/apidocs/",
|
||||
"https://jakarta.ee/specifications/platform/11/apidocs/",
|
||||
"https://docs.jboss.org/hibernate/orm/5.6/javadocs/",
|
||||
"https://eclipse.dev/aspectj/doc/released/aspectj5rt-api",
|
||||
"https://eclipse.dev/aspectj/doc/latest/runtime-api/",
|
||||
"https://www.quartz-scheduler.org/api/2.3.0/",
|
||||
"https://fasterxml.github.io/jackson-core/javadoc/2.14/",
|
||||
"https://fasterxml.github.io/jackson-databind/javadoc/2.14/",
|
||||
@@ -109,7 +104,8 @@ configure([rootProject] + javaProjects) { project ->
|
||||
// Previously there could be a split-package issue between JSR250 and JSR305 javax.annotation packages,
|
||||
// but since 6.0 JSR 250 annotations such as @Resource and @PostConstruct have been replaced by their
|
||||
// JakartaEE equivalents in the jakarta.annotation package.
|
||||
//"https://www.javadoc.io/doc/com.google.code.findbugs/jsr305/3.0.2/"
|
||||
//"https://www.javadoc.io/doc/com.google.code.findbugs/jsr305/3.0.2/",
|
||||
"https://jspecify.dev/docs/api/"
|
||||
] as String[]
|
||||
}
|
||||
|
||||
|
||||
+12
-1
@@ -9,7 +9,18 @@ The `org.springframework.build.conventions` plugin applies all conventions to th
|
||||
|
||||
* Configuring the Java compiler, see `JavaConventions`
|
||||
* Configuring the Kotlin compiler, see `KotlinConventions`
|
||||
* Configuring testing in the build with `TestConventions`
|
||||
* Configuring testing in the build with `TestConventions`
|
||||
* Configuring the ArchUnit rules for the project, see `org.springframework.build.architecture.ArchitectureRules`
|
||||
|
||||
This plugin also provides a DSL extension to optionally enable Java preview features for
|
||||
compiling and testing sources in a module. This can be applied with the following in a
|
||||
module build file:
|
||||
|
||||
```groovy
|
||||
springFramework {
|
||||
enableJavaPreviewFeatures = true
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Build Plugins
|
||||
|
||||
@@ -20,7 +20,7 @@ ext {
|
||||
dependencies {
|
||||
checkstyle "io.spring.javaformat:spring-javaformat-checkstyle:${javaFormatVersion}"
|
||||
implementation "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}"
|
||||
implementation "org.jetbrains.kotlin:kotlin-compiler-embeddable:${kotlinVersion}"
|
||||
implementation "com.tngtech.archunit:archunit:1.3.0"
|
||||
implementation "org.gradle:test-retry-gradle-plugin:1.5.6"
|
||||
implementation "io.spring.javaformat:spring-javaformat-gradle-plugin:${javaFormatVersion}"
|
||||
implementation "io.spring.nohttp:nohttp-gradle:0.0.11"
|
||||
@@ -28,6 +28,10 @@ dependencies {
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
architecturePlugin {
|
||||
id = "org.springframework.architecture"
|
||||
implementationClass = "org.springframework.build.architecture.ArchitecturePlugin"
|
||||
}
|
||||
conventionsPlugin {
|
||||
id = "org.springframework.build.conventions"
|
||||
implementationClass = "org.springframework.build.ConventionsPlugin"
|
||||
|
||||
@@ -50,7 +50,7 @@ public class CheckstyleConventions {
|
||||
project.getPlugins().apply(CheckstylePlugin.class);
|
||||
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g"));
|
||||
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
|
||||
checkstyle.setToolVersion("10.21.1");
|
||||
checkstyle.setToolVersion("10.21.2");
|
||||
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
|
||||
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
|
||||
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
|
||||
@@ -64,7 +64,7 @@ public class CheckstyleConventions {
|
||||
NoHttpExtension noHttp = project.getExtensions().getByType(NoHttpExtension.class);
|
||||
noHttp.setAllowlistFile(project.file("src/nohttp/allowlist.lines"));
|
||||
noHttp.getSource().exclude("**/test-output/**", "**/.settings/**",
|
||||
"**/.classpath", "**/.project", "**/.gradle/**", "**/node_modules/**");
|
||||
"**/.classpath", "**/.project", "**/.gradle/**", "**/node_modules/**", "**/spring-jcl/**");
|
||||
List<String> buildFolders = List.of("bin", "build", "out");
|
||||
project.allprojects(subproject -> {
|
||||
Path rootPath = project.getRootDir().toPath();
|
||||
|
||||
@@ -21,12 +21,15 @@ import org.gradle.api.Project;
|
||||
import org.gradle.api.plugins.JavaBasePlugin;
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinBasePlugin;
|
||||
|
||||
import org.springframework.build.architecture.ArchitecturePlugin;
|
||||
|
||||
/**
|
||||
* Plugin to apply conventions to projects that are part of Spring Framework's build.
|
||||
* Conventions are applied in response to various plugins being applied.
|
||||
*
|
||||
* <p>When the {@link JavaBasePlugin} is applied, the conventions in {@link CheckstyleConventions},
|
||||
* {@link TestConventions} and {@link JavaConventions} are applied.
|
||||
* The {@link ArchitecturePlugin} plugin is also applied.
|
||||
* When the {@link KotlinBasePlugin} is applied, the conventions in {@link KotlinConventions}
|
||||
* are applied.
|
||||
*
|
||||
@@ -36,6 +39,8 @@ public class ConventionsPlugin implements Plugin<Project> {
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
project.getExtensions().create("springFramework", SpringFrameworkExtension.class);
|
||||
new ArchitecturePlugin().apply(project);
|
||||
new CheckstyleConventions().apply(project);
|
||||
new JavaConventions().apply(project);
|
||||
new KotlinConventions().apply(project);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -73,21 +73,42 @@ public class JavaConventions {
|
||||
private void applyJavaCompileConventions(Project project) {
|
||||
project.getExtensions().getByType(JavaPluginExtension.class).toolchain(toolchain -> {
|
||||
toolchain.getVendor().set(JvmVendorSpec.BELLSOFT);
|
||||
toolchain.getLanguageVersion().set(JavaLanguageVersion.of(17));
|
||||
toolchain.getLanguageVersion().set(JavaLanguageVersion.of(23));
|
||||
});
|
||||
project.getTasks().withType(JavaCompile.class)
|
||||
.matching(compileTask -> compileTask.getName().equals(JavaPlugin.COMPILE_JAVA_TASK_NAME))
|
||||
.forEach(compileTask -> {
|
||||
compileTask.getOptions().setCompilerArgs(COMPILER_ARGS);
|
||||
compileTask.getOptions().setEncoding("UTF-8");
|
||||
});
|
||||
project.getTasks().withType(JavaCompile.class)
|
||||
.matching(compileTask -> compileTask.getName().equals(JavaPlugin.COMPILE_TEST_JAVA_TASK_NAME)
|
||||
|| compileTask.getName().equals("compileTestFixturesJava"))
|
||||
.forEach(compileTask -> {
|
||||
compileTask.getOptions().setCompilerArgs(TEST_COMPILER_ARGS);
|
||||
compileTask.getOptions().setEncoding("UTF-8");
|
||||
});
|
||||
SpringFrameworkExtension frameworkExtension = project.getExtensions().getByType(SpringFrameworkExtension.class);
|
||||
project.afterEvaluate(p -> {
|
||||
p.getTasks().withType(JavaCompile.class)
|
||||
.matching(compileTask -> compileTask.getName().startsWith(JavaPlugin.COMPILE_JAVA_TASK_NAME))
|
||||
.forEach(compileTask -> {
|
||||
compileTask.getOptions().setCompilerArgs(COMPILER_ARGS);
|
||||
compileTask.getOptions().getCompilerArgumentProviders().add(frameworkExtension.asArgumentProvider());
|
||||
compileTask.getOptions().setEncoding("UTF-8");
|
||||
setJavaRelease(compileTask);
|
||||
});
|
||||
p.getTasks().withType(JavaCompile.class)
|
||||
.matching(compileTask -> compileTask.getName().startsWith(JavaPlugin.COMPILE_TEST_JAVA_TASK_NAME)
|
||||
|| compileTask.getName().equals("compileTestFixturesJava"))
|
||||
.forEach(compileTask -> {
|
||||
compileTask.getOptions().setCompilerArgs(TEST_COMPILER_ARGS);
|
||||
compileTask.getOptions().getCompilerArgumentProviders().add(frameworkExtension.asArgumentProvider());
|
||||
compileTask.getOptions().setEncoding("UTF-8");
|
||||
setJavaRelease(compileTask);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private void setJavaRelease(JavaCompile task) {
|
||||
int defaultVersion = 17;
|
||||
int releaseVersion = defaultVersion;
|
||||
int compilerVersion = task.getJavaCompiler().get().getMetadata().getLanguageVersion().asInt();
|
||||
for (int version = defaultVersion ; version <= compilerVersion ; version++) {
|
||||
if (task.getName().contains("Java" + version)) {
|
||||
releaseVersion = version;
|
||||
break;
|
||||
}
|
||||
}
|
||||
task.getOptions().getRelease().set(releaseVersion);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,15 +16,14 @@
|
||||
|
||||
package org.springframework.build;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.gradle.api.Project;
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions;
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget;
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion;
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile;
|
||||
|
||||
/**
|
||||
* @author Brian Clozel
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class KotlinConventions {
|
||||
|
||||
@@ -34,15 +33,19 @@ public class KotlinConventions {
|
||||
}
|
||||
|
||||
private void configure(KotlinCompile compile) {
|
||||
KotlinJvmOptions kotlinOptions = compile.getKotlinOptions();
|
||||
kotlinOptions.setApiVersion("1.7");
|
||||
kotlinOptions.setLanguageVersion("1.7");
|
||||
kotlinOptions.setJvmTarget("17");
|
||||
kotlinOptions.setJavaParameters(true);
|
||||
kotlinOptions.setAllWarningsAsErrors(true);
|
||||
List<String> freeCompilerArgs = new ArrayList<>(compile.getKotlinOptions().getFreeCompilerArgs());
|
||||
freeCompilerArgs.addAll(List.of("-Xsuppress-version-warnings", "-Xjsr305=strict", "-opt-in=kotlin.RequiresOptIn"));
|
||||
compile.getKotlinOptions().setFreeCompilerArgs(freeCompilerArgs);
|
||||
compile.compilerOptions(options -> {
|
||||
options.getApiVersion().set(KotlinVersion.KOTLIN_2_1);
|
||||
options.getLanguageVersion().set(KotlinVersion.KOTLIN_2_1);
|
||||
options.getJvmTarget().set(JvmTarget.JVM_17);
|
||||
options.getJavaParameters().set(true);
|
||||
options.getAllWarningsAsErrors().set(true);
|
||||
options.getFreeCompilerArgs().addAll(
|
||||
"-Xsuppress-version-warnings",
|
||||
"-Xjsr305=strict", // For dependencies using JSR 305
|
||||
"-opt-in=kotlin.RequiresOptIn",
|
||||
"-Xjdk-release=17" // Needed due to https://youtrack.jetbrains.com/issue/KT-49746
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.process.CommandLineArgumentProvider;
|
||||
|
||||
public class SpringFrameworkExtension {
|
||||
|
||||
private final Property<Boolean> enableJavaPreviewFeatures;
|
||||
|
||||
public SpringFrameworkExtension(Project project) {
|
||||
this.enableJavaPreviewFeatures = project.getObjects().property(Boolean.class);
|
||||
}
|
||||
|
||||
public Property<Boolean> getEnableJavaPreviewFeatures() {
|
||||
return this.enableJavaPreviewFeatures;
|
||||
}
|
||||
|
||||
public CommandLineArgumentProvider asArgumentProvider() {
|
||||
return () -> {
|
||||
if (getEnableJavaPreviewFeatures().getOrElse(false)) {
|
||||
return List.of("--enable-preview");
|
||||
}
|
||||
return Collections.emptyList();
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -54,10 +54,7 @@ class TestConventions {
|
||||
test.include("**/*Tests.class", "**/*Test.class");
|
||||
test.setSystemProperties(Map.of(
|
||||
"java.awt.headless", "true",
|
||||
"io.netty.leakDetection.level", "paranoid",
|
||||
"io.netty5.leakDetectionLevel", "paranoid",
|
||||
"io.netty5.leakDetection.targetRecords", "32",
|
||||
"io.netty5.buffer.lifecycleTracingEnabled", "true"
|
||||
"io.netty.leakDetection.level", "paranoid"
|
||||
));
|
||||
if (project.hasProperty("testGroups")) {
|
||||
test.systemProperty("testGroups", project.getProperties().get("testGroups"));
|
||||
@@ -67,6 +64,8 @@ class TestConventions {
|
||||
"--add-opens=java.base/java.util=ALL-UNNAMED",
|
||||
"-Xshare:off"
|
||||
);
|
||||
test.getJvmArgumentProviders().add(project.getExtensions()
|
||||
.getByType(SpringFrameworkExtension.class).asArgumentProvider());
|
||||
}
|
||||
|
||||
private void configureTestRetryPlugin(Project project, Test test) {
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build.architecture;
|
||||
|
||||
import com.tngtech.archunit.core.domain.JavaClasses;
|
||||
import com.tngtech.archunit.core.importer.ClassFileImporter;
|
||||
import com.tngtech.archunit.lang.ArchRule;
|
||||
import com.tngtech.archunit.lang.EvaluationResult;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.List;
|
||||
import org.gradle.api.DefaultTask;
|
||||
import org.gradle.api.GradleException;
|
||||
import org.gradle.api.Task;
|
||||
import org.gradle.api.file.DirectoryProperty;
|
||||
import org.gradle.api.file.FileCollection;
|
||||
import org.gradle.api.file.FileTree;
|
||||
import org.gradle.api.provider.ListProperty;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.tasks.IgnoreEmptyDirectories;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.InputFiles;
|
||||
import org.gradle.api.tasks.Internal;
|
||||
import org.gradle.api.tasks.Optional;
|
||||
import org.gradle.api.tasks.OutputDirectory;
|
||||
import org.gradle.api.tasks.PathSensitive;
|
||||
import org.gradle.api.tasks.PathSensitivity;
|
||||
import org.gradle.api.tasks.SkipWhenEmpty;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
|
||||
import static org.springframework.build.architecture.ArchitectureRules.*;
|
||||
|
||||
/**
|
||||
* {@link Task} that checks for architecture problems.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
public abstract class ArchitectureCheck extends DefaultTask {
|
||||
|
||||
private FileCollection classes;
|
||||
|
||||
public ArchitectureCheck() {
|
||||
getOutputDirectory().convention(getProject().getLayout().getBuildDirectory().dir(getName()));
|
||||
getProhibitObjectsRequireNonNull().convention(true);
|
||||
getRules().addAll(packageInfoShouldBeNullMarked(),
|
||||
classesShouldNotImportForbiddenTypes(),
|
||||
javaClassesShouldNotImportKotlinAnnotations(),
|
||||
allPackagesShouldBeFreeOfTangles(),
|
||||
noClassesShouldCallStringToLowerCaseWithoutLocale(),
|
||||
noClassesShouldCallStringToUpperCaseWithoutLocale());
|
||||
getRuleDescriptions().set(getRules().map((rules) -> rules.stream().map(ArchRule::getDescription).toList()));
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
void checkArchitecture() throws IOException {
|
||||
JavaClasses javaClasses = new ClassFileImporter()
|
||||
.importPaths(this.classes.getFiles().stream().map(File::toPath).toList());
|
||||
List<EvaluationResult> violations = getRules().get()
|
||||
.stream()
|
||||
.map((rule) -> rule.evaluate(javaClasses))
|
||||
.filter(EvaluationResult::hasViolation)
|
||||
.toList();
|
||||
File outputFile = getOutputDirectory().file("failure-report.txt").get().getAsFile();
|
||||
outputFile.getParentFile().mkdirs();
|
||||
if (!violations.isEmpty()) {
|
||||
StringBuilder report = new StringBuilder();
|
||||
for (EvaluationResult violation : violations) {
|
||||
report.append(violation.getFailureReport());
|
||||
report.append(String.format("%n"));
|
||||
}
|
||||
Files.writeString(outputFile.toPath(), report.toString(), StandardOpenOption.CREATE,
|
||||
StandardOpenOption.TRUNCATE_EXISTING);
|
||||
throw new GradleException("Architecture check failed. See '" + outputFile + "' for details.");
|
||||
}
|
||||
else {
|
||||
outputFile.createNewFile();
|
||||
}
|
||||
}
|
||||
|
||||
public void setClasses(FileCollection classes) {
|
||||
this.classes = classes;
|
||||
}
|
||||
|
||||
@Internal
|
||||
public FileCollection getClasses() {
|
||||
return this.classes;
|
||||
}
|
||||
|
||||
@InputFiles
|
||||
@SkipWhenEmpty
|
||||
@IgnoreEmptyDirectories
|
||||
@PathSensitive(PathSensitivity.RELATIVE)
|
||||
final FileTree getInputClasses() {
|
||||
return this.classes.getAsFileTree();
|
||||
}
|
||||
|
||||
@Optional
|
||||
@InputFiles
|
||||
@PathSensitive(PathSensitivity.RELATIVE)
|
||||
public abstract DirectoryProperty getResourcesDirectory();
|
||||
|
||||
@OutputDirectory
|
||||
public abstract DirectoryProperty getOutputDirectory();
|
||||
|
||||
@Internal
|
||||
public abstract ListProperty<ArchRule> getRules();
|
||||
|
||||
@Internal
|
||||
public abstract Property<Boolean> getProhibitObjectsRequireNonNull();
|
||||
|
||||
@Input
|
||||
// The rules themselves can't be an input as they aren't serializable so we use
|
||||
// their descriptions instead
|
||||
abstract ListProperty<String> getRuleDescriptions();
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build.architecture;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.Task;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.plugins.JavaPluginExtension;
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
import org.gradle.language.base.plugins.LifecycleBasePlugin;
|
||||
|
||||
/**
|
||||
* {@link Plugin} for verifying a project's architecture.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class ArchitecturePlugin implements Plugin<Project> {
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> registerTasks(project));
|
||||
}
|
||||
|
||||
private void registerTasks(Project project) {
|
||||
JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class);
|
||||
List<TaskProvider<ArchitectureCheck>> architectureChecks = new ArrayList<>();
|
||||
for (SourceSet sourceSet : javaPluginExtension.getSourceSets()) {
|
||||
if (sourceSet.getName().contains("test")) {
|
||||
// skip test source sets.
|
||||
continue;
|
||||
}
|
||||
TaskProvider<ArchitectureCheck> checkArchitecture = project.getTasks()
|
||||
.register(taskName(sourceSet), ArchitectureCheck.class,
|
||||
(task) -> {
|
||||
task.setClasses(sourceSet.getOutput().getClassesDirs());
|
||||
task.getResourcesDirectory().set(sourceSet.getOutput().getResourcesDir());
|
||||
task.dependsOn(sourceSet.getProcessResourcesTaskName());
|
||||
task.setDescription("Checks the architecture of the classes of the " + sourceSet.getName()
|
||||
+ " source set.");
|
||||
task.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP);
|
||||
});
|
||||
architectureChecks.add(checkArchitecture);
|
||||
}
|
||||
if (!architectureChecks.isEmpty()) {
|
||||
TaskProvider<Task> checkTask = project.getTasks().named(LifecycleBasePlugin.CHECK_TASK_NAME);
|
||||
checkTask.configure((check) -> check.dependsOn(architectureChecks));
|
||||
}
|
||||
}
|
||||
|
||||
private static String taskName(SourceSet sourceSet) {
|
||||
return "checkArchitecture"
|
||||
+ sourceSet.getName().substring(0, 1).toUpperCase()
|
||||
+ sourceSet.getName().substring(1);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build.architecture;
|
||||
|
||||
import com.tngtech.archunit.base.DescribedPredicate;
|
||||
import com.tngtech.archunit.core.domain.JavaClass;
|
||||
import com.tngtech.archunit.lang.ArchRule;
|
||||
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition;
|
||||
import com.tngtech.archunit.library.dependencies.SliceAssignment;
|
||||
import com.tngtech.archunit.library.dependencies.SliceIdentifier;
|
||||
import com.tngtech.archunit.library.dependencies.SlicesRuleDefinition;
|
||||
import java.util.List;
|
||||
|
||||
abstract class ArchitectureRules {
|
||||
|
||||
static ArchRule allPackagesShouldBeFreeOfTangles() {
|
||||
return SlicesRuleDefinition.slices()
|
||||
.assignedFrom(new SpringSlices()).should().beFreeOfCycles();
|
||||
}
|
||||
|
||||
static ArchRule noClassesShouldCallStringToLowerCaseWithoutLocale() {
|
||||
return ArchRuleDefinition.noClasses()
|
||||
.should()
|
||||
.callMethod(String.class, "toLowerCase")
|
||||
.because("String.toLowerCase(Locale.ROOT) should be used instead");
|
||||
}
|
||||
|
||||
static ArchRule noClassesShouldCallStringToUpperCaseWithoutLocale() {
|
||||
return ArchRuleDefinition.noClasses()
|
||||
.should()
|
||||
.callMethod(String.class, "toUpperCase")
|
||||
.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()
|
||||
.haveFullyQualifiedName("reactor.core.support.Assert")
|
||||
.orShould().dependOnClassesThat()
|
||||
.haveFullyQualifiedName("org.slf4j.LoggerFactory")
|
||||
.orShould().dependOnClassesThat()
|
||||
.haveFullyQualifiedName("org.springframework.lang.NonNull")
|
||||
.orShould().dependOnClassesThat()
|
||||
.haveFullyQualifiedName("org.springframework.lang.Nullable");
|
||||
}
|
||||
|
||||
static ArchRule javaClassesShouldNotImportKotlinAnnotations() {
|
||||
return ArchRuleDefinition.noClasses()
|
||||
.that(new DescribedPredicate<JavaClass>("is not a Kotlin class") {
|
||||
@Override
|
||||
public boolean test(JavaClass javaClass) {
|
||||
return javaClass.getSourceCodeLocation()
|
||||
.getSourceFileName().endsWith(".java");
|
||||
}
|
||||
}
|
||||
)
|
||||
.should().dependOnClassesThat()
|
||||
.resideInAnyPackage("org.jetbrains.annotations..")
|
||||
.allowEmptyShould(true);
|
||||
}
|
||||
|
||||
static class SpringSlices implements SliceAssignment {
|
||||
|
||||
private final List<String> ignoredPackages = List.of("org.springframework.asm",
|
||||
"org.springframework.cglib",
|
||||
"org.springframework.javapoet",
|
||||
"org.springframework.objenesis");
|
||||
|
||||
@Override
|
||||
public SliceIdentifier getIdentifierOf(JavaClass javaClass) {
|
||||
|
||||
String packageName = javaClass.getPackageName();
|
||||
for (String ignoredPackage : ignoredPackages) {
|
||||
if (packageName.startsWith(ignoredPackage)) {
|
||||
return SliceIdentifier.ignore();
|
||||
}
|
||||
}
|
||||
return SliceIdentifier.of("spring framework");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Spring Framework Slices";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import org.gradle.api.attributes.Usage;
|
||||
import org.gradle.api.attributes.java.TargetJvmVersion;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.plugins.jvm.JvmTestSuite;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
import org.gradle.api.tasks.testing.Test;
|
||||
import org.gradle.testing.base.TestingExtension;
|
||||
|
||||
@@ -52,7 +53,7 @@ public class RuntimeHintsAgentPlugin implements Plugin<Project> {
|
||||
TestingExtension testing = project.getExtensions().getByType(TestingExtension.class);
|
||||
JvmTestSuite jvmTestSuite = (JvmTestSuite) testing.getSuites().getByName("test");
|
||||
RuntimeHintsAgentExtension agentExtension = createRuntimeHintsAgentExtension(project);
|
||||
Test agentTest = project.getTasks().create(RUNTIMEHINTS_TEST_TASK, Test.class, test -> {
|
||||
TaskProvider<Test> agentTest = project.getTasks().register(RUNTIMEHINTS_TEST_TASK, Test.class, test -> {
|
||||
test.useJUnitPlatform(options -> {
|
||||
options.includeTags("RuntimeHintsTests");
|
||||
});
|
||||
@@ -63,7 +64,7 @@ public class RuntimeHintsAgentPlugin implements Plugin<Project> {
|
||||
test.setClasspath(jvmTestSuite.getSources().getRuntimeClasspath());
|
||||
test.getJvmArgumentProviders().add(createRuntimeHintsAgentArgumentProvider(project, agentExtension));
|
||||
});
|
||||
project.getTasks().getByName("check", task -> task.dependsOn(agentTest));
|
||||
project.getTasks().named("check", task -> task.dependsOn(agentTest));
|
||||
project.getDependencies().add(CONFIGURATION_NAME, project.project(":spring-core-test"));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
|
||||
|
||||
plugins {
|
||||
id 'kotlin'
|
||||
id 'io.spring.antora.generate-antora-yml' version '0.0.1'
|
||||
@@ -41,6 +43,11 @@ repositories {
|
||||
}
|
||||
}
|
||||
|
||||
// To avoid a redeclaration error with Kotlin compiler
|
||||
tasks.named('compileKotlin', KotlinCompilationTask.class) {
|
||||
javaSources.from = []
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(project(":spring-aspects"))
|
||||
api(project(":spring-context"))
|
||||
@@ -62,6 +69,7 @@ dependencies {
|
||||
api("jakarta.servlet:jakarta.servlet-api")
|
||||
api("jakarta.resource:jakarta.resource-api")
|
||||
api("jakarta.validation:jakarta.validation-api")
|
||||
api("jakarta.websocket:jakarta.websocket-client-api")
|
||||
api("javax.cache:cache-api")
|
||||
api("org.apache.activemq:activemq-ra:6.1.2")
|
||||
api("org.apache.commons:commons-dbcp2:2.11.0")
|
||||
|
||||
@@ -101,7 +101,6 @@
|
||||
*** xref:core/aop-api/extensibility.adoc[]
|
||||
** xref:core/null-safety.adoc[]
|
||||
** xref:core/databuffer-codec.adoc[]
|
||||
** xref:core/spring-jcl.adoc[]
|
||||
** xref:core/aot.adoc[]
|
||||
** xref:core/appendix.adoc[]
|
||||
*** xref:core/appendix/xsd-schemas.adoc[]
|
||||
@@ -161,7 +160,6 @@
|
||||
**** xref:web/webmvc/mvc-servlet/exceptionhandlers.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/viewresolver.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/localeresolver.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/themeresolver.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/multipart.adoc[]
|
||||
**** xref:web/webmvc/mvc-servlet/logging.adoc[]
|
||||
*** xref:web/webmvc/filters.adoc[]
|
||||
|
||||
@@ -206,18 +206,17 @@ another file or files. The following example shows how to do so:
|
||||
<beans>
|
||||
<import resource="services.xml"/>
|
||||
<import resource="resources/messageSource.xml"/>
|
||||
<import resource="/resources/themeSource.xml"/>
|
||||
|
||||
<bean id="bean1" class="..."/>
|
||||
<bean id="bean2" class="..."/>
|
||||
</beans>
|
||||
----
|
||||
|
||||
In the preceding example, external bean definitions are loaded from three files:
|
||||
`services.xml`, `messageSource.xml`, and `themeSource.xml`. All location paths are
|
||||
In the preceding example, external bean definitions are loaded from the files
|
||||
`services.xml` and `messageSource.xml`. All location paths are
|
||||
relative to the definition file doing the importing, so `services.xml` must be in the
|
||||
same directory or classpath location as the file doing the importing, while
|
||||
`messageSource.xml` and `themeSource.xml` must be in a `resources` location below the
|
||||
`messageSource.xml` must be in a `resources` location below the
|
||||
location of the importing file. As you can see, a leading slash is ignored. However, given
|
||||
that these paths are relative, it is better form not to use the slash at all. The
|
||||
contents of the files being imported, including the top level `<beans/>` element, must
|
||||
|
||||
@@ -674,9 +674,7 @@ By default, the `AnnotationBeanNameGenerator` is used. For Spring
|
||||
xref:core/beans/classpath-scanning.adoc#beans-stereotype-annotations[stereotype annotations],
|
||||
if you supply a name via the annotation's `value` attribute that name will be used as
|
||||
the name in the corresponding bean definition. This convention also applies when the
|
||||
following JSR-250 and JSR-330 annotations are used instead of Spring stereotype
|
||||
annotations: `@jakarta.annotation.ManagedBean`, `@javax.annotation.ManagedBean`,
|
||||
`@jakarta.inject.Named`, and `@javax.inject.Named`.
|
||||
`@jakarta.inject.Named` annotation is used instead of Spring stereotype annotations.
|
||||
|
||||
As of Spring Framework 6.1, the name of the annotation attribute that is used to specify
|
||||
the bean name is no longer required to be `value`. Custom stereotype annotations can
|
||||
|
||||
@@ -57,9 +57,9 @@ The following table describes these properties:
|
||||
In addition to bean definitions that contain information on how to create a specific
|
||||
bean, the `ApplicationContext` implementations also permit the registration of existing
|
||||
objects that are created outside the container (by users). This is done by accessing the
|
||||
ApplicationContext's `BeanFactory` through the `getBeanFactory()` method, which returns
|
||||
the `DefaultListableBeanFactory` implementation. `DefaultListableBeanFactory` supports
|
||||
this registration through the `registerSingleton(..)` and `registerBeanDefinition(..)`
|
||||
ApplicationContext's `BeanFactory` through the `getAutowireCapableBeanFactory()` method,
|
||||
which returns the `DefaultListableBeanFactory` implementation. `DefaultListableBeanFactory`
|
||||
supports this registration through the `registerSingleton(..)` and `registerBeanDefinition(..)`
|
||||
methods. However, typical applications work solely with beans defined through regular
|
||||
bean definition metadata.
|
||||
|
||||
|
||||
-7
@@ -120,8 +120,6 @@ dynamically generate a subclass that overrides the method.
|
||||
subclasses cannot be `final`, and the method to be overridden cannot be `final`, either.
|
||||
* Unit-testing a class that has an `abstract` method requires you to subclass the class
|
||||
yourself and to supply a stub implementation of the `abstract` method.
|
||||
* Concrete methods are also necessary for component scanning, which requires concrete
|
||||
classes to pick up.
|
||||
* A further key limitation is that lookup methods do not work with factory methods and
|
||||
in particular not with `@Bean` methods in configuration classes, since, in that case,
|
||||
the container is not in charge of creating the instance and therefore cannot create
|
||||
@@ -293,11 +291,6 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
Note that you should typically declare such annotated lookup methods with a concrete
|
||||
stub implementation, in order for them to be compatible with Spring's component
|
||||
scanning rules where abstract classes get ignored by default. This limitation does not
|
||||
apply to explicitly registered or explicitly imported bean classes.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
Another way of accessing differently scoped target beans is an `ObjectFactory`/
|
||||
|
||||
@@ -1,57 +1,184 @@
|
||||
[[null-safety]]
|
||||
= Null-safety
|
||||
|
||||
Although Java does not let you express null-safety with its type system, the Spring Framework
|
||||
provides the following annotations in the `org.springframework.lang` package to let you
|
||||
declare nullability of APIs and fields:
|
||||
Although Java does not let you express null-safety with its type system, the Spring Framework codebase is annotated with
|
||||
https://jspecify.dev/docs/start-here/[JSpecify] annotations to declare the nullness of APIs, fields and related type
|
||||
usages. Reading the https://jspecify.dev/docs/user-guide/[JSpecify user guide] is highly recommended in order to get
|
||||
familiar with those annotations and semantics.
|
||||
|
||||
* {spring-framework-api}/lang/Nullable.html[`@Nullable`]: Annotation to indicate that a
|
||||
specific parameter, return value, or field can be `null`.
|
||||
* {spring-framework-api}/lang/NonNull.html[`@NonNull`]: Annotation to indicate that a specific
|
||||
parameter, return value, or field cannot be `null` (not needed on parameters, return values,
|
||||
and fields where `@NonNullApi` and `@NonNullFields` apply, respectively).
|
||||
* {spring-framework-api}/lang/NonNullApi.html[`@NonNullApi`]: Annotation at the package level
|
||||
that declares non-null as the default semantics for parameters and return values.
|
||||
* {spring-framework-api}/lang/NonNullFields.html[`@NonNullFields`]: Annotation at the package
|
||||
level that declares non-null as the default semantics for fields.
|
||||
The primary goal of this explicit null-safety arrangement is to prevent `NullPointerException` to be thrown at runtime via
|
||||
build time checks and to turn explicit nullness into a way to express the possible absence of value. It is useful in
|
||||
both Java by leveraging some tooling (https://github.com/uber/NullAway[NullAway] or IDEs supporting null-safety
|
||||
annotations such as IntelliJ IDEA or Eclipse) and Kotlin where JSpecify annotations are automatically translated to
|
||||
{kotlin-docs}/null-safety.html[Kotlin's null safety].
|
||||
|
||||
The Spring Framework itself leverages these annotations, but they can also be used in any
|
||||
Spring-based Java project to declare null-safe APIs and optionally null-safe fields.
|
||||
Nullability declarations for generic type arguments, varargs, and array elements are not supported yet.
|
||||
Nullability declarations are expected to be fine-tuned between Spring Framework releases,
|
||||
including minor ones. Nullability of types used inside method bodies is outside the
|
||||
scope of this feature.
|
||||
The {spring-framework-api}/core/Nullness.html[`Nullness` Spring API] can be used at runtime to detect the nullness of a
|
||||
type usage, a field, a method return type or a parameter. It provides full support for JSpecify annotations,
|
||||
Kotlin null safety, Java primitive types, as well as a pragmatic check on any `@Nullable` annotation (regardless of the
|
||||
package).
|
||||
|
||||
NOTE: Other common libraries such as Reactor and Spring Data provide null-safe APIs that
|
||||
use a similar nullability arrangement, delivering a consistent overall experience for
|
||||
Spring application developers.
|
||||
[[null-safety-libraries]]
|
||||
== Annotating libraries with JSpecify annotations
|
||||
|
||||
As of Spring Framework 7, the Spring Framework codebase leverages JSpecify annotations to expose null-safe APIs and
|
||||
to check the consistency of those null-safety declarations with https://github.com/uber/NullAway[NullAway] as part of
|
||||
its build. It is recommended for each library depending on Spring Framework (Spring portfolio projects), as
|
||||
well as other libraries related to the Spring ecosystem (Reactor, Micrometer and Spring community projects), to do the
|
||||
same.
|
||||
|
||||
[[null-safety-applications]]
|
||||
== Leveraging JSpecify annotations in Spring applications
|
||||
|
||||
Developing applications with IDEs supporting null-safety annotations, such as IntelliJ IDEA or Eclipse, will provide
|
||||
warnings in Java and errors in Kotlin when the null-safety contracts are not honored, allowing Spring application
|
||||
developers to refine their null handling to prevent `NullPointerException` to be thrown at runtime.
|
||||
|
||||
Optionally, Spring application developers can annotate their codebase and use https://github.com/uber/NullAway[NullAway]
|
||||
to enforce null-safety during build time at application level.
|
||||
|
||||
[[null-safety-guidelines]]
|
||||
== Guidelines
|
||||
|
||||
The purpose of this section is to share some guidelines proposed for specifying explicitly the nullness of Spring-related
|
||||
libraries or applications.
|
||||
|
||||
|
||||
[[null-safety-guidelines-jpecify]]
|
||||
=== JSpecify
|
||||
|
||||
The key points to understand is that by default, the nullness of types is unknown in Java, and that non-null type
|
||||
usages are by far more frequent than nullable ones. In order to keep codebases readable, we typically want to define
|
||||
that by default, type usages are non-null unless marked as nullable for a specific scope. This is exactly the purpose of
|
||||
https://jspecify.dev/docs/api/org/jspecify/annotations/NullMarked.html[`@NullMarked`] that is typically set with Spring
|
||||
at package level via a `package-info.java` file, for example:
|
||||
|
||||
[source,java,subs="verbatim,quotes",chomp="-packages",fold="none"]
|
||||
----
|
||||
@NullMarked
|
||||
package org.springframework.core;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
----
|
||||
|
||||
In the various Java files belonging to the package, nullable type usages are defined explicitly with
|
||||
https://jspecify.dev/docs/api/org/jspecify/annotations/Nullable.html[`@Nullable`]. It is recommended that this
|
||||
annotation is specified just before the related type.
|
||||
|
||||
For example, for a field:
|
||||
|
||||
[source,java,subs="verbatim,quotes"]
|
||||
----
|
||||
private @Nullable String fileEncoding;
|
||||
----
|
||||
|
||||
Or for method parameters and return value:
|
||||
|
||||
[source,java,subs="verbatim,quotes"]
|
||||
----
|
||||
public static @Nullable String buildMessage(@Nullable String message,
|
||||
@Nullable Throwable cause) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
When overriding a method, nullness annotations are not inherited from the superclass method. That means those
|
||||
nullness annotations should be repeated if you just want to override the implementation and keep the same API
|
||||
nullness.
|
||||
|
||||
With arrays and varargs, you need to be able to differentiate the nullness of the elements from the nullness of
|
||||
the array itself. Pay attention to the syntax
|
||||
https://docs.oracle.com/javase/specs/jls/se17/html/jls-9.html#jls-9.7.4[defined by the Java specification] which may be
|
||||
initially surprising:
|
||||
|
||||
- `@Nullable Object[] array` means individual elements can be null but the array itself can't.
|
||||
- `Object @Nullable [] array` means individual elements can't be null but the array itself can.
|
||||
- `@Nullable Object @Nullable [] array` means both individual elements and the array can be null.
|
||||
|
||||
The Java specifications also enforces that annotations defined with `@Target(ElementType.TYPE_USE)` like JSpecify
|
||||
`@Nullable` should be specified after the last `.` with inner or fully qualified types:
|
||||
|
||||
- `Cache.@Nullable ValueWrapper`
|
||||
- `jakarta.validation.@Nullable Validator`
|
||||
|
||||
https://jspecify.dev/docs/api/org/jspecify/annotations/NonNull.html[`@NonNull`] and
|
||||
https://jspecify.dev/docs/api/org/jspecify/annotations/NullUnmarked.html[`@NullUnmarked`] should rarely be needed for
|
||||
typical use cases.
|
||||
|
||||
[[null-safety-guidelines-nullaway]]
|
||||
=== NullAway
|
||||
|
||||
==== Configuration
|
||||
|
||||
The recommended configuration is:
|
||||
|
||||
- `NullAway:OnlyNullMarked=true` in order to perform nullness checks only for packages annotated with `@NullMarked`.
|
||||
- `NullAway:CustomContractAnnotations=org.springframework.lang.Contract` which makes NullAway aware of the
|
||||
{spring-framework-api}/lang/Contract.html[@Contract] annotation in the `org.springframework.lang` package which
|
||||
can be used to express complementary semantics to avoid non-relevant null-safety warnings in your codebase.
|
||||
|
||||
A good example of `@Contract` benefits is
|
||||
{spring-framework-api}/util/Assert.html#notNull(java.lang.Object,java.lang.String)[`Assert#notnull`] which is annotated
|
||||
with `@Contract("null, _ -> fail")`. With the configuration above, NullAway will understand that after a successful
|
||||
invocation, the value passed as a parameter is not null.
|
||||
|
||||
Optionally, it is possible to set `NullAway:JSpecifyMode=true` to enable
|
||||
https://github.com/uber/NullAway/wiki/JSpecify-Support[checks on the full JSpecify semantics], including annotations on
|
||||
generic types. Be aware that this mode is
|
||||
https://github.com/uber/NullAway/issues?q=is%3Aissue+is%3Aopen+label%3Ajspecify[still under development] and requires
|
||||
using JDK 22 or later (typically combined with the `--release` Java compiler flag to configure the
|
||||
expected baseline). It is recommended to enable the JSpecify mode only as a second step, after making sure the codebase
|
||||
generates no warning with the recommended configuration mentioned above.
|
||||
|
||||
==== Warnings suppression
|
||||
|
||||
There are a few valid use cases where NullAway will wrongly detect nullness problems. In such case, it is recommended
|
||||
to suppress related warnings and to document the reason:
|
||||
|
||||
- `@SuppressWarnings("NullAway.Init")` at field, constructor or class level can be used to avoid unnecessary warnings
|
||||
due to the lazy initialization of fields, for example due to a class implementing
|
||||
{spring-framework-api}/beans/factory/InitializingBean.html[`InitializingBean`].
|
||||
- `@SuppressWarnings("NullAway") // Dataflow analysis limitation` can be used when NullAway dataflow analysis is not
|
||||
able to detect that the path involving a nullness problem will never happen.
|
||||
- `@SuppressWarnings("NullAway") // Lambda` can be used when NullAway does not take into account assertions performed
|
||||
outside of a lambda for the code path within the lambda.
|
||||
- `@SuppressWarnings("NullAway") // Reflection` can be used for some reflection operations that are known returning
|
||||
non-null values even if that can't be expressed by the API.
|
||||
- `@SuppressWarnings("NullAway") // Well-known map keys` can be used when `Map#get` invocations are done with keys known
|
||||
to be present and non-null related values inserted previously.
|
||||
- `@SuppressWarnings("NullAway") // Overridden method does not define nullness` can be used when the super class does
|
||||
not define nullness (typically when the super class is coming from a dependency).
|
||||
|
||||
|
||||
[[use-cases]]
|
||||
== Use cases
|
||||
[[null-safety-migrating]]
|
||||
== Migrating from Spring null-safety annotations
|
||||
|
||||
In addition to providing an explicit declaration for Spring Framework API nullability,
|
||||
these annotations can be used by an IDE (such as IDEA or Eclipse) to provide useful
|
||||
warnings related to null-safety in order to avoid `NullPointerException` at runtime.
|
||||
Spring null-safety annotations {spring-framework-api}/lang/Nullable.html[`@Nullable`],
|
||||
{spring-framework-api}/lang/NonNull.html[`@NonNull`],
|
||||
{spring-framework-api}/lang/NonNullApi.html[`@NonNullApi`], and
|
||||
{spring-framework-api}/lang/NonNullFields.html[`@NonNullFields`] in the `org.springframework.lang` package have been
|
||||
introduced in Spring Framework 5 when JSpecify did not exist and the best option was to leverage JSR 305 (a dormant
|
||||
but widespread JSR) meta-annotations. They are deprecated as of Spring Framework 7 in favor of
|
||||
https://jspecify.dev/docs/start-here/[JSpecify] annotations, which provide significant enhancements such as properly
|
||||
defined specifications, a canonical dependency with no split-package issue, better tooling, better Kotlin integration
|
||||
and the capability to specify the nullness more precisely for more use cases.
|
||||
|
||||
They are also used to make Spring APIs null-safe in Kotlin projects, since Kotlin natively
|
||||
supports {kotlin-docs}/null-safety.html[null-safety]. More details
|
||||
are available in the xref:languages/kotlin/null-safety.adoc[Kotlin support documentation].
|
||||
A key difference is that Spring null-safety annotations, following JSR 305 semantics, apply to fields,
|
||||
parameters and return values while JSpecify annotations apply to type usages. This subtle difference
|
||||
is in practice pretty significant, as it allows for example to differentiate the nullness of elements from the
|
||||
nullness of arrays/varargs as well as defining the nullness of generic types.
|
||||
|
||||
That means array and varargs null-safety declarations have to be updated to keep the same semantic. For example
|
||||
`@Nullable Object[] array` with Spring annotations needs to be changed to `Object @Nullable [] array` with JSpecify
|
||||
annotations. Same for varargs.
|
||||
|
||||
It is also recommended to move field and return value annotations closer to the type, for example:
|
||||
|
||||
- For fields, instead of `@Nullable private String field` with Spring annotations, use `private @Nullable String field`
|
||||
with JSpecify annotations.
|
||||
- For return values, instead of `@Nullable public String method()` with Spring annotations, use
|
||||
`public @Nullable String method()` with JSpecify annotations.
|
||||
|
||||
[[jsr-305-meta-annotations]]
|
||||
== JSR-305 meta-annotations
|
||||
Also, with JSpecify, you don't need to specify `@NonNull` when overriding a type usage annotated with `@Nullable` in the
|
||||
super method to "undo" the nullable declaration in null-marked code. Just declare it unannotated and the null-marked
|
||||
defaults (a type usage is considered non-null unless explicitly annotated as nullable) will apply.
|
||||
|
||||
Spring annotations are meta-annotated with {JSR}305[JSR 305]
|
||||
annotations (a dormant but widespread JSR). JSR-305 meta-annotations let tooling vendors
|
||||
like IDEA or Kotlin provide null-safety support in a generic way, without having to
|
||||
hard-code support for Spring annotations.
|
||||
|
||||
It is neither necessary nor recommended to add a JSR-305 dependency to the project classpath to
|
||||
take advantage of Spring's null-safe APIs. Only projects such as Spring-based libraries that use
|
||||
null-safety annotations in their codebase should add `com.google.code.findbugs:jsr305:3.0.2`
|
||||
with `compileOnly` Gradle configuration or Maven `provided` scope to avoid compiler warnings.
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
[[spring-jcl]]
|
||||
= Logging
|
||||
|
||||
Spring comes with its own Commons Logging bridge implemented
|
||||
in the `spring-jcl` module. The implementation checks for the presence of the Log4j 2.x
|
||||
API and the SLF4J 1.7 API in the classpath and uses the first one of those found as the
|
||||
logging implementation, falling back to the Java platform's core logging facilities (also
|
||||
known as _JUL_ or `java.util.logging`) if neither Log4j 2.x nor SLF4J is available.
|
||||
|
||||
Put Log4j 2.x or Logback (or another SLF4J provider) in your classpath, without any extra
|
||||
bridges, and let the framework auto-adapt to your choice. For further information see the
|
||||
{spring-boot-docs-ref}/features/logging.html[Spring
|
||||
Boot Logging Reference Documentation].
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Spring's Commons Logging variant is only meant to be used for infrastructure logging
|
||||
purposes in the core framework and in extensions.
|
||||
|
||||
For logging needs within application code, prefer direct use of Log4j 2.x, SLF4J, or JUL.
|
||||
====
|
||||
|
||||
A `Log` implementation may be retrieved via `org.apache.commons.logging.LogFactory` as in
|
||||
the following example.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public class MyBean {
|
||||
private final Log log = LogFactory.getLog(getClass());
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
class MyBean {
|
||||
private val log = LogFactory.getLog(javaClass)
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
@@ -7,7 +7,7 @@ xref:overview.adoc[Overview] :: History, Design Philosophy, Feedback,
|
||||
Getting Started.
|
||||
xref:core.adoc[Core] :: IoC Container, Events, Resources, i18n,
|
||||
Validation, Data Binding, Type Conversion, SpEL, AOP, AOT.
|
||||
<<testing.adoc#testing, Testing>> :: Mock Objects, TestContext Framework,
|
||||
xref:testing.adoc[Testing] :: Mock Objects, TestContext Framework,
|
||||
Spring MVC Test, WebTestClient.
|
||||
xref:data-access.adoc[Data Access] :: Transactions, DAO Support,
|
||||
JDBC, R2DBC, O/R Mapping, XML Marshalling.
|
||||
|
||||
@@ -11,9 +11,7 @@ Spring Framework's email support:
|
||||
* The https://jakartaee.github.io/mail-api/[Jakarta Mail] library
|
||||
|
||||
This library is freely available on the web -- for example, in Maven Central as
|
||||
`com.sun.mail:jakarta.mail`. Please make sure to use the latest 2.x version (which uses
|
||||
the `jakarta.mail` package namespace) rather than Jakarta Mail 1.6.x (which uses the
|
||||
`javax.mail` package namespace).
|
||||
`org.eclipse.angus:angus-mail`.
|
||||
****
|
||||
|
||||
The Spring Framework provides a helpful utility library for sending email that shields
|
||||
|
||||
@@ -115,11 +115,15 @@ Finally, the body can be set to a callback function that writes to an `OutputStr
|
||||
|
||||
==== Retrieving the response
|
||||
|
||||
Once the request has been set up, the HTTP response is accessed by invoking `retrieve()`.
|
||||
The response body can be accessed by using `body(Class)` or `body(ParameterizedTypeReference)` for parameterized types like lists.
|
||||
Once the request has been set up, it can be sent by chaining method calls after `retrieve()`.
|
||||
For example, the response body can be accessed by using `retrieve().body(Class)` or `retrieve().body(ParameterizedTypeReference)` for parameterized types like lists.
|
||||
The `body` method converts the response contents into various types – for instance, bytes can be converted into a `String`, JSON can be converted into objects using Jackson, and so on (see <<rest-message-conversion>>).
|
||||
|
||||
The response can also be converted into a `ResponseEntity`, giving access to the response headers as well as the body.
|
||||
The response can also be converted into a `ResponseEntity`, giving access to the response headers as well as the body, with `retrieve().toEntity(Class)`
|
||||
|
||||
NOTE: Calling `retrieve()` by itself is a no-op and returns a `ResponseSpec`.
|
||||
Applications must invoke a terminal operation on the `ResponseSpec` to have any side effect.
|
||||
If consuming the response has no interest for your use case, you can use `retrieve().toBodilessEntity()`.
|
||||
|
||||
This sample shows how `RestClient` can be used to perform a simple `GET` request.
|
||||
|
||||
@@ -988,6 +992,27 @@ parameter annotation) is set to `false`, or the parameter is marked optional as
|
||||
|
||||
|
||||
|
||||
[[rest-http-interface.custom-resolver]]
|
||||
=== Custom argument resolver
|
||||
|
||||
For more complex cases, HTTP interfaces do not support `RequestEntity` types as method parameters.
|
||||
This would take over the entire HTTP request and not improve the semantics of the interface.
|
||||
Instead of adding many method parameters, developers can combine them into a custom type
|
||||
and configure a dedicated `HttpServiceArgumentResolver` implementation.
|
||||
|
||||
In the following HTTP interface, we are using a custom `Search` type as a parameter:
|
||||
|
||||
include-code::./CustomHttpServiceArgumentResolver[tag=httpinterface,indent=0]
|
||||
|
||||
We can implement our own `HttpServiceArgumentResolver` that supports our custom `Search` type
|
||||
and writes its data in the outgoing HTTP request.
|
||||
|
||||
include-code::./CustomHttpServiceArgumentResolver[tag=argumentresolver,indent=0]
|
||||
|
||||
Finally, we can use this argument resolver during the setup and use our HTTP interface.
|
||||
|
||||
include-code::./CustomHttpServiceArgumentResolver[tag=usage,indent=0]
|
||||
|
||||
[[rest-http-interface-return-values]]
|
||||
=== Return Values
|
||||
|
||||
|
||||
@@ -5,34 +5,11 @@ One of Kotlin's key features is {kotlin-docs}/null-safety.html[null-safety],
|
||||
which cleanly deals with `null` values at compile time rather than bumping into the famous
|
||||
`NullPointerException` at runtime. This makes applications safer through nullability
|
||||
declarations and expressing "`value or no value`" semantics without paying the cost of wrappers, such as `Optional`.
|
||||
(Kotlin allows using functional constructs with nullable values. See this
|
||||
{baeldung-blog}/kotlin-null-safety[comprehensive guide to Kotlin null-safety].)
|
||||
Kotlin allows using functional constructs with nullable values. See this
|
||||
{baeldung-blog}/kotlin-null-safety[comprehensive guide to Kotlin null-safety].
|
||||
|
||||
Although Java does not let you express null-safety in its type-system, the Spring Framework
|
||||
provides xref:languages/kotlin/null-safety.adoc[null-safety of the whole Spring Framework API]
|
||||
via tooling-friendly annotations declared in the `org.springframework.lang` package.
|
||||
By default, types from Java APIs used in Kotlin are recognized as
|
||||
{kotlin-docs}/java-interop.html#null-safety-and-platform-types[platform types],
|
||||
for which null-checks are relaxed.
|
||||
{kotlin-docs}/java-interop.html#jsr-305-support[Kotlin support for JSR-305 annotations]
|
||||
and Spring nullability annotations provide null-safety for the whole Spring Framework API to Kotlin developers,
|
||||
with the advantage of dealing with `null`-related issues at compile time.
|
||||
|
||||
NOTE: Libraries such as Reactor or Spring Data provide null-safe APIs to leverage this feature.
|
||||
|
||||
You can configure JSR-305 checks by adding the `-Xjsr305` compiler flag with the following
|
||||
options: `-Xjsr305={strict|warn|ignore}`.
|
||||
|
||||
For kotlin versions 1.1+, the default behavior is the same as `-Xjsr305=warn`.
|
||||
The `strict` value is required to have Spring Framework API null-safety taken into account
|
||||
in Kotlin types inferred from Spring API but should be used with the knowledge that Spring
|
||||
API nullability declaration could evolve even between minor releases and that more checks may
|
||||
be added in the future.
|
||||
|
||||
NOTE: Generic type arguments, varargs, and array elements nullability are not supported yet,
|
||||
but should be in an upcoming release. See {kotlin-github-org}/KEEP/issues/79[this discussion]
|
||||
for up-to-date information.
|
||||
|
||||
|
||||
|
||||
provides xref:core/null-safety.adoc[null-safety of the whole Spring Framework API]
|
||||
via tooling-friendly https://jspecify.dev/[JSpecify] annotations.
|
||||
|
||||
As of Kotlin 2.1, Kotlin enforces strict handling of nullability annotations from `org.jspecify.annotations` package.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
= Requirements
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
Spring Framework supports Kotlin 1.7+ and requires
|
||||
Spring Framework supports Kotlin 2.1+ and requires
|
||||
https://search.maven.org/artifact/org.jetbrains.kotlin/kotlin-stdlib[`kotlin-stdlib`]
|
||||
and https://search.maven.org/artifact/org.jetbrains.kotlin/kotlin-reflect[`kotlin-reflect`]
|
||||
to be present on the classpath. They are provided by default if you bootstrap a Kotlin project on
|
||||
|
||||
@@ -75,54 +75,6 @@ mockMvc.get("/person/{name}", "Lee") {
|
||||
|
||||
|
||||
|
||||
[[kotlin-script-templates]]
|
||||
== Kotlin Script Templates
|
||||
|
||||
Spring Framework provides a
|
||||
{spring-framework-api}/web/servlet/view/script/ScriptTemplateView.html[`ScriptTemplateView`]
|
||||
which supports {JSR}223[JSR-223] to render templates by using script engines.
|
||||
|
||||
By leveraging `scripting-jsr223` dependencies, it
|
||||
is possible to use such feature to render Kotlin-based templates with
|
||||
{kotlin-github-org}/kotlinx.html[kotlinx.html] DSL or Kotlin multiline interpolated `String`.
|
||||
|
||||
`build.gradle.kts`
|
||||
[source,kotlin,indent=0]
|
||||
----
|
||||
dependencies {
|
||||
runtime("org.jetbrains.kotlin:kotlin-scripting-jsr223:${kotlinVersion}")
|
||||
}
|
||||
----
|
||||
|
||||
Configuration is usually done with `ScriptTemplateConfigurer` and `ScriptTemplateViewResolver` beans.
|
||||
|
||||
`KotlinScriptConfiguration.kt`
|
||||
[source,kotlin,indent=0]
|
||||
----
|
||||
@Configuration
|
||||
class KotlinScriptConfiguration {
|
||||
|
||||
@Bean
|
||||
fun kotlinScriptConfigurer() = ScriptTemplateConfigurer().apply {
|
||||
engineName = "kotlin"
|
||||
setScripts("scripts/render.kts")
|
||||
renderFunction = "render"
|
||||
isSharedEngine = false
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun kotlinScriptViewResolver() = ScriptTemplateViewResolver().apply {
|
||||
setPrefix("templates/")
|
||||
setSuffix(".kts")
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
See the https://github.com/sdeleuze/kotlin-script-templating[kotlin-script-templating] example
|
||||
project for more details.
|
||||
|
||||
|
||||
|
||||
[[kotlin-multiplatform-serialization]]
|
||||
== Kotlin multiplatform serialization
|
||||
|
||||
|
||||
+3
-3
@@ -171,9 +171,9 @@ the parameters of a test class constructor are autowired from components in the
|
||||
|
||||
If `@TestConstructor` is not present or meta-present on a test class, the default _test
|
||||
constructor autowire mode_ will be used. See the tip below for details on how to change
|
||||
the default mode. Note, however, that a local declaration of `@Autowired`,
|
||||
`@jakarta.inject.Inject`, or `@javax.inject.Inject` on a constructor takes precedence
|
||||
over both `@TestConstructor` and the default mode.
|
||||
the default mode. Note, however, that a local declaration of `@Autowired` or
|
||||
`@jakarta.inject.Inject` on a constructor takes precedence over both `@TestConstructor`
|
||||
and the default mode.
|
||||
|
||||
.Changing the default test constructor autowire mode
|
||||
[TIP]
|
||||
|
||||
+106
-54
@@ -1,34 +1,62 @@
|
||||
[[spring-testing-annotation-beanoverriding-mockitobean]]
|
||||
= `@MockitoBean` and `@MockitoSpyBean`
|
||||
|
||||
`@MockitoBean` and `@MockitoSpyBean` are used on non-static fields in test classes to
|
||||
override beans in the test's `ApplicationContext` with a Mockito _mock_ or _spy_,
|
||||
respectively. In the latter case, an early instance of the original bean is captured and
|
||||
wrapped by the spy.
|
||||
{spring-framework-api}/test/context/bean/override/mockito/MockitoBean.html[`@MockitoBean`] and
|
||||
{spring-framework-api}/test/context/bean/override/mockito/MockitoSpyBean.html[`@MockitoSpyBean`]
|
||||
can be used in test classes to override a bean in the test's `ApplicationContext` with a
|
||||
Mockito _mock_ or _spy_, respectively. In the latter case, an early instance of the
|
||||
original bean is captured and wrapped by the spy.
|
||||
|
||||
By default, the annotated field's type is used to search for candidate beans to override.
|
||||
If multiple candidates match, `@Qualifier` can be provided to narrow the candidate to
|
||||
override. Alternatively, a candidate whose bean name matches the name of the field will
|
||||
match.
|
||||
The annotations can be applied in the following ways.
|
||||
|
||||
* On a non-static field in a test class or any of its superclasses.
|
||||
* On a non-static field in an enclosing class for a `@Nested` test class or in any class
|
||||
in the type hierarchy or enclosing class hierarchy above the `@Nested` test class.
|
||||
* At the type level on a test class or any superclass or implemented interface in the
|
||||
type hierarchy above the test class.
|
||||
* At the type level on an enclosing class for a `@Nested` test class or on any class or
|
||||
interface in the type hierarchy or enclosing class hierarchy above the `@Nested` test
|
||||
class.
|
||||
|
||||
When `@MockitoBean` or `@MockitoSpyBean` is declared on a field, the bean to mock or spy
|
||||
is inferred from the type of the annotated field. If multiple candidates exist in the
|
||||
`ApplicationContext`, a `@Qualifier` annotation can be declared on the field to help
|
||||
disambiguate. In the absence of a `@Qualifier` annotation, the name of the annotated
|
||||
field will be used as a _fallback qualifier_. Alternatively, you can explicitly specify a
|
||||
bean name to mock or spy by setting the `value` or `name` attribute in the annotation.
|
||||
|
||||
When `@MockitoBean` or `@MockitoSpyBean` is declared at the type level, the type of bean
|
||||
(or beans) to mock or spy must be supplied via the `types` attribute in the annotation –
|
||||
for example, `@MockitoBean(types = {OrderService.class, UserService.class})`. If multiple
|
||||
candidates exist in the `ApplicationContext`, you can explicitly specify a bean name to
|
||||
mock or spy by setting the `name` attribute. Note, however, that the `types` attribute
|
||||
must contain a single type if an explicit bean `name` is configured – for example,
|
||||
`@MockitoBean(name = "ps1", types = PrintingService.class)`.
|
||||
|
||||
To support reuse of mock configuration, `@MockitoBean` and `@MockitoSpyBean` may be used
|
||||
as meta-annotations to create custom _composed annotations_ – for example, to define
|
||||
common mock or spy configuration in a single annotation that can be reused across a test
|
||||
suite. `@MockitoBean` and `@MockitoSpyBean` can also be used as repeatable annotations at
|
||||
the type level — for example, to mock or spy several beans by name.
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
Qualifiers, including the name of the field, are used to determine if a separate
|
||||
Qualifiers, including the name of a field, are used to determine if a separate
|
||||
`ApplicationContext` needs to be created. If you are using this feature to mock or spy
|
||||
the same bean in several test classes, make sure to name the field consistently to avoid
|
||||
the same bean in several test classes, make sure to name the fields consistently to avoid
|
||||
creating unnecessary contexts.
|
||||
====
|
||||
|
||||
Each annotation also defines Mockito-specific attributes to fine-tune the mocking behavior.
|
||||
|
||||
The `@MockitoBean` annotation uses the `REPLACE_OR_CREATE`
|
||||
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-custom[strategy for test bean overriding].
|
||||
If no existing bean matches, a new bean is created on the fly. However, you can switch to
|
||||
the `REPLACE` strategy by setting the `enforceOverride` attribute to `true`. See the
|
||||
following section for an example.
|
||||
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-strategy[strategy for bean overrides].
|
||||
If a corresponding bean does not exist, a new bean will be created. However, you can
|
||||
switch to the `REPLACE` strategy by setting the `enforceOverride` attribute to `true` –
|
||||
for example, `@MockitoBean(enforceOverride = true)`.
|
||||
|
||||
The `@MockitoSpyBean` annotation uses the `WRAP`
|
||||
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-custom[strategy],
|
||||
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-strategy[strategy],
|
||||
and the original instance is wrapped in a Mockito spy. This strategy requires that
|
||||
exactly one candidate bean exists.
|
||||
|
||||
@@ -56,15 +84,8 @@ or `private` depending on the needs or coding practices of the project.
|
||||
[[spring-testing-annotation-beanoverriding-mockitobean-examples]]
|
||||
== `@MockitoBean` Examples
|
||||
|
||||
When using `@MockitoBean`, a new bean will be created if a corresponding bean does not
|
||||
exist. However, if you would like for the test to fail when a corresponding bean does not
|
||||
exist, you can set the `enforceOverride` attribute to `true` – for example,
|
||||
`@MockitoBean(enforceOverride = true)`.
|
||||
|
||||
To use a by-name override rather than a by-type override, specify the `name` (or `value`)
|
||||
attribute of the annotation.
|
||||
|
||||
The following example shows how to use the default behavior of the `@MockitoBean` annotation:
|
||||
The following example shows how to use the default behavior of the `@MockitoBean`
|
||||
annotation.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -81,7 +102,7 @@ Java::
|
||||
// tests...
|
||||
}
|
||||
----
|
||||
<1> Replace the bean with type `CustomService` with a Mockito `mock`.
|
||||
<1> Replace the bean with type `CustomService` with a Mockito mock.
|
||||
======
|
||||
|
||||
In the example above, we are creating a mock for `CustomService`. If more than one bean
|
||||
@@ -90,7 +111,8 @@ will fail, and you will need to provide a qualifier of some sort to identify whi
|
||||
`CustomService` beans you want to override. If no such bean exists, a bean will be
|
||||
created with an auto-generated bean name.
|
||||
|
||||
The following example uses a by-name lookup, rather than a by-type lookup:
|
||||
The following example uses a by-name lookup, rather than a by-type lookup. If no bean
|
||||
named `service` exists, one is created.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -108,32 +130,9 @@ Java::
|
||||
|
||||
}
|
||||
----
|
||||
<1> Replace the bean named `service` with a Mockito `mock`.
|
||||
<1> Replace the bean named `service` with a Mockito mock.
|
||||
======
|
||||
|
||||
If no bean named `service` exists, one is created.
|
||||
|
||||
`@MockitoBean` can also be used at the type level:
|
||||
|
||||
- on a test class or any superclass or implemented interface in the type hierarchy above
|
||||
the test class
|
||||
- on an enclosing class for a `@Nested` test class or on any class or interface in the
|
||||
type hierarchy or enclosing class hierarchy above the `@Nested` test class
|
||||
|
||||
When `@MockitoBean` is declared at the type level, the type of bean (or beans) to mock
|
||||
must be supplied via the `types` attribute – for example,
|
||||
`@MockitoBean(types = {OrderService.class, UserService.class})`. If multiple candidates
|
||||
exist in the application context, you can explicitly specify a bean name to mock by
|
||||
setting the `name` attribute. Note, however, that the `types` attribute must contain a
|
||||
single type if an explicit bean `name` is configured – for example,
|
||||
`@MockitoBean(name = "ps1", types = PrintingService.class)`.
|
||||
|
||||
To support reuse of mock configuration, `@MockitoBean` may be used as a meta-annotation
|
||||
to create custom _composed annotations_ — for example, to define common mock
|
||||
configuration in a single annotation that can be reused across a test suite.
|
||||
`@MockitoBean` can also be used as a repeatable annotation at the type level — for
|
||||
example, to mock several beans by name.
|
||||
|
||||
The following `@SharedMocks` annotation registers two mocks by-type and one mock by-name.
|
||||
|
||||
[tabs]
|
||||
@@ -191,7 +190,7 @@ APIs.
|
||||
== `@MockitoSpyBean` Examples
|
||||
|
||||
The following example shows how to use the default behavior of the `@MockitoSpyBean`
|
||||
annotation:
|
||||
annotation.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -208,7 +207,7 @@ Java::
|
||||
// tests...
|
||||
}
|
||||
----
|
||||
<1> Wrap the bean with type `CustomService` with a Mockito `spy`.
|
||||
<1> Wrap the bean with type `CustomService` with a Mockito spy.
|
||||
======
|
||||
|
||||
In the example above, we are wrapping the bean with type `CustomService`. If more than
|
||||
@@ -216,7 +215,7 @@ one bean of that type exists, the bean named `customService` is considered. Othe
|
||||
the test will fail, and you will need to provide a qualifier of some sort to identify
|
||||
which of the `CustomService` beans you want to spy.
|
||||
|
||||
The following example uses a by-name lookup, rather than a by-type lookup:
|
||||
The following example uses a by-name lookup, rather than a by-type lookup.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -233,5 +232,58 @@ Java::
|
||||
// tests...
|
||||
}
|
||||
----
|
||||
<1> Wrap the bean named `service` with a Mockito `spy`.
|
||||
<1> Wrap the bean named `service` with a Mockito spy.
|
||||
======
|
||||
|
||||
The following `@SharedSpies` annotation registers two spies by-type and one spy by-name.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@MockitoSpyBean(types = {OrderService.class, UserService.class}) // <1>
|
||||
@MockitoSpyBean(name = "ps1", types = PrintingService.class) // <2>
|
||||
public @interface SharedSpies {
|
||||
}
|
||||
----
|
||||
<1> Register `OrderService` and `UserService` spies by-type.
|
||||
<2> Register `PrintingService` spy by-name.
|
||||
======
|
||||
|
||||
The following demonstrates how `@SharedSpies` can be used on a test class.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@SpringJUnitConfig(TestConfig.class)
|
||||
@SharedSpies // <1>
|
||||
class BeanOverrideTests {
|
||||
|
||||
@Autowired OrderService orderService; // <2>
|
||||
|
||||
@Autowired UserService userService; // <2>
|
||||
|
||||
@Autowired PrintingService ps1; // <2>
|
||||
|
||||
// Inject other components that rely on the spies.
|
||||
|
||||
@Test
|
||||
void testThatDependsOnMocks() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Register common spies via the custom `@SharedSpies` annotation.
|
||||
<2> Optionally inject spies to _stub_ or _verify_ them.
|
||||
======
|
||||
|
||||
TIP: The spies can also be injected into `@Configuration` classes or other test-related
|
||||
components in the `ApplicationContext` in order to configure them with Mockito's stubbing
|
||||
APIs.
|
||||
|
||||
+3
-2
@@ -1,8 +1,9 @@
|
||||
[[spring-testing-annotation-beanoverriding-testbean]]
|
||||
= `@TestBean`
|
||||
|
||||
`@TestBean` is used on a non-static field in a test class to override a specific bean in
|
||||
the test's `ApplicationContext` with an instance provided by a factory method.
|
||||
{spring-framework-api}/test/context/bean/override/convention/TestBean.html[`@TestBean`]
|
||||
is used on a non-static field in a test class to override a specific bean in the test's
|
||||
`ApplicationContext` with an instance provided by a factory method.
|
||||
|
||||
The associated factory method name is derived from the annotated field's name, or the
|
||||
bean name if specified. The factory method must be `static`, accept no arguments, and
|
||||
|
||||
+7
-6
@@ -2,8 +2,8 @@
|
||||
= Bean Overriding in Tests
|
||||
|
||||
Bean overriding in tests refers to the ability to override specific beans in the
|
||||
`ApplicationContext` for a test class, by annotating one or more non-static fields in the
|
||||
test class.
|
||||
`ApplicationContext` for a test class, by annotating the test class or one or more
|
||||
non-static fields in the test class.
|
||||
|
||||
NOTE: This feature is intended as a less risky alternative to the practice of registering
|
||||
a bean via `@Bean` with the `DefaultListableBeanFactory`
|
||||
@@ -42,15 +42,16 @@ The `spring-test` module registers implementations of the latter two
|
||||
{spring-framework-code}/spring-test/src/main/resources/META-INF/spring.factories[`META-INF/spring.factories`
|
||||
properties file].
|
||||
|
||||
The bean overriding infrastructure searches in test classes for any non-static field that
|
||||
is meta-annotated with `@BeanOverride` and instantiates the corresponding
|
||||
`BeanOverrideProcessor` which is responsible for creating an appropriate
|
||||
`BeanOverrideHandler`.
|
||||
The bean overriding infrastructure searches for annotations on test classes as well as
|
||||
annotations on non-static fields in test classes that are meta-annotated with
|
||||
`@BeanOverride` and instantiates the corresponding `BeanOverrideProcessor` which is
|
||||
responsible for creating an appropriate `BeanOverrideHandler`.
|
||||
|
||||
The internal `BeanOverrideBeanFactoryPostProcessor` then uses bean override handlers to
|
||||
alter the test's `ApplicationContext` by creating, replacing, or wrapping beans as
|
||||
defined by the corresponding `BeanOverrideStrategy`:
|
||||
|
||||
[[testcontext-bean-overriding-strategy]]
|
||||
`REPLACE`::
|
||||
Replaces the bean. Throws an exception if a corresponding bean does not exist.
|
||||
`REPLACE_OR_CREATE`::
|
||||
|
||||
+1
-3
@@ -96,9 +96,7 @@ Kotlin::
|
||||
The following code snippet is similar to the one we saw earlier for a request-scoped
|
||||
bean. However, this time, the `userService` bean has a dependency on a session-scoped
|
||||
`userPreferences` bean. Note that the `UserPreferences` bean is instantiated by using a
|
||||
SpEL expression that retrieves the theme from the current HTTP session. In our test, we
|
||||
need to configure a theme in the mock session managed by the TestContext framework. The
|
||||
following example shows how to do so:
|
||||
SpEL expression that retrieves an attribute from the current HTTP session.
|
||||
|
||||
.Session-scoped bean configuration
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
|
||||
@@ -804,8 +804,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
ClassPathResource index = new ClassPathResource("static/index.html");
|
||||
List<String> extensions = List.of("js", "css", "ico", "png", "jpg", "gif");
|
||||
RequestPredicate spaPredicate = path("/api/**").or(path("/error")).or(pathExtension(extensions::contains)).negate();
|
||||
RequestPredicate spaPredicate = path("/api/**").or(path("/error")).negate();
|
||||
RouterFunction<ServerResponse> redirectToIndex = route()
|
||||
.resource(spaPredicate, index)
|
||||
.build();
|
||||
@@ -817,9 +816,7 @@ Kotlin::
|
||||
----
|
||||
val redirectToIndex = router {
|
||||
val index = ClassPathResource("static/index.html")
|
||||
val extensions = listOf("js", "css", "ico", "png", "jpg", "gif")
|
||||
val spaPredicate = !(path("/api/**") or path("/error") or
|
||||
pathExtension(extensions::contains))
|
||||
val spaPredicate = !(path("/api/**") or path("/error"))
|
||||
resource(spaPredicate, index)
|
||||
}
|
||||
----
|
||||
|
||||
@@ -466,7 +466,7 @@ Java::
|
||||
----
|
||||
@GetMapping
|
||||
FragmentsRendering handle() {
|
||||
return FragmentsRendering.with("posts").fragment("comments").build();
|
||||
return FragmentsRendering.fragment("posts").fragment("comments").build();
|
||||
}
|
||||
----
|
||||
|
||||
@@ -476,7 +476,7 @@ Kotlin::
|
||||
----
|
||||
@GetMapping
|
||||
fun handle(): FragmentsRendering {
|
||||
return FragmentsRendering.with("posts").fragment("comments").build()
|
||||
return FragmentsRendering.fragment("posts").fragment("comments").build()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
@@ -647,13 +647,12 @@ For https://www.webjars.org/documentation[WebJars], versioned URLs like
|
||||
`/webjars/jquery/1.2.0/jquery.min.js` are the recommended and most efficient way to use them.
|
||||
The related resource location is configured out of the box with Spring Boot (or can be configured
|
||||
manually via `ResourceHandlerRegistry`) and does not require to add the
|
||||
`org.webjars:webjars-locator-core` dependency.
|
||||
`org.webjars:webjars-locator-lite` dependency.
|
||||
|
||||
Version-less URLs like `/webjars/jquery/jquery.min.js` are supported through the
|
||||
`WebJarsResourceResolver` which is automatically registered when the
|
||||
`org.webjars:webjars-locator-core` library is present on the classpath, at the cost of a
|
||||
classpath scanning that could slow down application startup. The resolver can re-write URLs to
|
||||
include the version of the jar and can also match against incoming URLs without versions
|
||||
`org.webjars:webjars-locator-lite` library is present on the classpath. The resolver can re-write
|
||||
URLs to include the version of the jar and can also match against incoming URLs without versions
|
||||
-- for example, from `/webjars/jquery/jquery.min.js` to `/webjars/jquery/1.2.0/jquery.min.js`.
|
||||
|
||||
TIP: The Java configuration based on `ResourceHandlerRegistry` provides further options
|
||||
|
||||
@@ -783,7 +783,7 @@ Java::
|
||||
----
|
||||
ClassPathResource index = new ClassPathResource("static/index.html");
|
||||
List<String> extensions = List.of("js", "css", "ico", "png", "jpg", "gif");
|
||||
RequestPredicate spaPredicate = path("/api/**").or(path("/error")).or(pathExtension(extensions::contains)).negate();
|
||||
RequestPredicate spaPredicate = path("/api/**").or(path("/error")).negate();
|
||||
RouterFunction<ServerResponse> redirectToIndex = route()
|
||||
.resource(spaPredicate, index)
|
||||
.build();
|
||||
@@ -795,9 +795,7 @@ Kotlin::
|
||||
----
|
||||
val redirectToIndex = router {
|
||||
val index = ClassPathResource("static/index.html")
|
||||
val extensions = listOf("js", "css", "ico", "png", "jpg", "gif")
|
||||
val spaPredicate = !(path("/api/**") or path("/error") or
|
||||
pathExtension(extensions::contains))
|
||||
val spaPredicate = !(path("/api/**") or path("/error"))
|
||||
resource(spaPredicate, index)
|
||||
}
|
||||
----
|
||||
|
||||
@@ -48,7 +48,7 @@ Java::
|
||||
----
|
||||
@GetMapping
|
||||
FragmentsRendering handle() {
|
||||
return FragmentsRendering.with("posts").fragment("comments").build();
|
||||
return FragmentsRendering.fragment("posts").fragment("comments").build();
|
||||
}
|
||||
----
|
||||
|
||||
@@ -58,7 +58,7 @@ Kotlin::
|
||||
----
|
||||
@GetMapping
|
||||
fun handle(): FragmentsRendering {
|
||||
return FragmentsRendering.with("posts").fragment("comments").build()
|
||||
return FragmentsRendering.fragment("posts").fragment("comments").build()
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
@@ -118,12 +118,12 @@ See the sections on xref:web/webmvc-cors.adoc[CORS] and the xref:web/webmvc-cors
|
||||
[.small]#xref:web/webflux/reactive-spring.adoc#filters.url-handler[See equivalent in the Reactive stack]#
|
||||
|
||||
In previous Spring Framework versions, Spring MVC could be configured to ignore trailing slashes in URL paths
|
||||
when mapping incoming requests on controller methods. This could be done by enabling the `setUseTrailingSlashMatch`
|
||||
option on the `PathMatchConfigurer`. This means that sending a "GET /home/" request would be handled by a controller
|
||||
method annotated with `@GetMapping("/home")`.
|
||||
when mapping incoming requests on controller methods. This means that sending a "GET /home/" request would be
|
||||
handled by a controller method annotated with `@GetMapping("/home")`.
|
||||
|
||||
This option has been retired, but applications are still expected to handle such requests in a safe way.
|
||||
The `UrlHandlerFilter` Servlet filter has been designed for this purpose. It can be configured to:
|
||||
This option was deprecated in 6.0 and removed in 7.0, but applications are still expected to handle such
|
||||
requests in a safe way. The `UrlHandlerFilter` Servlet filter has been designed for this purpose.
|
||||
It can be configured to:
|
||||
|
||||
* respond with an HTTP redirect status when receiving URLs with trailing slashes, sending browsers to the non-trailing slash URL variant.
|
||||
* wrap the request to act as if the request was sent without a trailing slash and continue the processing of the request.
|
||||
|
||||
@@ -54,6 +54,11 @@ You can customize XML mapping as needed through the use of JAXB or Jackson's pro
|
||||
When you need further control (for cases where custom XML serializers/deserializers need to be provided for specific types), you can inject a custom `XmlMapper` through the `ObjectMapper` property.
|
||||
By default, this converter supports `application/xml`. This requires the `com.fasterxml.jackson.dataformat:jackson-dataformat-xml` dependency.
|
||||
|
||||
| `KotlinSerializationJsonHttpMessageConverter`
|
||||
| An `HttpMessageConverter` implementation that can read and write JSON using `kotlinx.serialization`.
|
||||
This converter is not configured by default, as this conflicts with Jackson.
|
||||
Developers must configure it as an additional converter ahead of the Jackson one.
|
||||
|
||||
| `MappingJackson2CborHttpMessageConverter`
|
||||
| `com.fasterxml.jackson.dataformat:jackson-dataformat-cbor`
|
||||
|
||||
|
||||
@@ -7,6 +7,10 @@ You can use the `@EnableWebMvc` annotation to enable MVC configuration with prog
|
||||
|
||||
include-code::./WebConfiguration[tag=snippet,indent=0]
|
||||
|
||||
WARNING: As of 7.0, support for the XML configuration namespace for Spring MVC has been deprecated.
|
||||
There are no plans yet for removing it completely but XML configuration will not be updated to follow
|
||||
the Java configuration model.
|
||||
|
||||
NOTE: When using Spring Boot, you may want to use `@Configuration` classes of type `WebMvcConfigurer` but without `@EnableWebMvc` to keep Spring Boot MVC customizations. See more details in xref:web/webmvc/mvc-config/customize.adoc[the MVC Config API section] and in {spring-boot-docs-ref}/web/servlet.html#web.servlet.spring-mvc.auto-configuration[the dedicated Spring Boot documentation].
|
||||
|
||||
The preceding example registers a number of Spring MVC
|
||||
|
||||
@@ -48,13 +48,12 @@ For https://www.webjars.org/documentation[WebJars], versioned URLs like
|
||||
`/webjars/jquery/1.2.0/jquery.min.js` are the recommended and most efficient way to use them.
|
||||
The related resource location is configured out of the box with Spring Boot (or can be configured
|
||||
manually via `ResourceHandlerRegistry`) and does not require to add the
|
||||
`org.webjars:webjars-locator-core` dependency.
|
||||
`org.webjars:webjars-locator-lite` dependency.
|
||||
|
||||
Version-less URLs like `/webjars/jquery/jquery.min.js` are supported through the
|
||||
`WebJarsResourceResolver` which is automatically registered when the
|
||||
`org.webjars:webjars-locator-core` library is present on the classpath, at the cost of a
|
||||
classpath scanning that could slow down application startup. The resolver can re-write URLs to
|
||||
include the version of the jar and can also match against incoming URLs without versions
|
||||
`org.webjars:webjars-locator-lite` library is present on the classpath. The resolver can re-write
|
||||
URLs to include the version of the jar and can also match against incoming URLs without versions
|
||||
-- for example, from `/webjars/jquery/jquery.min.js` to `/webjars/jquery/1.2.0/jquery.min.js`.
|
||||
|
||||
TIP: The Java configuration based on `ResourceHandlerRegistry` provides further options
|
||||
|
||||
+1
-2
@@ -30,8 +30,7 @@ and others) and is equivalent to `required=false`.
|
||||
|
||||
| `jakarta.servlet.http.PushBuilder`
|
||||
| Servlet 4.0 push builder API for programmatic HTTP/2 resource pushes.
|
||||
Note that, per the Servlet specification, the injected `PushBuilder` instance can be null if the client
|
||||
does not support that HTTP/2 feature.
|
||||
Note that this API has been deprecated as of Servlet 6.1.
|
||||
|
||||
| `java.security.Principal`
|
||||
| Currently authenticated user -- possibly a specific `Principal` implementation class if known.
|
||||
|
||||
+1
-3
@@ -157,9 +157,7 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
Note that you need to enable the use of matrix variables. In the MVC Java configuration,
|
||||
you need to set a `UrlPathHelper` with `removeSemicolonContent=false` through
|
||||
xref:web/webmvc/mvc-config/path-matching.adoc[Path Matching]. In the MVC XML namespace, you can set
|
||||
Note that you need to enable the use of matrix variables. In the MVC XML namespace, you can set
|
||||
`<mvc:annotation-driven enable-matrix-variables="true"/>`.
|
||||
|
||||
|
||||
|
||||
@@ -248,36 +248,6 @@ specific than other pattern that do not have double wildcards.
|
||||
For the full details, follow the above links to the pattern Comparators.
|
||||
|
||||
|
||||
[[mvc-ann-requestmapping-suffix-pattern-match]]
|
||||
== Suffix Match
|
||||
|
||||
Starting in 5.3, by default Spring MVC no longer performs `.{asterisk}` suffix pattern
|
||||
matching where a controller mapped to `/person` is also implicitly mapped to
|
||||
`/person.{asterisk}`. As a consequence path extensions are no longer used to interpret
|
||||
the requested content type for the response -- for example, `/person.pdf`, `/person.xml`,
|
||||
and so on.
|
||||
|
||||
Using file extensions in this way was necessary when browsers used to send `Accept` headers
|
||||
that were hard to interpret consistently. At present, that is no longer a necessity and
|
||||
using the `Accept` header should be the preferred choice.
|
||||
|
||||
Over time, the use of file name extensions has proven problematic in a variety of ways.
|
||||
It can cause ambiguity when overlain with the use of URI variables, path parameters, and
|
||||
URI encoding. Reasoning about URL-based authorization
|
||||
and security (see next section for more details) also becomes more difficult.
|
||||
|
||||
To completely disable the use of path extensions in versions prior to 5.3, set the following:
|
||||
|
||||
* `useSuffixPatternMatching(false)`, see xref:web/webmvc/mvc-config/path-matching.adoc[PathMatchConfigurer]
|
||||
* `favorPathExtension(false)`, see xref:web/webmvc/mvc-config/content-negotiation.adoc[ContentNegotiationConfigurer]
|
||||
|
||||
Having a way to request content types other than through the `"Accept"` header can still
|
||||
be useful, for example, when typing a URL in a browser. A safe alternative to path extensions is
|
||||
to use the query parameter strategy. If you must use file extensions, consider restricting
|
||||
them to a list of explicitly registered extensions through the `mediaTypes` property of
|
||||
xref:web/webmvc/mvc-config/content-negotiation.adoc[ContentNegotiationConfigurer].
|
||||
|
||||
|
||||
[[mvc-ann-requestmapping-rfd]]
|
||||
== Suffix Match and RFD
|
||||
|
||||
|
||||
@@ -4,12 +4,8 @@
|
||||
|
||||
[.small]#xref:web/webflux/http2.adoc[See equivalent in the Reactive stack]#
|
||||
|
||||
Servlet 4 containers are required to support HTTP/2, and Spring Framework 5 is compatible
|
||||
with Servlet API 4. From a programming model perspective, there is nothing specific that
|
||||
Servlet 4 containers are required to support HTTP/2, and Spring Framework requires
|
||||
Servlet API 6.1. From a programming model perspective, there is nothing specific that
|
||||
applications need to do. However, there are considerations related to server configuration.
|
||||
For more details, see the
|
||||
{spring-framework-wiki}/HTTP-2-support[HTTP/2 wiki page].
|
||||
|
||||
The Servlet API does expose one construct related to HTTP/2. You can use the
|
||||
`jakarta.servlet.http.PushBuilder` to proactively push resources to clients, and it
|
||||
is supported as a xref:web/webmvc/mvc-controller/ann-methods/arguments.adoc[method argument] to `@RequestMapping` methods.
|
||||
|
||||
@@ -11,8 +11,6 @@ The `DispatcherServlet` processes requests as follows:
|
||||
* The locale resolver is bound to the request to let elements in the process
|
||||
resolve the locale to use when processing the request (rendering the view, preparing
|
||||
data, and so on). If you do not need locale resolving, you do not need the locale resolver.
|
||||
* The theme resolver is bound to the request to let elements such as views determine
|
||||
which theme to use. If you do not use themes, you can ignore it.
|
||||
* If you specify a multipart file resolver, the request is inspected for multiparts. If
|
||||
multiparts are found, the request is wrapped in a `MultipartHttpServletRequest` for
|
||||
further processing by other elements in the process. See xref:web/webmvc/mvc-servlet/multipart.adoc[Multipart Resolver] for further
|
||||
|
||||
@@ -43,10 +43,6 @@ The following table lists the special beans detected by the `DispatcherServlet`:
|
||||
| Resolve the `Locale` a client is using and possibly their time zone, in order to be able
|
||||
to offer internationalized views. See xref:web/webmvc/mvc-servlet/localeresolver.adoc[Locale].
|
||||
|
||||
| xref:web/webmvc/mvc-servlet/themeresolver.adoc[`ThemeResolver`]
|
||||
| Resolve themes your web application can use -- for example, to offer personalized layouts.
|
||||
See xref:web/webmvc/mvc-servlet/themeresolver.adoc[Themes].
|
||||
|
||||
| xref:web/webmvc/mvc-servlet/multipart.adoc[`MultipartResolver`]
|
||||
| Abstraction for parsing a multi-part request (for example, browser form file upload) with
|
||||
the help of some multipart parsing library. See xref:web/webmvc/mvc-servlet/multipart.adoc[Multipart Resolver].
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
[[mvc-themeresolver]]
|
||||
= Themes
|
||||
|
||||
You can apply Spring Web MVC framework themes to set the overall look-and-feel of your
|
||||
application, thereby enhancing user experience. A theme is a collection of static
|
||||
resources, typically style sheets and images, that affect the visual style of the
|
||||
application.
|
||||
|
||||
WARNING: as of 6.0 support for themes has been deprecated theme in favor of using CSS,
|
||||
and without any special support on the server side.
|
||||
|
||||
|
||||
[[mvc-themeresolver-defining]]
|
||||
== Defining a theme
|
||||
|
||||
To use themes in your web application, you must set up an implementation of the
|
||||
`org.springframework.ui.context.ThemeSource` interface. The `WebApplicationContext`
|
||||
interface extends `ThemeSource` but delegates its responsibilities to a dedicated
|
||||
implementation. By default, the delegate is an
|
||||
`org.springframework.ui.context.support.ResourceBundleThemeSource` implementation that
|
||||
loads properties files from the root of the classpath. To use a custom `ThemeSource`
|
||||
implementation or to configure the base name prefix of the `ResourceBundleThemeSource`,
|
||||
you can register a bean in the application context with the reserved name, `themeSource`.
|
||||
The web application context automatically detects a bean with that name and uses it.
|
||||
|
||||
When you use the `ResourceBundleThemeSource`, a theme is defined in a simple properties
|
||||
file. The properties file lists the resources that make up the theme, as the following example shows:
|
||||
|
||||
[literal,subs="verbatim,quotes"]
|
||||
----
|
||||
styleSheet=/themes/cool/style.css
|
||||
background=/themes/cool/img/coolBg.jpg
|
||||
----
|
||||
|
||||
The keys of the properties are the names that refer to the themed elements from view
|
||||
code. For a JSP, you typically do this using the `spring:theme` custom tag, which is
|
||||
very similar to the `spring:message` tag. The following JSP fragment uses the theme
|
||||
defined in the previous example to customize the look and feel:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="<spring:theme code='styleSheet'/>" type="text/css"/>
|
||||
</head>
|
||||
<body style="background=<spring:theme code='background'/>">
|
||||
...
|
||||
</body>
|
||||
</html>
|
||||
----
|
||||
|
||||
By default, the `ResourceBundleThemeSource` uses an empty base name prefix. As a result,
|
||||
the properties files are loaded from the root of the classpath. Thus, you would put the
|
||||
`cool.properties` theme definition in a directory at the root of the classpath (for
|
||||
example, in `/WEB-INF/classes`). The `ResourceBundleThemeSource` uses the standard Java
|
||||
resource bundle loading mechanism, allowing for full internationalization of themes. For
|
||||
example, we could have a `/WEB-INF/classes/cool_nl.properties` that references a special
|
||||
background image with Dutch text on it.
|
||||
|
||||
|
||||
[[mvc-themeresolver-resolving]]
|
||||
== Resolving Themes
|
||||
|
||||
After you define themes, as described in the xref:web/webmvc/mvc-servlet/themeresolver.adoc#mvc-themeresolver-defining[preceding section],
|
||||
you decide which theme to use. The `DispatcherServlet` looks for a bean named `themeResolver`
|
||||
to find out which `ThemeResolver` implementation to use. A theme resolver works in much the same
|
||||
way as a `LocaleResolver`. It detects the theme to use for a particular request and can also
|
||||
alter the request's theme. The following table describes the theme resolvers provided by Spring:
|
||||
|
||||
[[mvc-theme-resolver-impls-tbl]]
|
||||
.ThemeResolver implementations
|
||||
[cols="1,4"]
|
||||
|===
|
||||
| Class | Description
|
||||
|
||||
| `FixedThemeResolver`
|
||||
| Selects a fixed theme, set by using the `defaultThemeName` property.
|
||||
|
||||
| `SessionThemeResolver`
|
||||
| The theme is maintained in the user's HTTP session. It needs to be set only once for
|
||||
each session but is not persisted between sessions.
|
||||
|
||||
| `CookieThemeResolver`
|
||||
| The selected theme is stored in a cookie on the client.
|
||||
|===
|
||||
|
||||
Spring also provides a `ThemeChangeInterceptor` that lets theme changes on every
|
||||
request with a simple request parameter.
|
||||
|
||||
|
||||
|
||||
@@ -85,10 +85,7 @@ for all HTTP processing -- including WebSocket handshake and all other HTTP
|
||||
requests -- such as Spring MVC's `DispatcherServlet`.
|
||||
|
||||
This is a significant limitation of JSR-356 that Spring's WebSocket support addresses with
|
||||
server-specific `RequestUpgradeStrategy` implementations even when running in a JSR-356 runtime.
|
||||
Such strategies currently exist for Tomcat, Jetty, GlassFish, WebLogic, WebSphere, and Undertow
|
||||
(and WildFly). As of Jakarta WebSocket 2.1, a standard request upgrade strategy is available
|
||||
which Spring chooses on Jakarta EE 10 based web containers such as Tomcat 10.1 and Jetty 12.
|
||||
a standard `RequestUpgradeStrategy` implementation when running in a WebSocket API 2.1+ runtime.
|
||||
|
||||
A secondary consideration is that Servlet containers with JSR-356 support are expected
|
||||
to perform a `ServletContainerInitializer` (SCI) scan that can slow down application
|
||||
|
||||
@@ -19,6 +19,6 @@ from where they are handled according to their destination prefix. As the channe
|
||||
a `ThreadPoolExecutor`, messages are processed in different threads, and the resulting sequence
|
||||
of handling may not match the exact order in which they were received.
|
||||
|
||||
To enable ordered publishing, set the `setPreserveReceiveOrder` flag as follows:
|
||||
To enable ordered receiving, set the `setPreserveReceiveOrder` flag as follows:
|
||||
|
||||
include-code::./ReceiveOrderWebSocketConfiguration[tag=snippet,indent=0]
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -24,7 +24,6 @@ import org.springframework.aot.hint.ExecutableMode;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.test.agent.EnabledIfRuntimeHintsAgent;
|
||||
import org.springframework.aot.test.agent.RuntimeHintsInvocations;
|
||||
import org.springframework.aot.test.agent.RuntimeHintsRecorder;
|
||||
import org.springframework.core.SpringVersion;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -33,6 +32,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
// method is only enabled if the RuntimeHintsAgent is loaded on the current JVM.
|
||||
// It also tags tests with the "RuntimeHints" JUnit tag.
|
||||
@EnabledIfRuntimeHintsAgent
|
||||
@SuppressWarnings("removal")
|
||||
class SampleReflectionRuntimeHintsTests {
|
||||
|
||||
@Test
|
||||
@@ -43,7 +43,7 @@ class SampleReflectionRuntimeHintsTests {
|
||||
typeHint.withMethod("getVersion", List.of(), ExecutableMode.INVOKE));
|
||||
|
||||
// Invoke the relevant piece of code we want to test within a recording lambda
|
||||
RuntimeHintsInvocations invocations = RuntimeHintsRecorder.record(() -> {
|
||||
RuntimeHintsInvocations invocations = org.springframework.aot.test.agent.RuntimeHintsRecorder.record(() -> {
|
||||
SampleReflection sample = new SampleReflection();
|
||||
sample.performReflection();
|
||||
});
|
||||
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.integration.resthttpinterface.customresolver;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.client.support.RestClientAdapter;
|
||||
import org.springframework.web.service.annotation.GetExchange;
|
||||
import org.springframework.web.service.invoker.HttpRequestValues;
|
||||
import org.springframework.web.service.invoker.HttpServiceArgumentResolver;
|
||||
import org.springframework.web.service.invoker.HttpServiceProxyFactory;
|
||||
|
||||
public class CustomHttpServiceArgumentResolver {
|
||||
|
||||
// tag::httpinterface[]
|
||||
interface RepositoryService {
|
||||
|
||||
@GetExchange("/repos/search")
|
||||
List<Repository> searchRepository(Search search);
|
||||
|
||||
}
|
||||
// end::httpinterface[]
|
||||
|
||||
class Sample {
|
||||
|
||||
void sample() {
|
||||
// tag::usage[]
|
||||
RestClient restClient = RestClient.builder().baseUrl("https://api.github.com/").build();
|
||||
RestClientAdapter adapter = RestClientAdapter.create(restClient);
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory
|
||||
.builderFor(adapter)
|
||||
.customArgumentResolver(new SearchQueryArgumentResolver())
|
||||
.build();
|
||||
RepositoryService repositoryService = factory.createClient(RepositoryService.class);
|
||||
|
||||
Search search = Search.create()
|
||||
.owner("spring-projects")
|
||||
.language("java")
|
||||
.query("rest")
|
||||
.build();
|
||||
List<Repository> repositories = repositoryService.searchRepository(search);
|
||||
// end::usage[]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// tag::argumentresolver[]
|
||||
static class SearchQueryArgumentResolver implements HttpServiceArgumentResolver {
|
||||
@Override
|
||||
public boolean resolve(Object argument, MethodParameter parameter, HttpRequestValues.Builder requestValues) {
|
||||
if (parameter.getParameterType().equals(Search.class)) {
|
||||
Search search = (Search) argument;
|
||||
requestValues.addRequestParameter("owner", search.owner());
|
||||
requestValues.addRequestParameter("language", search.language());
|
||||
requestValues.addRequestParameter("query", search.query());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// end::argumentresolver[]
|
||||
|
||||
|
||||
record Search (String query, String owner, String language) {
|
||||
|
||||
static Builder create() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
static class Builder {
|
||||
|
||||
Builder query(String query) { return this;}
|
||||
|
||||
Builder owner(String owner) { return this;}
|
||||
|
||||
Builder language(String language) { return this;}
|
||||
|
||||
Search build() {
|
||||
return new Search(null, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
record Repository(String name) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+4
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,11 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package example.indexed;
|
||||
package org.springframework.docs.core.beans.dependencies.beansfactorylazyinit
|
||||
|
||||
/**
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
@javax.annotation.ManagedBean
|
||||
public class IndexedJavaxManagedBeanComponent {
|
||||
}
|
||||
class AnotherBean {
|
||||
}
|
||||
+4
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,11 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package example.indexed;
|
||||
package org.springframework.docs.core.beans.dependencies.beansfactorylazyinit
|
||||
|
||||
/**
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
@jakarta.annotation.ManagedBean
|
||||
public class IndexedJakartaManagedBeanComponent {
|
||||
}
|
||||
class ExpensiveToCreateBean {
|
||||
}
|
||||
+4
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,11 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package example.scannable;
|
||||
package org.springframework.docs.core.expressions.expressionsbeandef
|
||||
|
||||
/**
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
@javax.inject.Named("myJavaxNamedComponent")
|
||||
public class JavaxNamedComponent {
|
||||
}
|
||||
class CustomerPreferenceDao {
|
||||
}
|
||||
+5
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,11 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package example.indexed;
|
||||
|
||||
/**
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
@javax.inject.Named("myIndexedJavaxNamedComponent")
|
||||
public class IndexedJavaxNamedComponent {
|
||||
}
|
||||
package org.springframework.docs.core.expressions.expressionsbeandef
|
||||
|
||||
class MovieFinder {
|
||||
}
|
||||
+3
-11
@@ -14,16 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.view;
|
||||
package org.springframework.docs.dataaccess.jdbc.jdbccomplextypes
|
||||
|
||||
/**
|
||||
* @author Rod Johnson
|
||||
*/
|
||||
class ResourceBundleViewResolverNoCacheTests extends ResourceBundleViewResolverTests {
|
||||
import java.util.Date
|
||||
|
||||
@Override
|
||||
protected boolean getCache() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
data class TestItem(val id: Long, val description: String, val expirationDate: Date)
|
||||
+5
-5
@@ -31,11 +31,11 @@ class TestItemStoredProcedure(dataSource: DataSource) : StoredProcedure(dataSour
|
||||
cs: CallableStatement, colIndx: Int, _: Int, _: String? ->
|
||||
val struct = cs.getObject(colIndx) as Struct
|
||||
val attr = struct.attributes
|
||||
val item = TestItem()
|
||||
item.id = (attr[0] as Number).toLong()
|
||||
item.description = attr[1] as String
|
||||
item.expirationDate = attr[2] as Date
|
||||
item
|
||||
TestItem(
|
||||
(attr[0] as Number).toLong(),
|
||||
attr[1] as String,
|
||||
attr[2] as Date
|
||||
)
|
||||
})
|
||||
// ...
|
||||
}
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.dataaccess.jdbc.jdbcjdbctemplateidioms
|
||||
|
||||
interface CorporateEventDao {
|
||||
}
|
||||
+1
-3
@@ -28,9 +28,7 @@ class CustomCacheConfiguration {
|
||||
// tag::snippet[]
|
||||
@Bean
|
||||
fun cacheManager(): CacheManager {
|
||||
return CaffeineCacheManager().apply {
|
||||
cacheNames = listOf("default", "books")
|
||||
}
|
||||
return CaffeineCacheManager("default", "books")
|
||||
}
|
||||
// end::snippet[]
|
||||
}
|
||||
+9
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,11 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package example.scannable;
|
||||
package org.springframework.docs.integration.jmx.jmxexporting
|
||||
|
||||
/**
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
@jakarta.annotation.ManagedBean("myJakartaManagedBeanComponent")
|
||||
public class JakartaManagedBeanComponent {
|
||||
}
|
||||
interface IJmxTestBean {
|
||||
|
||||
var name: String
|
||||
var age: Int
|
||||
fun add(x: Int, y: Int): Int
|
||||
fun dontExposeMe()
|
||||
}
|
||||
+2
-18
@@ -19,24 +19,8 @@ package org.springframework.docs.integration.jmx.jmxexporting
|
||||
// tag::snippet[]
|
||||
class JmxTestBean : IJmxTestBean {
|
||||
|
||||
private lateinit var name: String
|
||||
private var age = 0
|
||||
|
||||
override fun getAge(): Int {
|
||||
return age
|
||||
}
|
||||
|
||||
override fun setAge(age: Int) {
|
||||
this.age = age
|
||||
}
|
||||
|
||||
override fun setName(name: String) {
|
||||
this.name = name
|
||||
}
|
||||
|
||||
override fun getName(): String {
|
||||
return name
|
||||
}
|
||||
override lateinit var name: String
|
||||
override var age = 0
|
||||
|
||||
override fun add(x: Int, y: Int): Int {
|
||||
return x + y
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.integration.mailusagesimple
|
||||
|
||||
data class Customer(
|
||||
val emailAddress: String,
|
||||
val firstName: String,
|
||||
val lastName: String
|
||||
)
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.integration.mailusagesimple
|
||||
|
||||
data class Order(
|
||||
val customer: Customer,
|
||||
val orderNumber: String
|
||||
)
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.integration.resthttpinterface.customresolver
|
||||
|
||||
import org.springframework.core.MethodParameter
|
||||
import org.springframework.web.client.RestClient
|
||||
import org.springframework.web.client.support.RestClientAdapter
|
||||
import org.springframework.web.service.annotation.GetExchange
|
||||
import org.springframework.web.service.invoker.HttpRequestValues
|
||||
import org.springframework.web.service.invoker.HttpServiceArgumentResolver
|
||||
import org.springframework.web.service.invoker.HttpServiceProxyFactory
|
||||
|
||||
class CustomHttpServiceArgumentResolver {
|
||||
|
||||
// tag::httpinterface[]
|
||||
interface RepositoryService {
|
||||
|
||||
@GetExchange("/repos/search")
|
||||
fun searchRepository(search: Search): List<Repository>
|
||||
|
||||
}
|
||||
// end::httpinterface[]
|
||||
|
||||
class Sample {
|
||||
fun sample() {
|
||||
// tag::usage[]
|
||||
val restClient = RestClient.builder().baseUrl("https://api.github.com/").build()
|
||||
val adapter = RestClientAdapter.create(restClient)
|
||||
val factory = HttpServiceProxyFactory
|
||||
.builderFor(adapter)
|
||||
.customArgumentResolver(SearchQueryArgumentResolver())
|
||||
.build()
|
||||
val repositoryService = factory.createClient<RepositoryService>(RepositoryService::class.java)
|
||||
|
||||
val search = Search(owner = "spring-projects", language = "java", query = "rest")
|
||||
val repositories = repositoryService.searchRepository(search)
|
||||
// end::usage[]
|
||||
repositories.size
|
||||
}
|
||||
}
|
||||
|
||||
// tag::argumentresolver[]
|
||||
class SearchQueryArgumentResolver : HttpServiceArgumentResolver {
|
||||
override fun resolve(
|
||||
argument: Any?,
|
||||
parameter: MethodParameter,
|
||||
requestValues: HttpRequestValues.Builder
|
||||
): Boolean {
|
||||
if (parameter.getParameterType() == Search::class.java) {
|
||||
val search = argument as Search
|
||||
requestValues.addRequestParameter("owner", search.owner)
|
||||
.addRequestParameter("language", search.language)
|
||||
.addRequestParameter("query", search.query)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
// end::argumentresolver[]
|
||||
|
||||
data class Search(val query: String, val owner: String, val language: String)
|
||||
|
||||
data class Repository(val name: String)
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.testing.mockmvc.assertj.mockmvctestersetup
|
||||
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
@RestController
|
||||
class AccountController {
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.testing.mockmvc.assertj.mockmvctestersetup
|
||||
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableWebMvc
|
||||
class ApplicationWebConfiguration {
|
||||
}
|
||||
+2
-3
@@ -14,14 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
package org.springframework.docs.web.webmvc.mvcconfig.mvcconfiginterceptors
|
||||
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
|
||||
import org.springframework.web.servlet.handler.UserRoleAuthorizationInterceptor
|
||||
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor
|
||||
import org.springframework.web.servlet.theme.ThemeChangeInterceptor
|
||||
|
||||
// tag::snippet[]
|
||||
@Configuration
|
||||
@@ -29,7 +28,7 @@ class WebConfiguration : WebMvcConfigurer {
|
||||
|
||||
override fun addInterceptors(registry: InterceptorRegistry) {
|
||||
registry.addInterceptor(LocaleChangeInterceptor())
|
||||
registry.addInterceptor(ThemeChangeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**")
|
||||
registry.addInterceptor(UserRoleAuthorizationInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**")
|
||||
}
|
||||
}
|
||||
// end::snippet[]
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.webmvc.mvcconfig.mvcconfigvalidation
|
||||
|
||||
import org.springframework.validation.Errors
|
||||
import org.springframework.validation.Validator
|
||||
|
||||
class FooValidator : Validator {
|
||||
override fun supports(clazz: Class<*>) = false
|
||||
|
||||
override fun validate(target: Any, errors: Errors) {
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.web.websocket.websocketserverruntimeconfiguration
|
||||
|
||||
import org.springframework.web.socket.handler.AbstractWebSocketHandler
|
||||
|
||||
class MyEchoHandler : AbstractWebSocketHandler() {
|
||||
}
|
||||
+3
-1
@@ -14,7 +14,9 @@
|
||||
<mvc:interceptor>
|
||||
<mvc:mapping path="/**"/>
|
||||
<mvc:exclude-mapping path="/admin/**"/>
|
||||
<bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor"/>
|
||||
<bean class="org.springframework.web.servlet.handler.UserRoleAuthorizationInterceptor">
|
||||
<property name="authorizedRoles" value="ROLE_USER"/>
|
||||
</bean>
|
||||
</mvc:interceptor>
|
||||
</mvc:interceptors>
|
||||
<!-- end::snippet[] -->
|
||||
|
||||
@@ -8,18 +8,17 @@ javaPlatform {
|
||||
|
||||
dependencies {
|
||||
api(platform("com.fasterxml.jackson:jackson-bom:2.18.2"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.14.3"))
|
||||
api(platform("io.netty:netty-bom:4.1.117.Final"))
|
||||
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
|
||||
api(platform("io.projectreactor:reactor-bom:2024.0.2"))
|
||||
api(platform("io.rsocket:rsocket-bom:1.1.4"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.14.4"))
|
||||
api(platform("io.netty:netty-bom:4.1.118.Final"))
|
||||
api(platform("io.projectreactor:reactor-bom:2024.0.3"))
|
||||
api(platform("io.rsocket:rsocket-bom:1.1.5"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.24"))
|
||||
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
|
||||
api(platform("org.assertj:assertj-bom:3.27.2"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:12.0.16"))
|
||||
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.16"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.3"))
|
||||
api(platform("org.apache.logging.log4j:log4j-bom:3.0.0-beta3"))
|
||||
api(platform("org.assertj:assertj-bom:3.27.3"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:12.1.0.alpha1"))
|
||||
api(platform("org.eclipse.jetty.ee11:jetty-ee11-bom:12.1.0.alpha1"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.10.1"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.8.0"))
|
||||
api(platform("org.junit:junit-bom:5.11.4"))
|
||||
api(platform("org.mockito:mockito-bom:5.15.2"))
|
||||
|
||||
@@ -34,22 +33,21 @@ dependencies {
|
||||
api("com.google.protobuf:protobuf-java-util:4.29.3")
|
||||
api("com.h2database:h2:2.3.232")
|
||||
api("com.jayway.jsonpath:json-path:2.9.0")
|
||||
api("com.networknt:json-schema-validator:1.5.3")
|
||||
api("com.oracle.database.jdbc:ojdbc11:21.9.0.0")
|
||||
api("com.rometools:rome:1.19.0")
|
||||
api("com.squareup.okhttp3:mockwebserver:3.14.9")
|
||||
api("com.squareup.okhttp3:okhttp:3.14.9")
|
||||
api("com.sun.activation:jakarta.activation:2.0.1")
|
||||
api("com.sun.mail:jakarta.mail:2.0.1")
|
||||
api("com.sun.xml.bind:jaxb-core:3.0.2")
|
||||
api("com.sun.xml.bind:jaxb-impl:3.0.2")
|
||||
api("com.sun.xml.bind:jaxb-xjc:3.0.2")
|
||||
api("com.thoughtworks.qdox:qdox:2.1.0")
|
||||
api("com.thoughtworks.xstream:xstream:1.4.21")
|
||||
api("commons-io:commons-io:2.15.0")
|
||||
api("commons-logging:commons-logging:1.3.4")
|
||||
api("de.bechte.junit:junit-hierarchicalcontextrunner:4.12.2")
|
||||
api("io.micrometer:context-propagation:1.1.1")
|
||||
api("io.mockk:mockk:1.13.4")
|
||||
api("io.projectreactor.netty:reactor-netty5-http:2.0.0-M3")
|
||||
api("io.projectreactor.tools:blockhound:1.0.8.RELEASE")
|
||||
api("io.r2dbc:r2dbc-h2:1.0.0.RELEASE")
|
||||
api("io.r2dbc:r2dbc-spi-test:1.0.0.RELEASE")
|
||||
@@ -60,32 +58,31 @@ dependencies {
|
||||
api("io.undertow:undertow-servlet:2.3.18.Final")
|
||||
api("io.undertow:undertow-websockets-jsr:2.3.18.Final")
|
||||
api("io.vavr:vavr:0.10.4")
|
||||
api("jakarta.activation:jakarta.activation-api:2.0.1")
|
||||
api("jakarta.annotation:jakarta.annotation-api:2.0.0")
|
||||
api("jakarta.activation:jakarta.activation-api:2.1.3")
|
||||
api("jakarta.annotation:jakarta.annotation-api:3.0.0")
|
||||
api("jakarta.validation:jakarta.validation-api:3.1.0")
|
||||
api("jakarta.ejb:jakarta.ejb-api:4.0.1")
|
||||
api("jakarta.el:jakarta.el-api:4.0.0")
|
||||
api("jakarta.enterprise.concurrent:jakarta.enterprise.concurrent-api:2.0.0")
|
||||
api("jakarta.faces:jakarta.faces-api:3.0.0")
|
||||
api("jakarta.el:jakarta.el-api:6.0.1")
|
||||
api("jakarta.enterprise.concurrent:jakarta.enterprise.concurrent-api:3.1.1")
|
||||
api("jakarta.faces:jakarta.faces-api:4.1.2")
|
||||
api("jakarta.inject:jakarta.inject-api:2.0.1")
|
||||
api("jakarta.inject:jakarta.inject-tck:2.0.1")
|
||||
api("jakarta.interceptor:jakarta.interceptor-api:2.0.0")
|
||||
api("jakarta.jms:jakarta.jms-api:3.0.0")
|
||||
api("jakarta.json.bind:jakarta.json.bind-api:2.0.0")
|
||||
api("jakarta.json:jakarta.json-api:2.0.1")
|
||||
api("jakarta.mail:jakarta.mail-api:2.0.1")
|
||||
api("jakarta.persistence:jakarta.persistence-api:3.0.0")
|
||||
api("jakarta.resource:jakarta.resource-api:2.0.0")
|
||||
api("jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api:3.0.0")
|
||||
api("jakarta.servlet.jsp:jakarta.servlet.jsp-api:3.1.1")
|
||||
api("jakarta.servlet:jakarta.servlet-api:6.0.0")
|
||||
api("jakarta.interceptor:jakarta.interceptor-api:2.2.0")
|
||||
api("jakarta.jms:jakarta.jms-api:3.1.0")
|
||||
api("jakarta.json.bind:jakarta.json.bind-api:3.0.1")
|
||||
api("jakarta.json:jakarta.json-api:2.1.3")
|
||||
api("jakarta.mail:jakarta.mail-api:2.1.3")
|
||||
api("jakarta.persistence:jakarta.persistence-api:3.2.0")
|
||||
api("jakarta.resource:jakarta.resource-api:2.1.0")
|
||||
api("jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api:3.0.2")
|
||||
api("jakarta.servlet.jsp:jakarta.servlet.jsp-api:4.0.0")
|
||||
api("jakarta.servlet:jakarta.servlet-api:6.1.0")
|
||||
api("jakarta.transaction:jakarta.transaction-api:2.0.1")
|
||||
api("jakarta.validation:jakarta.validation-api:3.0.2")
|
||||
api("jakarta.websocket:jakarta.websocket-api:2.1.0")
|
||||
api("jakarta.websocket:jakarta.websocket-client-api:2.1.0")
|
||||
api("jakarta.validation:jakarta.validation-api:3.1.0")
|
||||
api("jakarta.websocket:jakarta.websocket-api:2.2.0")
|
||||
api("jakarta.websocket:jakarta.websocket-client-api:2.2.0")
|
||||
api("jakarta.xml.bind:jakarta.xml.bind-api:3.0.1")
|
||||
api("javax.annotation:javax.annotation-api:1.3.2")
|
||||
api("javax.cache:cache-api:1.1.1")
|
||||
api("javax.inject:javax.inject:1")
|
||||
api("javax.money:money-api:1.1")
|
||||
api("jaxen:jaxen:1.2.0")
|
||||
api("junit:junit:4.13.2")
|
||||
@@ -103,10 +100,10 @@ dependencies {
|
||||
api("org.apache.httpcomponents.client5:httpclient5:5.4.1")
|
||||
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.3.1")
|
||||
api("org.apache.poi:poi-ooxml:5.2.5")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.28")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.28")
|
||||
api("org.apache.tomcat:tomcat-util:10.1.28")
|
||||
api("org.apache.tomcat:tomcat-websocket:10.1.28")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-core:11.0.1")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-websocket:11.0.1")
|
||||
api("org.apache.tomcat:tomcat-util:11.0.1")
|
||||
api("org.apache.tomcat:tomcat-websocket:11.0.1")
|
||||
api("org.aspectj:aspectjrt:1.9.22.1")
|
||||
api("org.aspectj:aspectjtools:1.9.22.1")
|
||||
api("org.aspectj:aspectjweaver:1.9.22.1")
|
||||
@@ -116,23 +113,24 @@ dependencies {
|
||||
api("org.crac:crac:1.4.0")
|
||||
api("org.dom4j:dom4j:2.1.4")
|
||||
api("org.easymock:easymock:5.4.0")
|
||||
api("org.eclipse.angus:angus-mail:2.0.3")
|
||||
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.8")
|
||||
api("org.eclipse.persistence:org.eclipse.persistence.jpa:3.0.4")
|
||||
api("org.eclipse:yasson:2.0.4")
|
||||
api("org.eclipse.persistence:org.eclipse.persistence.jpa:5.0.0-B04")
|
||||
api("org.eclipse:yasson:3.0.4")
|
||||
api("org.ehcache:ehcache:3.10.8")
|
||||
api("org.ehcache:jcache:1.0.1")
|
||||
api("org.freemarker:freemarker:2.3.33")
|
||||
api("org.glassfish.external:opendmk_jmxremote_optional_jar:1.0-b01-ea")
|
||||
api("org.glassfish:jakarta.el:4.0.2")
|
||||
api("org.glassfish.tyrus:tyrus-container-servlet:2.1.3")
|
||||
api("org.graalvm.sdk:graal-sdk:22.3.1")
|
||||
api("org.hamcrest:hamcrest:2.2")
|
||||
api("org.hibernate:hibernate-core-jakarta:5.6.15.Final")
|
||||
api("org.hibernate:hibernate-validator:7.0.5.Final")
|
||||
api("org.hibernate.orm:hibernate-core:7.0.0.Beta3")
|
||||
api("org.hibernate.validator:hibernate-validator:9.0.0.CR1")
|
||||
api("org.hsqldb:hsqldb:2.7.4")
|
||||
api("org.htmlunit:htmlunit:4.6.0")
|
||||
api("org.javamoney:moneta:1.4.4")
|
||||
api("org.jruby:jruby:9.4.9.0")
|
||||
api("org.jspecify:jspecify:1.0.0")
|
||||
api("org.junit.support:testng-engine:1.0.5")
|
||||
api("org.mozilla:rhino:1.7.15")
|
||||
api("org.ogce:xpp3:1.1.6")
|
||||
@@ -140,11 +138,9 @@ dependencies {
|
||||
api("org.quartz-scheduler:quartz:2.3.2")
|
||||
api("org.seleniumhq.selenium:htmlunit3-driver:4.26.0")
|
||||
api("org.seleniumhq.selenium:selenium-java:4.26.0")
|
||||
api("org.skyscreamer:jsonassert:1.5.3")
|
||||
api("org.slf4j:slf4j-api:2.0.16")
|
||||
api("org.testng:testng:7.10.2")
|
||||
api("org.skyscreamer:jsonassert:2.0-rc1")
|
||||
api("org.testng:testng:7.11.0")
|
||||
api("org.webjars:underscorejs:1.8.3")
|
||||
api("org.webjars:webjars-locator-core:0.55")
|
||||
api("org.webjars:webjars-locator-lite:1.0.0")
|
||||
api("org.xmlunit:xmlunit-assertj:2.10.0")
|
||||
api("org.xmlunit:xmlunit-matchers:2.10.0")
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
version=6.2.2
|
||||
version=7.0.0-M2
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
org.gradle.parallel=true
|
||||
|
||||
kotlinVersion=1.9.25
|
||||
kotlinVersion=2.1.10
|
||||
|
||||
kotlin.jvm.target.validation.mode=ignore
|
||||
kotlin.stdlib.default.dependency=false
|
||||
|
||||
@@ -13,8 +13,8 @@ dependencies {
|
||||
jmh 'org.openjdk.jmh:jmh-generator-annprocess:1.37'
|
||||
jmh 'org.openjdk.jmh:jmh-generator-bytecode:1.37'
|
||||
jmh 'net.sf.jopt-simple:jopt-simple'
|
||||
errorprone 'com.uber.nullaway:nullaway:0.10.26'
|
||||
errorprone 'com.google.errorprone:error_prone_core:2.9.0'
|
||||
errorprone 'com.uber.nullaway:nullaway:0.12.3'
|
||||
errorprone 'com.google.errorprone:error_prone_core:2.36.0'
|
||||
}
|
||||
|
||||
pluginManager.withPlugin("kotlin") {
|
||||
@@ -76,9 +76,9 @@ javadoc {
|
||||
options.header = project.name
|
||||
options.use = true
|
||||
options.links(project.ext.javadocLinks)
|
||||
// Check for syntax during linting. 'none' doesn't seem to work in suppressing
|
||||
// all linting warnings all the time (see/link references most notably).
|
||||
options.addStringOption("Xdoclint:syntax", "-quiet")
|
||||
options.setOutputLevel(JavadocOutputLevel.QUIET)
|
||||
// Check for syntax during linting.
|
||||
options.addBooleanOption("Xdoclint:syntax", true)
|
||||
|
||||
// Suppress warnings due to cross-module @see and @link references.
|
||||
// Note that global 'api' task does display all warnings, and
|
||||
@@ -116,11 +116,9 @@ components.java.withVariantsFromConfiguration(configurations.testFixturesRuntime
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
options.errorprone {
|
||||
disableAllChecks = true
|
||||
option("NullAway:CustomContractAnnotations", "org.springframework.lang.Contract")
|
||||
option("NullAway:AnnotatedPackages", "org.springframework")
|
||||
option("NullAway:UnannotatedSubPackages", "org.springframework.instrument,org.springframework.context.index," +
|
||||
"org.springframework.asm,org.springframework.cglib,org.springframework.objenesis," +
|
||||
"org.springframework.javapoet,org.springframework.aot.nativex.substitution,org.springframework.aot.nativex.feature")
|
||||
option("NullAway:OnlyNullMarked", "true")
|
||||
option("NullAway:CustomContractAnnotations", "org.springframework.lang.Contract")
|
||||
option("NullAway:JSpecifyMode", "true")
|
||||
}
|
||||
}
|
||||
tasks.compileJava {
|
||||
|
||||
@@ -26,7 +26,7 @@ dependencies {
|
||||
testImplementation("jakarta.servlet:jakarta.servlet-api")
|
||||
testImplementation("org.aspectj:aspectjweaver")
|
||||
testImplementation("org.hsqldb:hsqldb")
|
||||
testImplementation("org.hibernate:hibernate-core-jakarta")
|
||||
testImplementation("org.hibernate.orm:hibernate-core")
|
||||
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -20,6 +20,7 @@ import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
@@ -31,7 +32,6 @@ import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.testfixture.beans.ITestBean;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.transaction.NoTransactionException;
|
||||
import org.springframework.transaction.interceptor.TransactionInterceptor;
|
||||
import org.springframework.transaction.testfixture.CallCountingTransactionManager;
|
||||
|
||||
+7
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,24 +18,23 @@ package org.springframework.aot.test;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.test.agent.EnabledIfRuntimeHintsAgent;
|
||||
import org.springframework.aot.test.agent.RuntimeHintsInvocations;
|
||||
import org.springframework.aot.test.agent.RuntimeHintsRecorder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
|
||||
@EnabledIfRuntimeHintsAgent
|
||||
@SuppressWarnings("removal")
|
||||
class ReflectionInvocationsTests {
|
||||
|
||||
@Test
|
||||
void sampleTest() {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_PUBLIC_METHODS);
|
||||
hints.reflection().registerType(String.class);
|
||||
|
||||
RuntimeHintsInvocations invocations = RuntimeHintsRecorder.record(() -> {
|
||||
RuntimeHintsInvocations invocations = org.springframework.aot.test.agent.RuntimeHintsRecorder.record(() -> {
|
||||
SampleReflection sample = new SampleReflection();
|
||||
sample.sample(); // does Method[] methods = String.class.getMethods();
|
||||
});
|
||||
@@ -45,9 +44,9 @@ class ReflectionInvocationsTests {
|
||||
@Test
|
||||
void multipleCallsTest() {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_PUBLIC_METHODS);
|
||||
hints.reflection().registerType(Integer.class,MemberCategory.INTROSPECT_PUBLIC_METHODS);
|
||||
RuntimeHintsInvocations invocations = RuntimeHintsRecorder.record(() -> {
|
||||
hints.reflection().registerType(String.class);
|
||||
hints.reflection().registerType(Integer.class);
|
||||
RuntimeHintsInvocations invocations = org.springframework.aot.test.agent.RuntimeHintsRecorder.record(() -> {
|
||||
SampleReflection sample = new SampleReflection();
|
||||
sample.multipleCalls(); // does Method[] methods = String.class.getMethods(); methods = Integer.class.getMethods();
|
||||
});
|
||||
|
||||
+1
-2
@@ -1,5 +1,5 @@
|
||||
plugins {
|
||||
id "com.gradle.develocity" version "3.17.2"
|
||||
id "com.gradle.develocity" version "3.19"
|
||||
id "io.spring.ge.conventions" version "0.0.17"
|
||||
id "org.gradle.toolchains.foojay-resolver-convention" version "0.7.0"
|
||||
}
|
||||
@@ -14,7 +14,6 @@ include "spring-core"
|
||||
include "spring-core-test"
|
||||
include "spring-expression"
|
||||
include "spring-instrument"
|
||||
include "spring-jcl"
|
||||
include "spring-jdbc"
|
||||
include "spring-jms"
|
||||
include "spring-messaging"
|
||||
|
||||
@@ -5,12 +5,12 @@ apply plugin: "kotlin"
|
||||
dependencies {
|
||||
api(project(":spring-beans"))
|
||||
api(project(":spring-core"))
|
||||
compileOnly("com.google.code.findbugs:jsr305") // for the AOP Alliance fork
|
||||
optional("org.apache.commons:commons-pool2")
|
||||
optional("org.aspectj:aspectjweaver")
|
||||
optional("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
|
||||
testFixturesImplementation(testFixtures(project(":spring-beans")))
|
||||
testFixturesImplementation(testFixtures(project(":spring-core")))
|
||||
testFixturesImplementation("com.google.code.findbugs:jsr305")
|
||||
testImplementation(project(":spring-core-test"))
|
||||
testImplementation(testFixtures(project(":spring-beans")))
|
||||
testImplementation(testFixtures(project(":spring-core")))
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
/**
|
||||
* The core AOP Alliance advice marker.
|
||||
*/
|
||||
@NullMarked
|
||||
package org.aopalliance.aop;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package org.aopalliance.intercept;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Intercepts the construction of a new object.
|
||||
*
|
||||
@@ -56,7 +54,6 @@ public interface ConstructorInterceptor extends Interceptor {
|
||||
* @throws Throwable if the interceptors or the target object
|
||||
* throws an exception
|
||||
*/
|
||||
@Nonnull
|
||||
Object construct(ConstructorInvocation invocation) throws Throwable;
|
||||
|
||||
}
|
||||
|
||||
@@ -18,8 +18,6 @@ package org.aopalliance.intercept;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Description of an invocation to a constructor, given to an
|
||||
* interceptor upon constructor-call.
|
||||
@@ -38,7 +36,6 @@ public interface ConstructorInvocation extends Invocation {
|
||||
* {@link Joinpoint#getStaticPart()} method (same result).
|
||||
* @return the constructor being called
|
||||
*/
|
||||
@Nonnull
|
||||
Constructor<?> getConstructor();
|
||||
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
package org.aopalliance.intercept;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* This interface represents an invocation in the program.
|
||||
@@ -34,7 +34,6 @@ public interface Invocation extends Joinpoint {
|
||||
* array to change the arguments.
|
||||
* @return the argument of the invocation
|
||||
*/
|
||||
@Nonnull
|
||||
Object[] getArguments();
|
||||
@Nullable Object[] getArguments();
|
||||
|
||||
}
|
||||
|
||||
@@ -18,8 +18,7 @@ package org.aopalliance.intercept;
|
||||
|
||||
import java.lang.reflect.AccessibleObject;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* This interface represents a generic runtime joinpoint (in the AOP
|
||||
@@ -49,23 +48,20 @@ public interface Joinpoint {
|
||||
* @return see the children interfaces' proceed definition
|
||||
* @throws Throwable if the joinpoint throws an exception
|
||||
*/
|
||||
@Nullable
|
||||
Object proceed() throws Throwable;
|
||||
@Nullable Object proceed() throws Throwable;
|
||||
|
||||
/**
|
||||
* Return the object that holds the current joinpoint's static part.
|
||||
* <p>For instance, the target object for an invocation.
|
||||
* @return the object (can be null if the accessible object is static)
|
||||
*/
|
||||
@Nullable
|
||||
Object getThis();
|
||||
@Nullable Object getThis();
|
||||
|
||||
/**
|
||||
* Return the static part of this joinpoint.
|
||||
* <p>The static part is an accessible object on which a chain of
|
||||
* interceptors is installed.
|
||||
*/
|
||||
@Nonnull
|
||||
AccessibleObject getStaticPart();
|
||||
|
||||
}
|
||||
|
||||
@@ -16,8 +16,7 @@
|
||||
|
||||
package org.aopalliance.intercept;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Intercepts calls on an interface on its way to the target. These
|
||||
@@ -55,7 +54,6 @@ public interface MethodInterceptor extends Interceptor {
|
||||
* @throws Throwable if the interceptors or the target object
|
||||
* throws an exception
|
||||
*/
|
||||
@Nullable
|
||||
Object invoke(@Nonnull MethodInvocation invocation) throws Throwable;
|
||||
@Nullable Object invoke(MethodInvocation invocation) throws Throwable;
|
||||
|
||||
}
|
||||
|
||||
@@ -18,8 +18,6 @@ package org.aopalliance.intercept;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Description of an invocation to a method, given to an interceptor
|
||||
* upon method-call.
|
||||
@@ -38,7 +36,6 @@ public interface MethodInvocation extends Invocation {
|
||||
* {@link Joinpoint#getStaticPart()} method (same result).
|
||||
* @return the method being called
|
||||
*/
|
||||
@Nonnull
|
||||
Method getMethod();
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
/**
|
||||
* The AOP Alliance reflective interception abstraction.
|
||||
*/
|
||||
@NullMarked
|
||||
package org.aopalliance.intercept;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
/**
|
||||
* Spring's variant of the AOP Alliance interfaces.
|
||||
*/
|
||||
@NullMarked
|
||||
package org.aopalliance;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,7 +18,7 @@ package org.springframework.aop;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* After returning advice is invoked only on normal method return, not if an
|
||||
@@ -41,6 +41,6 @@ public interface AfterReturningAdvice extends AfterAdvice {
|
||||
* allowed by the method signature. Otherwise the exception
|
||||
* will be wrapped as a runtime exception.
|
||||
*/
|
||||
void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable;
|
||||
void afterReturning(@Nullable Object returnValue, Method method, @Nullable Object[] args, @Nullable Object target) throws Throwable;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,7 +18,7 @@ package org.springframework.aop;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Advice invoked before a method is invoked. Such advices cannot
|
||||
@@ -40,6 +40,6 @@ public interface MethodBeforeAdvice extends BeforeAdvice {
|
||||
* allowed by the method signature. Otherwise the exception
|
||||
* will be wrapped as a runtime exception.
|
||||
*/
|
||||
void before(Method method, Object[] args, @Nullable Object target) throws Throwable;
|
||||
void before(Method method, @Nullable Object[] args, @Nullable Object target) throws Throwable;
|
||||
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user