mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
432 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6c03531c86 | |||
| 54dab45151 | |||
| 2e3a923225 | |||
| 77c6f160fe | |||
| 2d33aac350 | |||
| 711ddd1ac6 | |||
| 35a0d24dc3 | |||
| ae3dc0d4b1 | |||
| 1c9cff668c | |||
| 15511890bd | |||
| 22bfe7da5a | |||
| 5c6b82a947 | |||
| b7c3833732 | |||
| d91277095a | |||
| 50a0000ed7 | |||
| d6e9562aeb | |||
| f6089afd0e | |||
| 561583842f | |||
| bed3689001 | |||
| 3d85ec2354 | |||
| 330dcf15bc | |||
| 82b51479d5 | |||
| 2d408685ce | |||
| cc7815145f | |||
| a1586ff0a0 | |||
| 0637b699cd | |||
| c3aa0cd815 | |||
| a9fffa844f | |||
| bb04c6b1f3 | |||
| cb8b7a455a | |||
| 9dbb269bb9 | |||
| f3aa61c5bb | |||
| dc242df533 | |||
| c05b4bcd71 | |||
| 246f291f67 | |||
| 4eb93da31d | |||
| cabd1f53df | |||
| a471aa6a9a | |||
| 515295e205 | |||
| 1455a9fe22 | |||
| 36636b000b | |||
| 94050b3638 | |||
| 62db2680f5 | |||
| 5c8043dcea | |||
| 13df59cef4 | |||
| 78f08ed1dd | |||
| 03dd8e20e2 | |||
| dcab1f2237 | |||
| 3e93ca02cc | |||
| 583dc04986 | |||
| 749d7671a0 | |||
| 08bc959c9b | |||
| 4e6532f639 | |||
| 0c42874629 | |||
| 5253af1a04 | |||
| e3281a527c | |||
| a96f22d5e2 | |||
| 7a74e45946 | |||
| 8af3eb1aea | |||
| d26265420a | |||
| bede619e7d | |||
| bf2a72b043 | |||
| 67edcde0a2 | |||
| 81bc586e15 | |||
| 6f95af978e | |||
| d360def92e | |||
| 86b87d7d85 | |||
| 186e70cd04 | |||
| 91bb7d8daf | |||
| db1010f9c9 | |||
| cf87441a26 | |||
| dc982d0d51 | |||
| 996e66abdb | |||
| 96d9081190 | |||
| bf5b22d64f | |||
| 8b51b36729 | |||
| 2fea3d7921 | |||
| f47352ff04 | |||
| c1ed504ac1 | |||
| 5b660da52d | |||
| 3b4f8dbb8e | |||
| 4b0a048570 | |||
| 57632f9f08 | |||
| 7c009ccc1f | |||
| f421f38e24 | |||
| 88e26a9570 | |||
| 2fc78dfb69 | |||
| e52ee01ec8 | |||
| 2b1eb488fc | |||
| 5bec072dcb | |||
| 8736ca038a | |||
| aa9f0b3cb2 | |||
| 56b14dc54a | |||
| 2a74fe5ddb | |||
| 218a148898 | |||
| 38c473fd05 | |||
| d2bd0d5716 | |||
| 4d433174eb | |||
| 9f4d46fe33 | |||
| 2a1abb5553 | |||
| 7edd4e8e22 | |||
| 05b15812bb | |||
| 0e7aba4179 | |||
| cf8810042a | |||
| 88bc3f270e | |||
| b695dbc2bf | |||
| cf4ac96688 | |||
| 0a50854e1a | |||
| 637aa9f71f | |||
| b1b9ee0ccf | |||
| a30c06b883 | |||
| 458c30cb63 | |||
| 5698191ba0 | |||
| 7d197972e0 | |||
| 1732844137 | |||
| 7f40b49f4d | |||
| 2f070e59e6 | |||
| f648fd7c3b | |||
| 1ccd5512c5 | |||
| 5a86ab99df | |||
| 29d307e211 | |||
| 4c7735016b | |||
| 0a715bcab3 | |||
| 8d23ec4747 | |||
| 14bc0d6469 | |||
| 3bd342ee7d | |||
| 871860798e | |||
| e58ea0d945 | |||
| 8f56b4a8ae | |||
| 2ca10e20ca | |||
| 0ef5a4090b | |||
| 6df27644eb | |||
| 2f2c4188e5 | |||
| ccebaa6b0c | |||
| ae5cff1705 | |||
| 3e102e5d8e | |||
| 9cd4d32195 | |||
| 73a1c2d509 | |||
| 2b56ca08d4 | |||
| b89cf2b16a | |||
| 99e1b0d117 | |||
| 89d563097f | |||
| 10e8039de8 | |||
| 2086cda296 | |||
| 66235fa8c8 | |||
| cf31d088e2 | |||
| 4a74e1fc2d | |||
| e7d7cb8641 | |||
| 555d4a6004 | |||
| 1cdbcc58f3 | |||
| b46e528922 | |||
| e97ae434fb | |||
| 76f45c4289 | |||
| 97ebc43ea9 | |||
| 214a54dd6f | |||
| 875037c431 | |||
| 35e8ae2a84 | |||
| 26cab0616e | |||
| e1b1435a00 | |||
| 8e2134c6a7 | |||
| 7c7a59bdd2 | |||
| acd69d38e9 | |||
| 4a3daa7812 | |||
| 27d5213176 | |||
| 19dfc781f5 | |||
| 1d2b55e670 | |||
| 7f7c0d59f6 | |||
| 7d4db434a5 | |||
| 986c4fd926 | |||
| d6422d368a | |||
| 21ed8aad74 | |||
| 7211db9262 | |||
| 3b87c87a33 | |||
| 460ffbc0f6 | |||
| 8a67018e61 | |||
| 68ae1e5700 | |||
| 4b732d62c2 | |||
| 670fc9bb4e | |||
| 7a65aad09c | |||
| b1bf8c5242 | |||
| 6767f7010c | |||
| 8af1d8e842 | |||
| 052b6357c8 | |||
| 282ee02419 | |||
| 667e30f580 | |||
| f9883d8bd6 | |||
| 73ee86c666 | |||
| 76d00d78db | |||
| bf014ef18b | |||
| 13679bb906 | |||
| 6fc1f72704 | |||
| 04e69bdb26 | |||
| 3e48031601 | |||
| f285971cb3 | |||
| 89f6e8ec49 | |||
| 53ed15e4a7 | |||
| 9af4f5cf17 | |||
| 92d1ebefbb | |||
| 71245f9655 | |||
| f30a67c9d0 | |||
| 0435e9c49e | |||
| 38c831f15f | |||
| c4e0f96ef7 | |||
| 5ee11fb1b3 | |||
| 6a8f0d6d7d | |||
| 246e4977a2 | |||
| 4e5723ca9e | |||
| ffb1caaff4 | |||
| 918c5f1f33 | |||
| b431594021 | |||
| fc885224f0 | |||
| 5dc1190930 | |||
| 6f5d3a4b12 | |||
| 4c246b7c96 | |||
| 5345a13918 | |||
| 7fa2a28970 | |||
| 8172d7adfe | |||
| 6b64e9992a | |||
| c188f22378 | |||
| e53ed3e3c6 | |||
| f4c1ad7ae6 | |||
| 20be9e150c | |||
| 65d77624d1 | |||
| 107f47cfcf | |||
| fad544e077 | |||
| 19fec0633f | |||
| 729dc0b671 | |||
| 38d5c0fed6 | |||
| 3577e3b758 | |||
| e1bbdf0913 | |||
| 90867e7e62 | |||
| 5cac2ca11a | |||
| db3c7a157e | |||
| d2e55a2038 | |||
| ae6c64abc5 | |||
| c1bd9243ee | |||
| e66d965fed | |||
| aa4282d7f8 | |||
| 34f0c27b5e | |||
| 722199fe6d | |||
| 9cb8766977 | |||
| 8d24e22e33 | |||
| c79436f832 | |||
| 1ffffef85e | |||
| f0039833c8 | |||
| ac1176af53 | |||
| dcbc2ef134 | |||
| 4ec4e93ece | |||
| 7f0ab22c47 | |||
| 92f4e883b3 | |||
| dedc6a7e9b | |||
| c9e85ec297 | |||
| 1a8d64f9d8 | |||
| 13221ac0ef | |||
| 40da093f58 | |||
| b32a2cadfb | |||
| d87465f9e9 | |||
| 14a461e795 | |||
| 6461eec582 | |||
| be136d79ee | |||
| 9eea768205 | |||
| 19b5f11734 | |||
| 6f7f032ced | |||
| 7d4c8a403e | |||
| eb01cc0d9d | |||
| 30e75e4a09 | |||
| 1fa6ac30b5 | |||
| ddab971fca | |||
| b44c31e997 | |||
| 1ea593e777 | |||
| 70a545e13a | |||
| c29c67839b | |||
| f6dfbac582 | |||
| 6d9aba88d0 | |||
| db826551a6 | |||
| 646bd7f893 | |||
| 1cb2dfa459 | |||
| 79c54098ab | |||
| dc6c96de0a | |||
| b5ca646431 | |||
| 138e7a0e07 | |||
| 4ce2559980 | |||
| 1c5bb732b9 | |||
| 3d7ef3ebfc | |||
| 219004ef13 | |||
| 0758c8b14c | |||
| 4a6dbb17f4 | |||
| a29c84f663 | |||
| 390fe0fe78 | |||
| ac1a030c35 | |||
| fdbefad59c | |||
| bdcd10e228 | |||
| 861ef88d9f | |||
| 193424c465 | |||
| c76d681dc4 | |||
| 4149d5cb2b | |||
| 89a10d5c9b | |||
| 4ac521607e | |||
| 57c10a1fac | |||
| d81619addd | |||
| 246ebd24bf | |||
| 9a1e5eb8d7 | |||
| 4ff0f86ae2 | |||
| e68487a481 | |||
| cfb29db278 | |||
| 570c5b34e6 | |||
| fbc265b72b | |||
| 5acee7b22e | |||
| 7c5bcbcba4 | |||
| 3477738bed | |||
| fa5d246a1b | |||
| aeb77cf4e1 | |||
| 18dc7d73d6 | |||
| 17b2087198 | |||
| 9e7c64270b | |||
| 69dabe9df5 | |||
| 4d206f79d0 | |||
| 5c589833d7 | |||
| 9198774f13 | |||
| 430824038e | |||
| 260404b7f2 | |||
| 567547b63c | |||
| 98efa1df5a | |||
| 87ea3bd31a | |||
| f59c4023e9 | |||
| 58b8330e8d | |||
| c2d2e99c2f | |||
| eab1a3dc6b | |||
| 4b5e96578d | |||
| b0d08fe2d4 | |||
| 122372c580 | |||
| 9c610d9a70 | |||
| 5ae6c0e8ca | |||
| eb8492d597 | |||
| c98bebd6d3 | |||
| d0ffc16efc | |||
| 4bd1485ce4 | |||
| 4339c8eac2 | |||
| 233b59f144 | |||
| 3c00637c88 | |||
| 524588ef93 | |||
| bed4d684e6 | |||
| 229214a9b0 | |||
| 906109e129 | |||
| 00edba07ff | |||
| aee03c5201 | |||
| 89d746ddf8 | |||
| 3ddc512108 | |||
| 76eb5b8c19 | |||
| 491ebb4ec4 | |||
| f811f0dc18 | |||
| f5397d6426 | |||
| 2a1b30dbed | |||
| 734fc476ee | |||
| 5a2b127a21 | |||
| bfed6a3bc5 | |||
| 644887e094 | |||
| b9c304b890 | |||
| e1a32d4ba9 | |||
| 6383a0d7ca | |||
| 3fb170058f | |||
| f9fe8efb2e | |||
| ff9c7141c5 | |||
| 871f705bca | |||
| 22b41c33ba | |||
| 266953195c | |||
| 889c4e0ff5 | |||
| ad2e95be4b | |||
| 63ca8d5d17 | |||
| bc01e3116f | |||
| 480051a21c | |||
| c077805761 | |||
| ceb7afdca2 | |||
| 70004e9ad0 | |||
| 969d0bd08b | |||
| a8fb16b47c | |||
| bc2257aaff | |||
| 218957f0e8 | |||
| a001319f1f | |||
| 874e61a0c6 | |||
| 4a02893c31 | |||
| 902e5707a8 | |||
| f529386ce2 | |||
| b8715811f8 | |||
| 08998376cc | |||
| 6adda31328 | |||
| c6e6a3e44d | |||
| bc2895a30b | |||
| d86af57e0f | |||
| d4cde29f75 | |||
| 5f1e25a61f | |||
| 0ddfe3977a | |||
| c6146ea2db | |||
| eefdee7983 | |||
| ab48ac36e9 | |||
| c1f0faade7 | |||
| cd2a3fdd8d | |||
| 71dfebbfe5 | |||
| a85bf3185e | |||
| 6d5bf6d9b3 | |||
| 6b482df6fa | |||
| ea4e7df9ca | |||
| 7c07c43201 | |||
| 6791ea94a0 | |||
| 7ee8e66c7f | |||
| 34372ee32b | |||
| b6df5a677e | |||
| fc9a118406 | |||
| 87e6d1b7da | |||
| ea3573176a | |||
| bcf235cc66 | |||
| 7ad197e8e1 | |||
| 7e67da8a26 | |||
| d4e8daaede | |||
| e788aeb25b | |||
| 4b2e40153a | |||
| aef4b21f19 | |||
| 5bd1c1fddb | |||
| 5d22aa9770 | |||
| e3aa5b6b11 | |||
| 00e05e603d | |||
| 0fbfecd392 | |||
| b4131ce131 | |||
| 0f707706f1 | |||
| f526b23fd7 | |||
| 2fc8b13dd5 | |||
| e73bbd4ad3 | |||
| 9f8038963f | |||
| 4f8eca1350 | |||
| 18ea43c905 | |||
| bcccba50c1 | |||
| 8288db496d |
@@ -0,0 +1,17 @@
|
||||
name: Print JVM thread dumps
|
||||
description: Prints a thread dump for all running JVMs
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- if: ${{ runner.os == 'Linux' }}
|
||||
shell: bash
|
||||
run: |
|
||||
for jvm_pid in $(jps -q -J-XX:+PerfDisableSharedMem); do
|
||||
jcmd $java_pid Thread.print
|
||||
done
|
||||
- if: ${{ runner.os == 'Windows' }}
|
||||
shell: powershell
|
||||
run: |
|
||||
foreach ($jvm_pid in $(jps -q -J-XX:+PerfDisableSharedMem)) {
|
||||
jcmd $jvm_pid Thread.print
|
||||
}
|
||||
@@ -2,7 +2,7 @@ name: Build and deploy snapshot
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 6.1.x
|
||||
- main
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
name: Build Pull Request
|
||||
on: pull_request
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build pull request
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
steps:
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'liberica'
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Validate Gradle wrapper
|
||||
uses: gradle/wrapper-validation-action@699bb18358f12c5b78b37bb0111d3a0e2276e0e2
|
||||
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
CI: 'true'
|
||||
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
|
||||
run: ./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false --no-daemon --no-parallel --continue build
|
||||
|
||||
- name: Print JVM thread dumps when cancelled
|
||||
uses: ./.github/actions/print-jvm-thread-dumps
|
||||
if: cancelled()
|
||||
|
||||
- name: Upload build reports
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: build-reports
|
||||
path: '**/build/reports/'
|
||||
@@ -1,8 +1,7 @@
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 6.1.x
|
||||
schedule:
|
||||
- cron: '30 9 * * *'
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [](https://github.com/spring-projects/spring-framework/actions/workflows/build-and-deploy-snapshot.yml?query=branch%3A6.1.x) [](https://ge.spring.io/scans?search.rootProjectNames=spring)
|
||||
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [](https://github.com/spring-projects/spring-framework/actions/workflows/build-and-deploy-snapshot.yml?query=branch%3Amain) [](https://ge.spring.io/scans?search.rootProjectNames=spring)
|
||||
|
||||
This is the home of the Spring Framework: the foundation for all [Spring projects](https://spring.io/projects). Collectively the Spring Framework and the family of Spring projects are often referred to simply as "Spring".
|
||||
|
||||
|
||||
+2
-1
@@ -2,13 +2,14 @@ plugins {
|
||||
id 'io.freefair.aspectj' version '8.4' apply false
|
||||
// kotlinVersion is managed in gradle.properties
|
||||
id 'org.jetbrains.kotlin.plugin.serialization' version "${kotlinVersion}" apply false
|
||||
id 'org.jetbrains.dokka' version '1.8.20'
|
||||
id 'org.jetbrains.dokka' version '1.9.20'
|
||||
id 'org.unbroken-dome.xjc' version '2.0.0' apply false
|
||||
id 'com.github.ben-manes.versions' version '0.51.0'
|
||||
id 'com.github.johnrengelman.shadow' version '8.1.1' apply false
|
||||
id 'de.undercouch.download' version '5.4.0'
|
||||
id 'me.champeau.jmh' version '0.7.2' apply false
|
||||
id 'me.champeau.mrjar' version '0.1.1'
|
||||
id "net.ltgt.errorprone" version "3.1.0" apply false
|
||||
}
|
||||
|
||||
ext {
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@ NOTE: CI is being migrated to GitHub Actions.
|
||||
|
||||
The Spring Framework uses https://concourse-ci.org/[Concourse] for its CI build and other automated tasks.
|
||||
The Spring team has a dedicated Concourse instance available at https://ci.spring.io with a build pipeline
|
||||
for https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-6.1.x[Spring Framework 6.1.x].
|
||||
for https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-6.2.x[Spring Framework 6.2.x].
|
||||
|
||||
=== Setting up your development environment
|
||||
|
||||
@@ -53,7 +53,7 @@ The pipeline can be deployed using the following command:
|
||||
|
||||
[source]
|
||||
----
|
||||
$ fly -t spring set-pipeline -p spring-framework-6.1.x -c ci/pipeline.yml -l ci/parameters.yml
|
||||
$ fly -t spring set-pipeline -p spring-framework-6.2.x -c ci/pipeline.yml -l ci/parameters.yml
|
||||
----
|
||||
|
||||
NOTE: This assumes that you have credhub integration configured with the appropriate secrets.
|
||||
|
||||
+2
-2
@@ -3,8 +3,8 @@ github-repo-name: "spring-projects/spring-framework"
|
||||
sonatype-staging-profile: "org.springframework"
|
||||
docker-hub-organization: "springci"
|
||||
artifactory-server: "https://repo.spring.io"
|
||||
branch: "6.1.x"
|
||||
milestone: "6.1.x"
|
||||
branch: "main"
|
||||
milestone: "6.2.x"
|
||||
build-name: "spring-framework"
|
||||
pipeline-name: "spring-framework"
|
||||
concourse-url: "https://ci.spring.io"
|
||||
|
||||
@@ -20,6 +20,8 @@ asciidoc:
|
||||
fold: 'all'
|
||||
table-stripes: 'odd'
|
||||
include-java: 'example$docs-src/main/java/org/springframework/docs'
|
||||
include-kotlin: 'example$docs-src/main/kotlin/org/springframework/docs'
|
||||
include-xml: 'example$docs-src/main/resources/org/springframework/docs'
|
||||
spring-site: 'https://spring.io'
|
||||
spring-site-blog: '{spring-site}/blog'
|
||||
spring-site-cve: "{spring-site}/security"
|
||||
|
||||
@@ -30,7 +30,7 @@ antora {
|
||||
'@asciidoctor/tabs': '1.0.0-beta.3',
|
||||
'@opendevise/antora-release-line-extension': '1.0.0',
|
||||
'@springio/antora-extensions': '1.8.2',
|
||||
'@springio/asciidoctor-extensions': '1.0.0-alpha.9'
|
||||
'@springio/asciidoctor-extensions': '1.0.0-alpha.10'
|
||||
]
|
||||
}
|
||||
|
||||
@@ -60,10 +60,25 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
api(project(":spring-context"))
|
||||
api(project(":spring-jdbc"))
|
||||
api(project(":spring-jms"))
|
||||
api(project(":spring-web"))
|
||||
api(project(":spring-webmvc"))
|
||||
api(project(":spring-context-support"))
|
||||
api(project(":spring-aspects"))
|
||||
api(project(":spring-websocket"))
|
||||
|
||||
api("org.jetbrains.kotlin:kotlin-stdlib")
|
||||
api("jakarta.jms:jakarta.jms-api")
|
||||
api("jakarta.servlet:jakarta.servlet-api")
|
||||
api("org.apache.commons:commons-dbcp2:2.11.0")
|
||||
api("com.mchange:c3p0:0.9.5.5")
|
||||
api("com.fasterxml.jackson.core:jackson-databind")
|
||||
api("com.fasterxml.jackson.module:jackson-module-parameter-names")
|
||||
api("jakarta.validation:jakarta.validation-api")
|
||||
api("org.aspectj:aspectjweaver")
|
||||
api("io.projectreactor.netty:reactor-netty-http")
|
||||
api("org.eclipse.jetty.websocket:jetty-websocket-jetty-api")
|
||||
|
||||
implementation(project(":spring-core-test"))
|
||||
implementation("org.assertj:assertj-core")
|
||||
|
||||
@@ -183,6 +183,7 @@
|
||||
***** xref:testing/annotations/integration-spring/annotation-sqlmergemode.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-sqlgroup.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-disabledinaotmode.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-beanoverriding.adoc[]
|
||||
**** xref:testing/annotations/integration-junit4.adoc[]
|
||||
**** xref:testing/annotations/integration-junit-jupiter.adoc[]
|
||||
**** xref:testing/annotations/integration-meta.adoc[]
|
||||
|
||||
@@ -205,7 +205,7 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val person = factory.getBean("person") as Person;
|
||||
val person = factory.getBean("person") as Person
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -128,18 +128,7 @@ the resulting pointcut is effectively the union of the specified patterns.)
|
||||
|
||||
The following example shows how to use `JdkRegexpMethodPointcut`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<bean id="settersAndAbsquatulatePointcut"
|
||||
class="org.springframework.aop.support.JdkRegexpMethodPointcut">
|
||||
<property name="patterns">
|
||||
<list>
|
||||
<value>.*set.*</value>
|
||||
<value>.*absquatulate</value>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
include-code::./JdkRegexpConfiguration[tag=snippet,indent=0]
|
||||
|
||||
Spring provides a convenience class named `RegexpMethodPointcutAdvisor`, which lets us
|
||||
also reference an `Advice` (remember that an `Advice` can be an interceptor, before advice,
|
||||
@@ -147,21 +136,7 @@ throws advice, and others). Behind the scenes, Spring uses a `JdkRegexpMethodPoi
|
||||
Using `RegexpMethodPointcutAdvisor` simplifies wiring, as the one bean encapsulates both
|
||||
pointcut and advice, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<bean id="settersAndAbsquatulateAdvisor"
|
||||
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
|
||||
<property name="advice">
|
||||
<ref bean="beanNameOfAopAllianceInterceptor"/>
|
||||
</property>
|
||||
<property name="patterns">
|
||||
<list>
|
||||
<value>.*set.*</value>
|
||||
<value>.*absquatulate</value>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
include-code::./RegexpConfiguration[tag=snippet,indent=0]
|
||||
|
||||
You can use `RegexpMethodPointcutAdvisor` with any `Advice` type.
|
||||
|
||||
|
||||
@@ -8,54 +8,8 @@ determines that a bean is advised by one or more aspects, it automatically gener
|
||||
a proxy for that bean to intercept method invocations and ensures that advice is run
|
||||
as needed.
|
||||
|
||||
The @AspectJ support can be enabled with XML- or Java-style configuration. In either
|
||||
case, you also need to ensure that AspectJ's `aspectjweaver.jar` library is on the
|
||||
classpath of your application (version 1.9 or later). This library is available in the
|
||||
`lib` directory of an AspectJ distribution or from the Maven Central repository.
|
||||
|
||||
|
||||
[[aop-enable-aspectj-java]]
|
||||
== Enabling @AspectJ Support with Java Configuration
|
||||
|
||||
To enable @AspectJ support with Java `@Configuration`, add the `@EnableAspectJAutoProxy`
|
||||
annotation, as the following example shows:
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableAspectJAutoProxy
|
||||
public class AppConfig {
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableAspectJAutoProxy
|
||||
class AppConfig
|
||||
----
|
||||
======
|
||||
|
||||
[[aop-enable-aspectj-xml]]
|
||||
== Enabling @AspectJ Support with XML Configuration
|
||||
|
||||
To enable @AspectJ support with XML-based configuration, use the `aop:aspectj-autoproxy`
|
||||
element, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:aspectj-autoproxy/>
|
||||
----
|
||||
|
||||
This assumes that you use schema support as described in
|
||||
xref:core/appendix/xsd-schemas.adoc[XML Schema-based configuration].
|
||||
See xref:core/appendix/xsd-schemas.adoc#aop[the AOP schema] for how to
|
||||
import the tags in the `aop` namespace.
|
||||
|
||||
|
||||
The @AspectJ support can be enabled with programmatic or XML configuration. In either
|
||||
case, you also need to ensure that AspectJ's `org.aspectj:aspectjweaver` library is on the
|
||||
classpath of your application (version 1.9 or later).
|
||||
|
||||
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
|
||||
|
||||
@@ -9,43 +9,12 @@ minimal steps required for a not-very-useful aspect.
|
||||
The first of the two examples shows a regular bean definition in the application context
|
||||
that points to a bean class that is annotated with `@Aspect`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<bean id="myAspect" class="com.xyz.NotVeryUsefulAspect">
|
||||
<!-- configure properties of the aspect here -->
|
||||
</bean>
|
||||
----
|
||||
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
|
||||
|
||||
The second of the two examples shows the `NotVeryUsefulAspect` class definition, which is
|
||||
annotated with `@Aspect`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary",chomp="-packages",fold="none"]
|
||||
----
|
||||
package com.xyz;
|
||||
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
|
||||
@Aspect
|
||||
public class NotVeryUsefulAspect {
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages",fold="none"]
|
||||
----
|
||||
package com.xyz
|
||||
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
|
||||
@Aspect
|
||||
class NotVeryUsefulAspect
|
||||
----
|
||||
======
|
||||
include-code::./NotVeryUsefulAspect[tag=snippet,indent=0]
|
||||
|
||||
Aspects (classes annotated with `@Aspect`) can have methods and fields, the same as any
|
||||
other class. They can also contain pointcut, advice, and introduction (inter-type)
|
||||
|
||||
@@ -16,93 +16,9 @@ aspect.
|
||||
Because we want to retry the operation, we need to use around advice so that we can
|
||||
call `proceed` multiple times. The following listing shows the basic aspect implementation:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Aspect
|
||||
public class ConcurrentOperationExecutor implements Ordered {
|
||||
include-code::./ConcurrentOperationExecutor[tag=snippet,indent=0]
|
||||
|
||||
private static final int DEFAULT_MAX_RETRIES = 2;
|
||||
|
||||
private int maxRetries = DEFAULT_MAX_RETRIES;
|
||||
private int order = 1;
|
||||
|
||||
public void setMaxRetries(int maxRetries) {
|
||||
this.maxRetries = maxRetries;
|
||||
}
|
||||
|
||||
public int getOrder() {
|
||||
return this.order;
|
||||
}
|
||||
|
||||
public void setOrder(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
@Around("com.xyz.CommonPointcuts.businessService()") // <1>
|
||||
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
|
||||
int numAttempts = 0;
|
||||
PessimisticLockingFailureException lockFailureException;
|
||||
do {
|
||||
numAttempts++;
|
||||
try {
|
||||
return pjp.proceed();
|
||||
}
|
||||
catch(PessimisticLockingFailureException ex) {
|
||||
lockFailureException = ex;
|
||||
}
|
||||
} while(numAttempts <= this.maxRetries);
|
||||
throw lockFailureException;
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> References the `businessService` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[Sharing Named Pointcut Definitions].
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Aspect
|
||||
class ConcurrentOperationExecutor : Ordered {
|
||||
|
||||
private val DEFAULT_MAX_RETRIES = 2
|
||||
private var maxRetries = DEFAULT_MAX_RETRIES
|
||||
private var order = 1
|
||||
|
||||
fun setMaxRetries(maxRetries: Int) {
|
||||
this.maxRetries = maxRetries
|
||||
}
|
||||
|
||||
override fun getOrder(): Int {
|
||||
return this.order
|
||||
}
|
||||
|
||||
fun setOrder(order: Int) {
|
||||
this.order = order
|
||||
}
|
||||
|
||||
@Around("com.xyz.CommonPointcuts.businessService()") // <1>
|
||||
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any? {
|
||||
var numAttempts = 0
|
||||
var lockFailureException: PessimisticLockingFailureException
|
||||
do {
|
||||
numAttempts++
|
||||
try {
|
||||
return pjp.proceed()
|
||||
} catch (ex: PessimisticLockingFailureException) {
|
||||
lockFailureException = ex
|
||||
}
|
||||
|
||||
} while (numAttempts <= this.maxRetries)
|
||||
throw lockFailureException
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> References the `businessService` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[Sharing Named Pointcut Definitions].
|
||||
======
|
||||
`@Around("com.xyz.CommonPointcuts.businessService()")` references the `businessService` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[Sharing Named Pointcut Definitions].
|
||||
|
||||
Note that the aspect implements the `Ordered` interface so that we can set the precedence of
|
||||
the aspect higher than the transaction advice (we want a fresh transaction each time we
|
||||
@@ -114,70 +30,15 @@ we have exhausted all of our retry attempts.
|
||||
|
||||
The corresponding Spring configuration follows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:aspectj-autoproxy/>
|
||||
|
||||
<bean id="concurrentOperationExecutor"
|
||||
class="com.xyz.service.impl.ConcurrentOperationExecutor">
|
||||
<property name="maxRetries" value="3"/>
|
||||
<property name="order" value="100"/>
|
||||
</bean>
|
||||
----
|
||||
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
|
||||
|
||||
To refine the aspect so that it retries only idempotent operations, we might define the following
|
||||
`Idempotent` annotation:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
// marker annotation
|
||||
public @interface Idempotent {
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
// marker annotation
|
||||
annotation class Idempotent
|
||||
----
|
||||
======
|
||||
include-code::./service/Idempotent[tag=snippet,indent=0]
|
||||
|
||||
We can then use the annotation to annotate the implementation of service operations. The change
|
||||
to the aspect to retry only idempotent operations involves refining the pointcut
|
||||
expression so that only `@Idempotent` operations match, as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Around("execution(* com.xyz..service.*.*(..)) && " +
|
||||
"@annotation(com.xyz.service.Idempotent)")
|
||||
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Around("execution(* com.xyz..service.*.*(..)) && " +
|
||||
"@annotation(com.xyz.service.Idempotent)")
|
||||
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any? {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
include-code::./service/SampleService[tag=snippet,indent=0]
|
||||
|
||||
@@ -72,7 +72,7 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
val usageTracked = context.getBean("myService", UsageTracked.class)
|
||||
val usageTracked = context.getBean<UsageTracked>("myService")
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ Kotlin::
|
||||
When used as a marker interface in this way, Spring configures new instances of the
|
||||
annotated type (`Account`, in this case) by using a bean definition (typically
|
||||
prototype-scoped) with the same name as the fully-qualified type name
|
||||
(`com.xyz.domain.Account`). Since the default name for a bean is the
|
||||
(`com.xyz.domain.Account`). Since the default name for a bean defined via XML is the
|
||||
fully-qualified name of its type, a convenient way to declare the prototype definition
|
||||
is to omit the `id` attribute, as the following example shows:
|
||||
|
||||
@@ -177,41 +177,10 @@ either use a build-time Ant or Maven task to do this (see, for example, the
|
||||
{aspectj-docs-devguide}/antTasks.html[AspectJ Development
|
||||
Environment Guide]) or load-time weaving (see xref:core/aop/using-aspectj.adoc#aop-aj-ltw[Load-time Weaving with AspectJ in the Spring Framework]). The
|
||||
`AnnotationBeanConfigurerAspect` itself needs to be configured by Spring (in order to obtain
|
||||
a reference to the bean factory that is to be used to configure new objects). If you
|
||||
use Java-based configuration, you can add `@EnableSpringConfigured` to any
|
||||
`@Configuration` class, as follows:
|
||||
a reference to the bean factory that is to be used to configure new objects). You can define
|
||||
the related configuration as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableSpringConfigured
|
||||
public class AppConfig {
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableSpringConfigured
|
||||
class AppConfig {
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
If you prefer XML based configuration, the Spring
|
||||
xref:core/appendix/xsd-schemas.adoc#context[`context` namespace]
|
||||
defines a convenient `context:spring-configured` element, which you can use as follows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<context:spring-configured/>
|
||||
----
|
||||
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
|
||||
|
||||
Instances of `@Configurable` objects created before the aspect has been configured
|
||||
result in a message being issued to the debug log and no configuration of the
|
||||
@@ -783,52 +752,9 @@ adding one line. (Note that you almost certainly need to use an
|
||||
`ApplicationContext` as your Spring container -- typically, a `BeanFactory` is not
|
||||
enough because the LTW support uses `BeanFactoryPostProcessors`.)
|
||||
|
||||
To enable the Spring Framework's LTW support, you need to configure a `LoadTimeWeaver`,
|
||||
which typically is done by using the `@EnableLoadTimeWeaving` annotation, as follows:
|
||||
To enable the Spring Framework's LTW support, you need to configure a `LoadTimeWeaver` as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableLoadTimeWeaving
|
||||
public class AppConfig {
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableLoadTimeWeaving
|
||||
class AppConfig {
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Alternatively, if you prefer XML-based configuration, use the
|
||||
`<context:load-time-weaver/>` element. Note that the element is defined in the
|
||||
`context` namespace. The following example shows how to use `<context:load-time-weaver/>`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
https://www.springframework.org/schema/context/spring-context.xsd">
|
||||
|
||||
<context:load-time-weaver/>
|
||||
|
||||
</beans>
|
||||
----
|
||||
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
|
||||
|
||||
The preceding configuration automatically defines and registers a number of LTW-specific
|
||||
infrastructure beans, such as a `LoadTimeWeaver` and an `AspectJWeavingEnabler`, for you.
|
||||
@@ -864,63 +790,12 @@ Note that the table lists only the `LoadTimeWeavers` that are autodetected when
|
||||
use the `DefaultContextLoadTimeWeaver`. You can specify exactly which `LoadTimeWeaver`
|
||||
implementation to use.
|
||||
|
||||
To specify a specific `LoadTimeWeaver` with Java configuration, implement the
|
||||
`LoadTimeWeavingConfigurer` interface and override the `getLoadTimeWeaver()` method.
|
||||
To configure a specific `LoadTimeWeaver`, implement the
|
||||
`LoadTimeWeavingConfigurer` interface and override the `getLoadTimeWeaver()` method
|
||||
(or use the XML equivalent).
|
||||
The following example specifies a `ReflectiveLoadTimeWeaver`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableLoadTimeWeaving
|
||||
public class AppConfig implements LoadTimeWeavingConfigurer {
|
||||
|
||||
@Override
|
||||
public LoadTimeWeaver getLoadTimeWeaver() {
|
||||
return new ReflectiveLoadTimeWeaver();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableLoadTimeWeaving
|
||||
class AppConfig : LoadTimeWeavingConfigurer {
|
||||
|
||||
override fun getLoadTimeWeaver(): LoadTimeWeaver {
|
||||
return ReflectiveLoadTimeWeaver()
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
If you use XML-based configuration, you can specify the fully qualified class name
|
||||
as the value of the `weaver-class` attribute on the `<context:load-time-weaver/>`
|
||||
element. Again, the following example specifies a `ReflectiveLoadTimeWeaver`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
https://www.springframework.org/schema/context/spring-context.xsd">
|
||||
|
||||
<context:load-time-weaver
|
||||
weaver-class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>
|
||||
|
||||
</beans>
|
||||
----
|
||||
include-code::./CustomWeaverConfiguration[tag=snippet,indent=0]
|
||||
|
||||
The `LoadTimeWeaver` that is defined and registered by the configuration can be later
|
||||
retrieved from the Spring container by using the well known name, `loadTimeWeaver`.
|
||||
|
||||
+46
-3
@@ -1,5 +1,5 @@
|
||||
[[beans-autowired-annotation-primary]]
|
||||
= Fine-tuning Annotation-based Autowiring with `@Primary`
|
||||
= Fine-tuning Annotation-based Autowiring with `@Primary` or `@Fallback`
|
||||
|
||||
Because autowiring by type may lead to multiple candidates, it is often necessary to have
|
||||
more control over the selection process. One way to accomplish this is with Spring's
|
||||
@@ -50,8 +50,51 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
With the preceding configuration, the following `MovieRecommender` is autowired with the
|
||||
`firstMovieCatalog`:
|
||||
Alternatively, as of 6.2, there is a `@Fallback` annotation for demarcating
|
||||
any beans other than the regular ones to be injected. If only one regular
|
||||
bean is left, it is effectively primary as well:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class MovieConfiguration {
|
||||
|
||||
@Bean
|
||||
public MovieCatalog firstMovieCatalog() { ... }
|
||||
|
||||
@Bean
|
||||
@Fallback
|
||||
public MovieCatalog secondMovieCatalog() { ... }
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class MovieConfiguration {
|
||||
|
||||
@Bean
|
||||
fun firstMovieCatalog(): MovieCatalog { ... }
|
||||
|
||||
@Bean
|
||||
@Fallback
|
||||
fun secondMovieCatalog(): MovieCatalog { ... }
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
With both variants of the preceding configuration, the following
|
||||
`MovieRecommender` is autowired with the `firstMovieCatalog`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
||||
+7
-6
@@ -1,12 +1,13 @@
|
||||
[[beans-autowired-annotation-qualifiers]]
|
||||
= Fine-tuning Annotation-based Autowiring with Qualifiers
|
||||
|
||||
`@Primary` is an effective way to use autowiring by type with several instances when one
|
||||
primary candidate can be determined. When you need more control over the selection process,
|
||||
you can use Spring's `@Qualifier` annotation. You can associate qualifier values
|
||||
with specific arguments, narrowing the set of type matches so that a specific bean is
|
||||
chosen for each argument. In the simplest case, this can be a plain descriptive value, as
|
||||
shown in the following example:
|
||||
`@Primary` and `@Fallback` are effective ways to use autowiring by type with several
|
||||
instances when one primary (or non-fallback) candidate can be determined.
|
||||
|
||||
When you need more control over the selection process, you can use Spring's `@Qualifier`
|
||||
annotation. You can associate qualifier values with specific arguments, narrowing the set
|
||||
of type matches so that a specific bean is chosen for each argument. In the simplest case,
|
||||
this can be a plain descriptive value, as shown in the following example:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
|
||||
+2
-2
@@ -101,8 +101,8 @@ NOTE: When configuring a `PropertySourcesPlaceholderConfigurer` using JavaConfig
|
||||
|
||||
Using the above configuration ensures Spring initialization failure if any `${}`
|
||||
placeholder could not be resolved. It is also possible to use methods like
|
||||
`setPlaceholderPrefix`, `setPlaceholderSuffix`, or `setValueSeparator` to customize
|
||||
placeholders.
|
||||
`setPlaceholderPrefix`, `setPlaceholderSuffix`, `setValueSeparator`, or
|
||||
`setEscapeCharacter` to customize placeholders.
|
||||
|
||||
NOTE: Spring Boot configures by default a `PropertySourcesPlaceholderConfigurer` bean that
|
||||
will get properties from `application.properties` and `application.yml` files.
|
||||
|
||||
@@ -73,7 +73,23 @@ runtime (concurrently with live access to the factory) is not officially support
|
||||
lead to concurrent access exceptions, inconsistent state in the bean container, or both.
|
||||
====
|
||||
|
||||
[[beans-definition-overriding]]
|
||||
== Overriding Beans
|
||||
|
||||
Bean overriding is happening when a bean is registered using an identifier that is
|
||||
already allocated. While bean overriding is possible, it makes the configuration harder
|
||||
to read and this feature will be deprecated in a future release.
|
||||
|
||||
To disable bean overriding altogether, you can set the `allowBeanDefinitionOverriding`
|
||||
to `false` on the `ApplicationContext` before it is refreshed. In such setup, an
|
||||
exception is thrown if bean overriding is used.
|
||||
|
||||
By default, the container logs every bean overriding at `INFO` level so that you can
|
||||
adapt your configuration accordingly. While not recommended, you can silence those logs
|
||||
by setting the `allowBeanDefinitionOverriding` flag to `true`.
|
||||
|
||||
NOTE: We acknowledge that overriding beans in a test is convenient, and there is
|
||||
explicit support for this. For more details please refer to xref:testing/annotations/integration-spring/annotation-beanoverriding.adoc[this section].
|
||||
|
||||
[[beans-beanname]]
|
||||
== Naming Beans
|
||||
|
||||
+41
-4
@@ -503,10 +503,47 @@ way, navigating `@Configuration` classes and their dependencies becomes no diffe
|
||||
than the usual process of navigating interface-based code.
|
||||
--
|
||||
|
||||
TIP: If you want to influence the startup creation order of certain beans, consider
|
||||
declaring some of them as `@Lazy` (for creation on first access instead of on startup)
|
||||
or as `@DependsOn` certain other beans (making sure that specific other beans are
|
||||
created before the current bean, beyond what the latter's direct dependencies imply).
|
||||
|
||||
[[beans-java-startup]]
|
||||
== Influencing the Startup of `@Bean`-defined Singletons
|
||||
|
||||
If you want to influence the startup creation order of certain singleton beans, consider
|
||||
declaring some of them as `@Lazy` for creation on first access instead of on startup.
|
||||
|
||||
`@DependsOn` forces certain other beans to be initialized first, making sure that
|
||||
the specified beans are created before the current bean, beyond what the latter's
|
||||
direct dependencies imply.
|
||||
|
||||
[[beans-java-startup-background]]
|
||||
=== Background Initialization
|
||||
|
||||
As of 6.2, there is a background initialization option: `@Bean(bootstrap=BACKGROUND)`
|
||||
allows for singling out specific beans for background initialization, covering the
|
||||
entire bean creation step for each such bean on context startup.
|
||||
|
||||
Dependent beans with non-lazy injection points automatically wait for the bean instance
|
||||
to be completed. All regular background initializations are forced to complete at the end
|
||||
of context startup. Only beans additionally marked as `@Lazy` are allowed to be completed
|
||||
later (up until the first actual access).
|
||||
|
||||
Background initialization typically goes together with `@Lazy` (or `ObjectProvider`)
|
||||
injection points in dependent beans. Otherwise, the main bootstrap thread is going to
|
||||
block when an actual background-initialized bean instance needs to be injected early.
|
||||
|
||||
This form of concurrent startup applies to individual beans: if such a bean depends on
|
||||
other beans, they need to have been initialized already, either simply through being
|
||||
declared earlier or through `@DependsOn` which enforces initialization in the main
|
||||
bootstrap thread before background initialization for the affected bean is triggered.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
A `bootstrapExecutor` bean of type `Executor` must be declared for background
|
||||
bootstrapping to be actually active. Otherwise, the background markers will be ignored at
|
||||
runtime.
|
||||
|
||||
The bootstrap executor may be a bounded executor just for startup purposes or a shared
|
||||
thread pool which serves for other purposes as well.
|
||||
====
|
||||
|
||||
|
||||
[[beans-java-conditional]]
|
||||
|
||||
@@ -1,219 +1,34 @@
|
||||
[[expressions-beandef]]
|
||||
= Expressions in Bean Definitions
|
||||
|
||||
You can use SpEL expressions with XML-based or annotation-based configuration metadata for
|
||||
defining `BeanDefinition` instances. In both cases, the syntax to define the expression is of the
|
||||
form `#{ <expression string> }`.
|
||||
|
||||
|
||||
|
||||
[[expressions-beandef-xml-based]]
|
||||
== XML Configuration
|
||||
|
||||
A property or constructor argument value can be set by using expressions, as the following
|
||||
example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<bean id="numberGuess" class="org.spring.samples.NumberGuess">
|
||||
<property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
|
||||
|
||||
<!-- other properties -->
|
||||
</bean>
|
||||
----
|
||||
You can use SpEL expressions with configuration metadata for defining bean instances. In both
|
||||
cases, the syntax to define the expression is of the form `#{ <expression string> }`.
|
||||
|
||||
All beans in the application context are available as predefined variables with their
|
||||
common bean name. This includes standard context beans such as `environment` (of type
|
||||
`org.springframework.core.env.Environment`) as well as `systemProperties` and
|
||||
`systemEnvironment` (of type `Map<String, Object>`) for access to the runtime environment.
|
||||
|
||||
The following example shows access to the `systemProperties` bean as a SpEL variable:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<bean id="taxCalculator" class="org.spring.samples.TaxCalculator">
|
||||
<property name="defaultLocale" value="#{ systemProperties['user.region'] }"/>
|
||||
|
||||
<!-- other properties -->
|
||||
</bean>
|
||||
----
|
||||
|
||||
Note that you do not have to prefix the predefined variable with the `#` symbol here.
|
||||
|
||||
You can also refer to other bean properties by name, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<bean id="numberGuess" class="org.spring.samples.NumberGuess">
|
||||
<property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
|
||||
|
||||
<!-- other properties -->
|
||||
</bean>
|
||||
|
||||
<bean id="shapeGuess" class="org.spring.samples.ShapeGuess">
|
||||
<property name="initialShapeSeed" value="#{ numberGuess.randomNumber }"/>
|
||||
|
||||
<!-- other properties -->
|
||||
</bean>
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[expressions-beandef-annotation-based]]
|
||||
== Annotation Configuration
|
||||
|
||||
To specify a default value, you can place the `@Value` annotation on fields, methods,
|
||||
and method or constructor parameters.
|
||||
and method or constructor parameters (or XML equivalent).
|
||||
|
||||
The following example sets the default value of a field:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class FieldValueTestBean {
|
||||
include-code::./FieldValueTestBean[tag=snippet,indent=0]
|
||||
|
||||
@Value("#{ systemProperties['user.region'] }")
|
||||
private String defaultLocale;
|
||||
|
||||
public void setDefaultLocale(String defaultLocale) {
|
||||
this.defaultLocale = defaultLocale;
|
||||
}
|
||||
|
||||
public String getDefaultLocale() {
|
||||
return this.defaultLocale;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class FieldValueTestBean {
|
||||
|
||||
@Value("#{ systemProperties['user.region'] }")
|
||||
var defaultLocale: String? = null
|
||||
}
|
||||
----
|
||||
======
|
||||
Note that you do not have to prefix the predefined variable with the `#` symbol here.
|
||||
|
||||
The following example shows the equivalent but on a property setter method:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class PropertyValueTestBean {
|
||||
|
||||
private String defaultLocale;
|
||||
|
||||
@Value("#{ systemProperties['user.region'] }")
|
||||
public void setDefaultLocale(String defaultLocale) {
|
||||
this.defaultLocale = defaultLocale;
|
||||
}
|
||||
|
||||
public String getDefaultLocale() {
|
||||
return this.defaultLocale;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class PropertyValueTestBean {
|
||||
|
||||
@Value("#{ systemProperties['user.region'] }")
|
||||
var defaultLocale: String? = null
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./PropertyValueTestBean[tag=snippet,indent=0]
|
||||
|
||||
Autowired methods and constructors can also use the `@Value` annotation, as the following
|
||||
examples show:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class SimpleMovieLister {
|
||||
|
||||
private MovieFinder movieFinder;
|
||||
private String defaultLocale;
|
||||
|
||||
@Autowired
|
||||
public void configure(MovieFinder movieFinder,
|
||||
@Value("#{ systemProperties['user.region'] }") String defaultLocale) {
|
||||
this.movieFinder = movieFinder;
|
||||
this.defaultLocale = defaultLocale;
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class SimpleMovieLister {
|
||||
|
||||
private lateinit var movieFinder: MovieFinder
|
||||
private lateinit var defaultLocale: String
|
||||
|
||||
@Autowired
|
||||
fun configure(movieFinder: MovieFinder,
|
||||
@Value("#{ systemProperties['user.region'] }") defaultLocale: String) {
|
||||
this.movieFinder = movieFinder
|
||||
this.defaultLocale = defaultLocale
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class MovieRecommender {
|
||||
|
||||
private String defaultLocale;
|
||||
|
||||
private CustomerPreferenceDao customerPreferenceDao;
|
||||
|
||||
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao,
|
||||
@Value("#{systemProperties['user.country']}") String defaultLocale) {
|
||||
this.customerPreferenceDao = customerPreferenceDao;
|
||||
this.defaultLocale = defaultLocale;
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class MovieRecommender(private val customerPreferenceDao: CustomerPreferenceDao,
|
||||
@Value("#{systemProperties['user.country']}") private val defaultLocale: String) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
include-code::./SimpleMovieLister[tag=snippet,indent=0]
|
||||
|
||||
include-code::./MovieRecommender[tag=snippet,indent=0]
|
||||
|
||||
You can also refer to other bean properties by name, as the following example shows:
|
||||
|
||||
include-code::./ShapeGuess[tag=snippet,indent=0]
|
||||
|
||||
+59
-1
@@ -1,7 +1,7 @@
|
||||
[[expressions-operator-safe-navigation]]
|
||||
= Safe Navigation Operator
|
||||
|
||||
The safe navigation operator (`?`) is used to avoid a `NullPointerException` and comes
|
||||
The safe navigation operator (`?.`) is used to avoid a `NullPointerException` and comes
|
||||
from the https://www.groovy-lang.org/operators.html#_safe_navigation_operator[Groovy]
|
||||
language. Typically, when you have a reference to an object, you might need to verify
|
||||
that it is not `null` before accessing methods or properties of the object. To avoid
|
||||
@@ -81,6 +81,64 @@ For example, the expression `#calculator?.max(4, 2)` evaluates to `null` if the
|
||||
`max(int, int)` method will be invoked on the `#calculator`.
|
||||
====
|
||||
|
||||
[[expressions-operator-safe-navigation-indexing]]
|
||||
== Safe Index Access
|
||||
|
||||
Since Spring Framework 6.2, the Spring Expression Language supports safe navigation for
|
||||
indexing into the following types of structures.
|
||||
|
||||
* xref:core/expressions/language-ref/properties-arrays.adoc#expressions-indexing-arrays-and-collections[arrays and collections]
|
||||
* xref:core/expressions/language-ref/properties-arrays.adoc#expressions-indexing-strings[strings]
|
||||
* xref:core/expressions/language-ref/properties-arrays.adoc#expressions-indexing-maps[maps]
|
||||
* xref:core/expressions/language-ref/properties-arrays.adoc#expressions-indexing-objects[objects]
|
||||
|
||||
The following example shows how to use the safe navigation operator for indexing into
|
||||
a list (`?.[]`).
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
IEEE society = new IEEE();
|
||||
EvaluationContext context = new StandardEvaluationContext(society);
|
||||
|
||||
// evaluates to Inventor("Nikola Tesla")
|
||||
Inventor inventor = parser.parseExpression("members?.[0]") // <1>
|
||||
.getValue(context, Inventor.class);
|
||||
|
||||
society.members = null;
|
||||
|
||||
// evaluates to null - does not throw an exception
|
||||
inventor = parser.parseExpression("members?.[0]") // <2>
|
||||
.getValue(context, Inventor.class);
|
||||
----
|
||||
<1> Use null-safe index operator on a non-null `members` list
|
||||
<2> Use null-safe index operator on a null `members` list
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val society = IEEE()
|
||||
val context = StandardEvaluationContext(society)
|
||||
|
||||
// evaluates to Inventor("Nikola Tesla")
|
||||
var inventor = parser.parseExpression("members?.[0]") // <1>
|
||||
.getValue(context, Inventor::class.java)
|
||||
|
||||
society.members = null
|
||||
|
||||
// evaluates to null - does not throw an exception
|
||||
inventor = parser.parseExpression("members?.[0]") // <2>
|
||||
.getValue(context, Inventor::class.java)
|
||||
----
|
||||
<1> Use null-safe index operator on a non-null `members` list
|
||||
<2> Use null-safe index operator on a null `members` list
|
||||
======
|
||||
|
||||
[[expressions-operator-safe-navigation-selection-and-projection]]
|
||||
== Safe Collection Selection and Projection
|
||||
|
||||
+146
-36
@@ -1,11 +1,25 @@
|
||||
[[expressions-properties-arrays]]
|
||||
= Properties, Arrays, Lists, Maps, and Indexers
|
||||
|
||||
Navigating with property references is easy. To do so, use a period to indicate a nested
|
||||
property value. The instances of the `Inventor` class, `pupin` and `tesla`, were
|
||||
populated with data listed in the xref:core/expressions/example-classes.adoc[Classes used in the examples]
|
||||
section. To navigate "down" the object graph and get Tesla's year of birth and
|
||||
Pupin's city of birth, we use the following expressions:
|
||||
The Spring Expression Language provides support for navigating object graphs and indexing
|
||||
into various structures.
|
||||
|
||||
NOTE: Numerical index values are zero-based, such as when accessing the n^th^ element of
|
||||
an array in Java.
|
||||
|
||||
TIP: See the xref:core/expressions/language-ref/operator-safe-navigation.adoc[Safe Navigation Operator]
|
||||
section for details on how to navigate object graphs and index into various structures
|
||||
using the null-safe operator.
|
||||
|
||||
[[expressions-property-navigation]]
|
||||
== Property Navigation
|
||||
|
||||
You can navigate property references within an object graph by using a period to indicate
|
||||
a nested property value. The instances of the `Inventor` class, `pupin` and `tesla`, were
|
||||
populated with data listed in the
|
||||
xref:core/expressions/example-classes.adoc[Classes used in the examples] section. To
|
||||
navigate _down_ the object graph and get Tesla's year of birth and Pupin's city of birth,
|
||||
we use the following expressions:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -16,6 +30,7 @@ Java::
|
||||
// evaluates to 1856
|
||||
int year = (Integer) parser.parseExpression("birthdate.year + 1900").getValue(context);
|
||||
|
||||
// evaluates to "Smiljan"
|
||||
String city = (String) parser.parseExpression("placeOfBirth.city").getValue(context);
|
||||
----
|
||||
|
||||
@@ -26,6 +41,7 @@ Kotlin::
|
||||
// evaluates to 1856
|
||||
val year = parser.parseExpression("birthdate.year + 1900").getValue(context) as Int
|
||||
|
||||
// evaluates to "Smiljan"
|
||||
val city = parser.parseExpression("placeOfBirth.city").getValue(context) as String
|
||||
----
|
||||
======
|
||||
@@ -39,8 +55,20 @@ method invocations -- for example, `getPlaceOfBirth().getCity()` instead of
|
||||
`placeOfBirth.city`.
|
||||
====
|
||||
|
||||
The contents of arrays and lists are obtained by using square bracket notation, as the
|
||||
following example shows:
|
||||
[[expressions-indexing-arrays-and-collections]]
|
||||
== Indexing into Arrays and Collections
|
||||
|
||||
The n^th^ element of an array or collection (for example, a `Set` or `List`) can be
|
||||
obtained by using square bracket notation, as the following example shows.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
If the indexed collection is a `java.util.List`, the n^th^ element will be accessed
|
||||
directly via `list.get(n)`.
|
||||
|
||||
For any other type of `Collection`, the n^th^ element will be accessed by iterating over
|
||||
the collection using its `Iterator` and returning the n^th^ element encountered.
|
||||
====
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -63,7 +91,8 @@ Java::
|
||||
String name = parser.parseExpression("members[0].name").getValue(
|
||||
context, ieee, String.class);
|
||||
|
||||
// List and Array navigation
|
||||
// List and Array Indexing
|
||||
|
||||
// evaluates to "Wireless communication"
|
||||
String invention = parser.parseExpression("members[0].inventions[6]").getValue(
|
||||
context, ieee, String.class);
|
||||
@@ -88,16 +117,22 @@ Kotlin::
|
||||
val name = parser.parseExpression("members[0].name").getValue(
|
||||
context, ieee, String::class.java)
|
||||
|
||||
// List and Array navigation
|
||||
// List and Array Indexing
|
||||
|
||||
// evaluates to "Wireless communication"
|
||||
val invention = parser.parseExpression("members[0].inventions[6]").getValue(
|
||||
context, ieee, String::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
The contents of maps are obtained by specifying the literal key value within the
|
||||
brackets. In the following example, because keys for the `officers` map are strings, we can specify
|
||||
string literals:
|
||||
[[expressions-indexing-strings]]
|
||||
== Indexing into Strings
|
||||
|
||||
The n^th^ character of a string can be obtained by specifying the index within square
|
||||
brackets, as demonstrated in the following example.
|
||||
|
||||
NOTE: The n^th^ character of a string will evaluate to a `java.lang.String`, not a
|
||||
`java.lang.Character`.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -105,38 +140,113 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// Officer's Dictionary
|
||||
|
||||
Inventor pupin = parser.parseExpression("officers['president']").getValue(
|
||||
societyContext, Inventor.class);
|
||||
|
||||
// evaluates to "Idvor"
|
||||
String city = parser.parseExpression("officers['president'].placeOfBirth.city").getValue(
|
||||
societyContext, String.class);
|
||||
|
||||
// setting values
|
||||
parser.parseExpression("officers['advisors'][0].placeOfBirth.country").setValue(
|
||||
societyContext, "Croatia");
|
||||
// evaluates to "T" (8th letter of "Nikola Tesla")
|
||||
String character = parser.parseExpression("members[0].name[7]")
|
||||
.getValue(societyContext, String.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// Officer's Dictionary
|
||||
|
||||
val pupin = parser.parseExpression("officers['president']").getValue(
|
||||
societyContext, Inventor::class.java)
|
||||
|
||||
// evaluates to "Idvor"
|
||||
val city = parser.parseExpression("officers['president'].placeOfBirth.city").getValue(
|
||||
societyContext, String::class.java)
|
||||
|
||||
// setting values
|
||||
parser.parseExpression("officers['advisors'][0].placeOfBirth.country").setValue(
|
||||
societyContext, "Croatia")
|
||||
// evaluates to "T" (8th letter of "Nikola Tesla")
|
||||
val character = parser.parseExpression("members[0].name[7]")
|
||||
.getValue(societyContext, String::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
[[expressions-indexing-maps]]
|
||||
== Indexing into Maps
|
||||
|
||||
The contents of maps are obtained by specifying the key value within square brackets. In
|
||||
the following example, because keys for the `officers` map are strings, we can specify
|
||||
string literals such as `'president'`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// Officer's Map
|
||||
|
||||
// evaluates to Inventor("Pupin")
|
||||
Inventor pupin = parser.parseExpression("officers['president']")
|
||||
.getValue(societyContext, Inventor.class);
|
||||
|
||||
// evaluates to "Idvor"
|
||||
String city = parser.parseExpression("officers['president'].placeOfBirth.city")
|
||||
.getValue(societyContext, String.class);
|
||||
|
||||
String countryExpression = "officers['advisors'][0].placeOfBirth.country";
|
||||
|
||||
// setting values
|
||||
parser.parseExpression(countryExpression)
|
||||
.setValue(societyContext, "Croatia");
|
||||
|
||||
// evaluates to "Croatia"
|
||||
String country = parser.parseExpression(countryExpression)
|
||||
.getValue(societyContext, String.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// Officer's Map
|
||||
|
||||
// evaluates to Inventor("Pupin")
|
||||
val pupin = parser.parseExpression("officers['president']")
|
||||
.getValue(societyContext, Inventor::class.java)
|
||||
|
||||
// evaluates to "Idvor"
|
||||
val city = parser.parseExpression("officers['president'].placeOfBirth.city")
|
||||
.getValue(societyContext, String::class.java)
|
||||
|
||||
val countryExpression = "officers['advisors'][0].placeOfBirth.country"
|
||||
|
||||
// setting values
|
||||
parser.parseExpression(countryExpression)
|
||||
.setValue(societyContext, "Croatia")
|
||||
|
||||
// evaluates to "Croatia"
|
||||
val country = parser.parseExpression(countryExpression)
|
||||
.getValue(societyContext, String::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
[[expressions-indexing-objects]]
|
||||
== Indexing into Objects
|
||||
|
||||
A property of an object can be obtained by specifying the name of the property within
|
||||
square brackets. This is analogous to accessing the value of a map based on its key. The
|
||||
following example demonstrates how to _index_ into an object to retrieve a specific
|
||||
property.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// Create an inventor to use as the root context object.
|
||||
Inventor tesla = new Inventor("Nikola Tesla");
|
||||
|
||||
// evaluates to "Nikola Tesla"
|
||||
String name = parser.parseExpression("#root['name']")
|
||||
.getValue(context, tesla, String.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// Create an inventor to use as the root context object.
|
||||
val tesla = Inventor("Nikola Tesla")
|
||||
|
||||
// evaluates to "Nikola Tesla"
|
||||
val name = parser.parseExpression("#root['name']")
|
||||
.getValue(context, tesla, String::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
+2
-99
@@ -11,106 +11,9 @@ formatters manually with the help of:
|
||||
* `org.springframework.format.datetime.standard.DateTimeFormatterRegistrar`
|
||||
* `org.springframework.format.datetime.DateFormatterRegistrar`
|
||||
|
||||
For example, the following Java configuration registers a global `yyyyMMdd` format:
|
||||
For example, the following configuration registers a global `yyyyMMdd` format:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
|
||||
@Bean
|
||||
public FormattingConversionService conversionService() {
|
||||
|
||||
// Use the DefaultFormattingConversionService but do not register defaults
|
||||
DefaultFormattingConversionService conversionService =
|
||||
new DefaultFormattingConversionService(false);
|
||||
|
||||
// Ensure @NumberFormat is still supported
|
||||
conversionService.addFormatterForFieldAnnotation(
|
||||
new NumberFormatAnnotationFormatterFactory());
|
||||
|
||||
// Register JSR-310 date conversion with a specific global format
|
||||
DateTimeFormatterRegistrar dateTimeRegistrar = new DateTimeFormatterRegistrar();
|
||||
dateTimeRegistrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"));
|
||||
dateTimeRegistrar.registerFormatters(conversionService);
|
||||
|
||||
// Register date conversion with a specific global format
|
||||
DateFormatterRegistrar dateRegistrar = new DateFormatterRegistrar();
|
||||
dateRegistrar.setFormatter(new DateFormatter("yyyyMMdd"));
|
||||
dateRegistrar.registerFormatters(conversionService);
|
||||
|
||||
return conversionService;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class AppConfig {
|
||||
|
||||
@Bean
|
||||
fun conversionService(): FormattingConversionService {
|
||||
// Use the DefaultFormattingConversionService but do not register defaults
|
||||
return DefaultFormattingConversionService(false).apply {
|
||||
|
||||
// Ensure @NumberFormat is still supported
|
||||
addFormatterForFieldAnnotation(NumberFormatAnnotationFormatterFactory())
|
||||
|
||||
// Register JSR-310 date conversion with a specific global format
|
||||
val dateTimeRegistrar = DateTimeFormatterRegistrar()
|
||||
dateTimeRegistrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"))
|
||||
dateTimeRegistrar.registerFormatters(this)
|
||||
|
||||
// Register date conversion with a specific global format
|
||||
val dateRegistrar = DateFormatterRegistrar()
|
||||
dateRegistrar.setFormatter(DateFormatter("yyyyMMdd"))
|
||||
dateRegistrar.registerFormatters(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
If you prefer XML-based configuration, you can use a
|
||||
`FormattingConversionServiceFactoryBean`. The following example shows how to do so:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
|
||||
<property name="registerDefaultFormatters" value="false" />
|
||||
<property name="formatters">
|
||||
<set>
|
||||
<bean class="org.springframework.format.number.NumberFormatAnnotationFormatterFactory" />
|
||||
</set>
|
||||
</property>
|
||||
<property name="formatterRegistrars">
|
||||
<set>
|
||||
<bean class="org.springframework.format.datetime.standard.DateTimeFormatterRegistrar">
|
||||
<property name="dateFormatter">
|
||||
<bean class="org.springframework.format.datetime.standard.DateTimeFormatterFactoryBean">
|
||||
<property name="pattern" value="yyyyMMdd"/>
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
</set>
|
||||
</property>
|
||||
</bean>
|
||||
</beans>
|
||||
----
|
||||
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
|
||||
|
||||
Note there are extra considerations when configuring date and time formats in web
|
||||
applications. Please see
|
||||
|
||||
@@ -46,47 +46,9 @@ To configure a `DriverManagerDataSource`:
|
||||
for the correct value.)
|
||||
. Provide a username and a password to connect to the database.
|
||||
|
||||
The following example shows how to configure a `DriverManagerDataSource` in Java:
|
||||
The following example shows how to configure a `DriverManagerDataSource`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
DriverManagerDataSource dataSource = new DriverManagerDataSource();
|
||||
dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
|
||||
dataSource.setUrl("jdbc:hsqldb:hsql://localhost:");
|
||||
dataSource.setUsername("sa");
|
||||
dataSource.setPassword("");
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val dataSource = DriverManagerDataSource().apply {
|
||||
setDriverClassName("org.hsqldb.jdbcDriver")
|
||||
url = "jdbc:hsqldb:hsql://localhost:"
|
||||
username = "sa"
|
||||
password = ""
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows the corresponding XML configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
|
||||
<property name="driverClassName" value="${jdbc.driverClassName}"/>
|
||||
<property name="url" value="${jdbc.url}"/>
|
||||
<property name="username" value="${jdbc.username}"/>
|
||||
<property name="password" value="${jdbc.password}"/>
|
||||
</bean>
|
||||
|
||||
<context:property-placeholder location="jdbc.properties"/>
|
||||
----
|
||||
include-code::./DriverManagerDataSourceConfiguration[tag=snippet,indent=0]
|
||||
|
||||
The next two examples show the basic connectivity and configuration for DBCP and C3P0.
|
||||
To learn about more options that help control the pooling features, see the product
|
||||
@@ -94,32 +56,11 @@ documentation for the respective connection pooling implementations.
|
||||
|
||||
The following example shows DBCP configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
|
||||
<property name="driverClassName" value="${jdbc.driverClassName}"/>
|
||||
<property name="url" value="${jdbc.url}"/>
|
||||
<property name="username" value="${jdbc.username}"/>
|
||||
<property name="password" value="${jdbc.password}"/>
|
||||
</bean>
|
||||
|
||||
<context:property-placeholder location="jdbc.properties"/>
|
||||
----
|
||||
include-code::./BasicDataSourceConfiguration[tag=snippet,indent=0]
|
||||
|
||||
The following example shows C3P0 configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
|
||||
<property name="driverClass" value="${jdbc.driverClassName}"/>
|
||||
<property name="jdbcUrl" value="${jdbc.url}"/>
|
||||
<property name="user" value="${jdbc.username}"/>
|
||||
<property name="password" value="${jdbc.password}"/>
|
||||
</bean>
|
||||
|
||||
<context:property-placeholder location="jdbc.properties"/>
|
||||
----
|
||||
|
||||
include-code::./ComboPooledDataSourceConfiguration[tag=snippet,indent=0]
|
||||
|
||||
[[jdbc-DataSourceUtils]]
|
||||
== Using `DataSourceUtils`
|
||||
|
||||
@@ -387,112 +387,19 @@ Kotlin::
|
||||
======
|
||||
--
|
||||
|
||||
The following example shows the corresponding XML configuration:
|
||||
The following example shows the corresponding configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
https://www.springframework.org/schema/context/spring-context.xsd">
|
||||
|
||||
<bean id="corporateEventDao" class="com.example.JdbcCorporateEventDao">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
</bean>
|
||||
|
||||
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
|
||||
<property name="driverClassName" value="${jdbc.driverClassName}"/>
|
||||
<property name="url" value="${jdbc.url}"/>
|
||||
<property name="username" value="${jdbc.username}"/>
|
||||
<property name="password" value="${jdbc.password}"/>
|
||||
</bean>
|
||||
|
||||
<context:property-placeholder location="jdbc.properties"/>
|
||||
|
||||
</beans>
|
||||
----
|
||||
include-code::./JdbcCorporateEventDaoConfiguration[tag=snippet,indent=0]
|
||||
|
||||
An alternative to explicit configuration is to use component-scanning and annotation
|
||||
support for dependency injection. In this case, you can annotate the class with `@Repository`
|
||||
(which makes it a candidate for component-scanning) and annotate the `DataSource` setter
|
||||
method with `@Autowired`. The following example shows how to do so:
|
||||
(which makes it a candidate for component-scanning). The following example shows how to do so:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Repository // <1>
|
||||
public class JdbcCorporateEventDao implements CorporateEventDao {
|
||||
include-code::./JdbcCorporateEventRepository[tag=snippet,indent=0]
|
||||
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
The following example shows the corresponding configuration:
|
||||
|
||||
@Autowired // <2>
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.jdbcTemplate = new JdbcTemplate(dataSource); // <3>
|
||||
}
|
||||
|
||||
// JDBC-backed implementations of the methods on the CorporateEventDao follow...
|
||||
}
|
||||
----
|
||||
<1> Annotate the class with `@Repository`.
|
||||
<2> Annotate the `DataSource` setter method with `@Autowired`.
|
||||
<3> Create a new `JdbcTemplate` with the `DataSource`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Repository // <1>
|
||||
class JdbcCorporateEventDao(dataSource: DataSource) : CorporateEventDao { // <2>
|
||||
|
||||
private val jdbcTemplate = JdbcTemplate(dataSource) // <3>
|
||||
|
||||
// JDBC-backed implementations of the methods on the CorporateEventDao follow...
|
||||
}
|
||||
----
|
||||
<1> Annotate the class with `@Repository`.
|
||||
<2> Constructor injection of the `DataSource`.
|
||||
<3> Create a new `JdbcTemplate` with the `DataSource`.
|
||||
======
|
||||
--
|
||||
|
||||
|
||||
The following example shows the corresponding XML configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
https://www.springframework.org/schema/context/spring-context.xsd">
|
||||
|
||||
<!-- Scans within the base package of the application for @Component classes to configure as beans -->
|
||||
<context:component-scan base-package="org.springframework.docs.test" />
|
||||
|
||||
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
|
||||
<property name="driverClassName" value="${jdbc.driverClassName}"/>
|
||||
<property name="url" value="${jdbc.url}"/>
|
||||
<property name="username" value="${jdbc.username}"/>
|
||||
<property name="password" value="${jdbc.password}"/>
|
||||
</bean>
|
||||
|
||||
<context:property-placeholder location="jdbc.properties"/>
|
||||
|
||||
</beans>
|
||||
----
|
||||
include-code::./JdbcCorporateEventRepositoryConfiguration[tag=snippet,indent=0]
|
||||
|
||||
If you use Spring's `JdbcDaoSupport` class and your various JDBC-backed DAO classes
|
||||
extend from it, your sub-class inherits a `setDataSource(..)` method from the
|
||||
|
||||
+75
-109
@@ -16,124 +16,22 @@ lightweight nature. Benefits include ease of configuration, quick startup time,
|
||||
testability, and the ability to rapidly evolve your SQL during development.
|
||||
|
||||
|
||||
[[jdbc-embedded-database-xml]]
|
||||
== Creating an Embedded Database by Using Spring XML
|
||||
[[jdbc-embedded-database]]
|
||||
== Creating an Embedded Database
|
||||
|
||||
If you want to expose an embedded database instance as a bean in a Spring
|
||||
`ApplicationContext`, you can use the `embedded-database` tag in the `spring-jdbc` namespace:
|
||||
You can expose an embedded database instance as a bean as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<jdbc:embedded-database id="dataSource" generate-name="true">
|
||||
<jdbc:script location="classpath:schema.sql"/>
|
||||
<jdbc:script location="classpath:test-data.sql"/>
|
||||
</jdbc:embedded-database>
|
||||
----
|
||||
include-code::./JdbcEmbeddedDatabaseConfiguration[tag=snippet,indent=0]
|
||||
|
||||
The preceding configuration creates an embedded HSQL database that is populated with SQL from
|
||||
The preceding configuration creates an embedded H2 database that is populated with SQL from
|
||||
the `schema.sql` and `test-data.sql` resources in the root of the classpath. In addition, as
|
||||
a best practice, the embedded database is assigned a uniquely generated name. The
|
||||
embedded database is made available to the Spring container as a bean of type
|
||||
`javax.sql.DataSource` that can then be injected into data access objects as needed.
|
||||
|
||||
|
||||
[[jdbc-embedded-database-java]]
|
||||
== Creating an Embedded Database Programmatically
|
||||
|
||||
The `EmbeddedDatabaseBuilder` class provides a fluent API for constructing an embedded
|
||||
database programmatically. You can use this when you need to create an embedded database in a
|
||||
stand-alone environment or in a stand-alone integration test, as in the following example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
EmbeddedDatabase db = new EmbeddedDatabaseBuilder()
|
||||
.generateUniqueName(true)
|
||||
.setType(H2)
|
||||
.setScriptEncoding("UTF-8")
|
||||
.ignoreFailedDrops(true)
|
||||
.addScript("schema.sql")
|
||||
.addScripts("user_data.sql", "country_data.sql")
|
||||
.build();
|
||||
|
||||
// perform actions against the db (EmbeddedDatabase extends javax.sql.DataSource)
|
||||
|
||||
db.shutdown()
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val db = EmbeddedDatabaseBuilder()
|
||||
.generateUniqueName(true)
|
||||
.setType(H2)
|
||||
.setScriptEncoding("UTF-8")
|
||||
.ignoreFailedDrops(true)
|
||||
.addScript("schema.sql")
|
||||
.addScripts("user_data.sql", "country_data.sql")
|
||||
.build()
|
||||
|
||||
// perform actions against the db (EmbeddedDatabase extends javax.sql.DataSource)
|
||||
|
||||
db.shutdown()
|
||||
----
|
||||
======
|
||||
|
||||
See the {spring-framework-api}/jdbc/datasource/embedded/EmbeddedDatabaseBuilder.html[javadoc for `EmbeddedDatabaseBuilder`]
|
||||
for further details on all supported options.
|
||||
|
||||
You can also use the `EmbeddedDatabaseBuilder` to create an embedded database by using Java
|
||||
configuration, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class DataSourceConfig {
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
return new EmbeddedDatabaseBuilder()
|
||||
.generateUniqueName(true)
|
||||
.setType(H2)
|
||||
.setScriptEncoding("UTF-8")
|
||||
.ignoreFailedDrops(true)
|
||||
.addScript("schema.sql")
|
||||
.addScripts("user_data.sql", "country_data.sql")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class DataSourceConfig {
|
||||
|
||||
@Bean
|
||||
fun dataSource(): DataSource {
|
||||
return EmbeddedDatabaseBuilder()
|
||||
.generateUniqueName(true)
|
||||
.setType(H2)
|
||||
.setScriptEncoding("UTF-8")
|
||||
.ignoreFailedDrops(true)
|
||||
.addScript("schema.sql")
|
||||
.addScripts("user_data.sql", "country_data.sql")
|
||||
.build()
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[jdbc-embedded-database-types]]
|
||||
== Selecting the Embedded Database Type
|
||||
@@ -168,6 +66,74 @@ attribute of the `embedded-database` tag to `DERBY`. If you use the builder API,
|
||||
call the `setType(EmbeddedDatabaseType)` method with `EmbeddedDatabaseType.DERBY`.
|
||||
|
||||
|
||||
[[jdbc-embedded-database-types-custom]]
|
||||
== Customizing the Embedded Database Type
|
||||
|
||||
While each supported type comes with default connection settings, it is possible
|
||||
to customize them if necessary. The following example uses H2 with a custom driver:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class DataSourceConfig {
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
return new EmbeddedDatabaseBuilder()
|
||||
.setDatabaseConfigurer(EmbeddedDatabaseConfigurers
|
||||
.customizeConfigurer(H2, this::customize))
|
||||
.addScript("schema.sql")
|
||||
.build();
|
||||
}
|
||||
|
||||
private EmbeddedDatabaseConfigurer customize(EmbeddedDatabaseConfigurer defaultConfigurer) {
|
||||
return new EmbeddedDatabaseConfigurerDelegate(defaultConfigurer) {
|
||||
@Override
|
||||
public void configureConnectionProperties(ConnectionProperties properties, String databaseName) {
|
||||
super.configureConnectionProperties(properties, databaseName);
|
||||
properties.setDriverClass(CustomDriver.class);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class DataSourceConfig {
|
||||
|
||||
@Bean
|
||||
fun dataSource(): DataSource {
|
||||
return EmbeddedDatabaseBuilder()
|
||||
.setDatabaseConfigurer(EmbeddedDatabaseConfigurers
|
||||
.customizeConfigurer(EmbeddedDatabaseType.H2) { this.customize(it) })
|
||||
.addScript("schema.sql")
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun customize(defaultConfigurer: EmbeddedDatabaseConfigurer): EmbeddedDatabaseConfigurer {
|
||||
return object : EmbeddedDatabaseConfigurerDelegate(defaultConfigurer) {
|
||||
override fun configureConnectionProperties(
|
||||
properties: ConnectionProperties,
|
||||
databaseName: String
|
||||
) {
|
||||
super.configureConnectionProperties(properties, databaseName)
|
||||
properties.setDriverClass(CustomDriver::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[jdbc-embedded-database-dao-testing]]
|
||||
== Testing Data Access Logic with an Embedded Database
|
||||
|
||||
@@ -177,8 +143,8 @@ can be useful for one-offs when the embedded database does not need to be reused
|
||||
classes. However, if you wish to create an embedded database that is shared within a test suite,
|
||||
consider using the xref:testing/testcontext-framework.adoc[Spring TestContext Framework] and
|
||||
configuring the embedded database as a bean in the Spring `ApplicationContext` as described
|
||||
in xref:data-access/jdbc/embedded-database-support.adoc#jdbc-embedded-database-xml[Creating an Embedded Database by Using Spring XML] and xref:data-access/jdbc/embedded-database-support.adoc#jdbc-embedded-database-java[Creating an Embedded Database Programmatically]. The following listing
|
||||
shows the test template:
|
||||
in xref:data-access/jdbc/embedded-database-support.adoc#jdbc-embedded-database[Creating an Embedded Database].
|
||||
The following listing shows the test template:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
||||
@@ -272,6 +272,13 @@ is being accessed by other components (for example, calling `createEntityManager
|
||||
calls block until the background bootstrapping has completed. In particular, when you use
|
||||
Spring Data JPA, make sure to set up deferred bootstrapping for its repositories as well.
|
||||
|
||||
As of 6.2, JPA initialization is enforced before context refresh completion, waiting for
|
||||
asynchronous bootstrapping to complete by then. This makes the availability of the fully
|
||||
initialized database infrastructure predictable and allows for custom post-initialization
|
||||
logic in `ContextRefreshedEvent` listeners etc. Putting such application-level database
|
||||
initialization into `@PostConstruct` methods or the like is not recommended; this is
|
||||
better placed in `Lifecycle.start` (if applicable) or a `ContextRefreshedEvent` listener.
|
||||
|
||||
|
||||
[[orm-jpa-dao]]
|
||||
== Implementing DAOs Based on JPA: `EntityManagerFactory` and `EntityManager`
|
||||
|
||||
+48
-8
@@ -436,16 +436,36 @@ properties of the `@Transactional` annotation:
|
||||
| Optional array of exception name patterns that must not cause rollback.
|
||||
|===
|
||||
|
||||
TIP: See xref:data-access/transaction/declarative/rolling-back.adoc#transaction-declarative-rollback-rules[Rollback rules] for further details
|
||||
on rollback rule semantics, patterns, and warnings regarding possible unintentional
|
||||
matches for pattern-based rollback rules.
|
||||
TIP: See xref:data-access/transaction/declarative/rolling-back.adoc#transaction-declarative-rollback-rules[Rollback rules]
|
||||
for further details on rollback rule semantics, patterns, and warnings
|
||||
regarding possible unintentional matches for pattern-based rollback rules.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
As of 6.2, you can globally change the default rollback behavior – for example, through
|
||||
`@EnableTransactionManagement(rollbackOn=ALL_EXCEPTIONS)`, leading to a rollback
|
||||
for all exceptions raised within a transaction, including any checked exception.
|
||||
For further customizations, `AnnotationTransactionAttributeSource` provides an
|
||||
`addDefaultRollbackRule(RollbackRuleAttribute)` method for custom default rules.
|
||||
|
||||
Note that transaction-specific rollback rules override the default behavior but
|
||||
retain the chosen default for unspecified exceptions. This is the case for
|
||||
Spring's `@Transactional` as well as JTA's `jakarta.transaction.Transactional`
|
||||
annotation.
|
||||
|
||||
Unless you rely on EJB-style business exceptions with commit behavior, it is
|
||||
advisable to switch to `ALL_EXCEPTIONS` for consistent rollback semantics even
|
||||
in case of a (potentially accidental) checked exception. Also, it is advisable
|
||||
to make that switch for Kotlin-based applications where there is no enforcement
|
||||
of checked exceptions at all.
|
||||
====
|
||||
|
||||
Currently, you cannot have explicit control over the name of a transaction, where 'name'
|
||||
means the transaction name that appears in a transaction monitor and in logging output.
|
||||
For declarative transactions, the transaction name is always the fully-qualified class
|
||||
name + `.` + the method name of the transactionally advised class. For example, if the
|
||||
name of the transactionally advised class + `.` + the method name. For example, if the
|
||||
`handlePayment(..)` method of the `BusinessService` class started a transaction, the
|
||||
name of the transaction would be: `com.example.BusinessService.handlePayment`.
|
||||
name of the transaction would be `com.example.BusinessService.handlePayment`.
|
||||
|
||||
[[tx-multiple-tx-mgrs-with-attransactional]]
|
||||
== Multiple Transaction Managers with `@Transactional`
|
||||
@@ -529,12 +549,32 @@ transaction managers, differentiated by the `order`, `account`, and `reactive-ac
|
||||
qualifiers. The default `<tx:annotation-driven>` target bean name, `transactionManager`,
|
||||
is still used if no specifically qualified `TransactionManager` bean is found.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
If all transactional methods on the same class share the same qualifier, consider
|
||||
declaring a type-level `org.springframework.beans.factory.annotation.Qualifier`
|
||||
annotation instead. If its value matches the qualifier value (or bean name) of a
|
||||
specific transaction manager, that transaction manager is going to be used for
|
||||
transaction definitions without a specific qualifier on `@Transactional` itself.
|
||||
|
||||
Such a type-level qualifier can be declared on the concrete class, applying to
|
||||
transaction definitions from a base class as well. This effectively overrides
|
||||
the default transaction manager choice for any unqualified base class methods.
|
||||
|
||||
Last but not least, such a type-level bean qualifier can serve multiple purposes,
|
||||
e.g. with a value of "order" it can be used for autowiring purposes (identifying
|
||||
the order repository) as well as transaction manager selection, as long as the
|
||||
target beans for autowiring as well as the associated transaction manager
|
||||
definitions declare the same qualifier value. Such a qualifier value only needs
|
||||
to be unique within a set of type-matching beans, not having to serve as an ID.
|
||||
====
|
||||
|
||||
[[tx-custom-attributes]]
|
||||
== Custom Composed Annotations
|
||||
|
||||
If you find you repeatedly use the same attributes with `@Transactional` on many different
|
||||
methods, xref:core/beans/classpath-scanning.adoc#beans-meta-annotations[Spring's meta-annotation support] lets you
|
||||
define custom composed annotations for your specific use cases. For example, consider the
|
||||
If you find you repeatedly use the same attributes with `@Transactional` on many different methods,
|
||||
xref:core/beans/classpath-scanning.adoc#beans-meta-annotations[Spring's meta-annotation support]
|
||||
lets you define custom composed annotations for your specific use cases. For example, consider the
|
||||
following annotation definitions:
|
||||
|
||||
[tabs]
|
||||
|
||||
@@ -28,4 +28,6 @@ Spring's testing annotations include the following:
|
||||
* xref:testing/annotations/integration-spring/annotation-sqlmergemode.adoc[`@SqlMergeMode`]
|
||||
* xref:testing/annotations/integration-spring/annotation-sqlgroup.adoc[`@SqlGroup`]
|
||||
* xref:testing/annotations/integration-spring/annotation-disabledinaotmode.adoc[`@DisabledInAotMode`]
|
||||
* xref:testing/annotations/integration-spring/annotation-beanoverriding.adoc#spring-testing-annotation-beanoverriding-testbean[`@TestBean`]
|
||||
* xref:testing/annotations/integration-spring/annotation-beanoverriding.adoc#spring-testing-annotation-beanoverriding-mockitobean[`@MockitoBean` and `@MockitoSpyBean`]
|
||||
|
||||
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
[[spring-testing-annotation-beanoverriding]]
|
||||
= Bean Overriding in Tests
|
||||
|
||||
Bean Overriding in Tests refers to the ability to override specific beans in the Context
|
||||
for a test class, by annotating one or more fields in said test class.
|
||||
|
||||
NOTE: This is intended as a less risky alternative to the practice of registering a bean via
|
||||
`@Bean` with the `DefaultListableBeanFactory` `setAllowBeanDefinitionOverriding` set to
|
||||
`true`.
|
||||
|
||||
The Spring Testing Framework provides two sets of annotations presented below. One relies
|
||||
purely on Spring, while the second set relies on the Mockito third party library.
|
||||
|
||||
[[spring-testing-annotation-beanoverriding-testbean]]
|
||||
== `@TestBean`
|
||||
|
||||
`@TestBean` is used on a test class field to override a specific bean with an instance
|
||||
provided by a conventionally named static method.
|
||||
|
||||
By default, the bean name and the associated static method name are derived from the
|
||||
annotated field's name, but the annotation allows for specific values to be provided.
|
||||
|
||||
The `@TestBean` annotation uses the `REPLACE_DEFINITION`
|
||||
xref:#spring-testing-annotation-beanoverriding-extending[strategy for test bean overriding].
|
||||
|
||||
The following example shows how to fully configure the `@TestBean` annotation, with
|
||||
explicit values equivalent to the default:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
class OverrideBeanTests {
|
||||
@TestBean(name = "service", methodName = "serviceTestOverride") // <1>
|
||||
private CustomService service;
|
||||
|
||||
// test case body...
|
||||
|
||||
private static CustomService serviceTestOverride() { // <2>
|
||||
return new MyFakeCustomService();
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Mark a field for bean overriding in this test class
|
||||
<2> The result of this static method will be used as the instance and injected into the field
|
||||
======
|
||||
|
||||
NOTE: The method to invoke is searched in the test class and any enclosing class it might
|
||||
have, as well as its hierarchy. This typically allows nested test class to rely on the
|
||||
method to use in the root test class.
|
||||
|
||||
[[spring-testing-annotation-beanoverriding-mockitobean]]
|
||||
== `@MockitoBean` and `@MockitoSpyBean`
|
||||
|
||||
`@MockitoBean` and `@MockitoSpyBean` are used on a test class field to override a bean
|
||||
with a mocking and spying instance, respectively. In the later case, the original bean
|
||||
definition is not replaced but instead an early instance is captured and wrapped by the
|
||||
spy.
|
||||
|
||||
By default, the name of the bean to override is derived from the annotated field's name,
|
||||
but both annotations allows for a specific `name` to be provided. Each annotation also
|
||||
defines Mockito-specific attributes to fine-tune the mocking details.
|
||||
|
||||
The `@MockitoBean` annotation uses the `CREATE_OR_REPLACE_DEFINITION`
|
||||
xref:#spring-testing-annotation-beanoverriding-extending[strategy for test bean overriding].
|
||||
|
||||
The `@MockitoSpyBean` annotation uses the `WRAP_EARLY_BEAN`
|
||||
xref:#spring-testing-annotation-beanoverriding-extending[strategy] and the original instance
|
||||
is wrapped in a Mockito spy.
|
||||
|
||||
The following example shows how to configure the bean name for both `@MockitoBean` and
|
||||
`@MockitoSpyBean` annotations:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
class OverrideBeanTests {
|
||||
@MockitoBean(name = "service1") // <1>
|
||||
private CustomService mockService;
|
||||
|
||||
@MockitoSpyBean(name = "service2") // <2>
|
||||
private CustomService spyService; // <3>
|
||||
|
||||
// test case body...
|
||||
}
|
||||
----
|
||||
<1> Mark `mockService` as a Mockito mock override of bean `service1` in this test class
|
||||
<2> Mark `spyService` as a Mockito spy override of bean `service2` in this test class
|
||||
<3> Both fields will be injected with the Mockito values (the mock and the spy respectively)
|
||||
======
|
||||
|
||||
|
||||
[[spring-testing-annotation-beanoverriding-extending]]
|
||||
== Extending bean override with a custom annotation
|
||||
|
||||
The three annotations introduced above build upon the `@BeanOverride` meta-annotation
|
||||
and associated infrastructure, which allows to define custom bean overriding variants.
|
||||
|
||||
To create an extension, the following is needed:
|
||||
|
||||
- An annotation meta-annotated with `@BeanOverride` that defines the
|
||||
`BeanOverrideProcessor` to use.
|
||||
- The `BeanOverrideProcessor` implementation itself.
|
||||
- One or more concrete `OverrideMetadata` implementations provided by the processor.
|
||||
|
||||
The Spring TestContext Framework includes infrastructure classes that support bean
|
||||
overriding: a `BeanFactoryPostProcessor`, a `TestExecutionListener` and a
|
||||
`ContextCustomizerFactory`.
|
||||
The later two are automatically registered via the Spring TestContext Framework
|
||||
`spring.factories` file, and are responsible for setting up the rest of the infrastructure.
|
||||
|
||||
The test classes are parsed looking for any field meta-annotated with `@BeanOverride`,
|
||||
instantiating the relevant `BeanOverrideProcessor` in order to register an
|
||||
`OverrideMetadata`.
|
||||
|
||||
Then the `BeanOverrideBeanFactoryPostProcessor` will use that information to alter the
|
||||
context, registering and replacing bean definitions as defined by each metadata
|
||||
`BeanOverrideStrategy`:
|
||||
|
||||
- `REPLACE_DEFINITION`: replaces the bean definition. If it is not present in the
|
||||
context, an exception is thrown.
|
||||
- `CREATE_OR_REPLACE_DEFINITION`: replaces the bean definition if the bean definition
|
||||
does not exist, or create one if it is not.
|
||||
- `WRAP_BEAN`: get the original instance early so that it can be wrapped.
|
||||
|
||||
NOTE: The Bean Overriding infrastructure does not include any bean resolution step,
|
||||
unlike an `@Autowired`-annotated field for instance. As such, the name of the bean to
|
||||
override must be somehow provided to or computed by the `BeanOverrideProcessor`.
|
||||
Typically, the user provides the name one way or the other.
|
||||
+1
-2
@@ -8,8 +8,7 @@ to use the raw HtmlUnit libraries.
|
||||
== MockMvc and HtmlUnit Setup
|
||||
|
||||
First, make sure that you have included a test dependency on
|
||||
`net.sourceforge.htmlunit:htmlunit`. In order to use HtmlUnit with Apache HttpComponents
|
||||
4.5+, you need to use HtmlUnit 2.18 or higher.
|
||||
`org.htmlunit:htmlunit`.
|
||||
|
||||
We can easily create an HtmlUnit `WebClient` that integrates with MockMvc by using the
|
||||
`MockMvcWebClientBuilder`, as follows:
|
||||
|
||||
+1
-1
@@ -166,7 +166,7 @@ following sections to make this pattern much easier to implement.
|
||||
== MockMvc and WebDriver Setup
|
||||
|
||||
To use Selenium WebDriver with the Spring MVC Test framework, make sure that your project
|
||||
includes a test dependency on `org.seleniumhq.selenium:selenium-htmlunit-driver`.
|
||||
includes a test dependency on `org.seleniumhq.selenium:selenium-htmlunit3-driver`.
|
||||
|
||||
We can easily create a Selenium WebDriver that integrates with MockMvc by using the
|
||||
`MockMvcHtmlUnitDriverBuilder` as the following example shows:
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@ Kotlin::
|
||||
|
||||
This test has some obvious drawbacks. If we update our controller to use the parameter
|
||||
`message` instead of `text`, our form test continues to pass, even though the HTML form
|
||||
is out of synch with the controller. To resolve this we can combine our two tests, as
|
||||
is out of sync with the controller. To resolve this we can combine our two tests, as
|
||||
follows:
|
||||
|
||||
[tabs]
|
||||
|
||||
@@ -16,6 +16,8 @@ by default, exactly in the following order:
|
||||
Micrometer's `ObservationRegistry`.
|
||||
* `DirtiesContextTestExecutionListener`: Handles the `@DirtiesContext` annotation for
|
||||
"`after`" modes.
|
||||
* `CommonCachesTestExecutionListener`: Clears resource caches in the test's
|
||||
`ApplicationContext` if necessary.
|
||||
* `TransactionalTestExecutionListener`: Provides transactional test execution with
|
||||
default rollback semantics.
|
||||
* `SqlScriptsTestExecutionListener`: Runs SQL scripts configured by using the `@Sql`
|
||||
|
||||
@@ -803,7 +803,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ClassPathResource index = new ClassPathResource("static/index.html");
|
||||
List<String> extensions = Arrays.asList("js", "css", "ico", "png", "jpg", "gif");
|
||||
List<String> extensions = List.of("js", "css", "ico", "png", "jpg", "gif");
|
||||
RequestPredicate spaPredicate = path("/api/**").or(path("/error")).or(pathExtension(extensions::contains)).negate();
|
||||
RouterFunction<ServerResponse> redirectToIndex = route()
|
||||
.resource(spaPredicate, index)
|
||||
|
||||
@@ -44,6 +44,10 @@ has an `@ExceptionHandler` method that handles any `ErrorResponse` exception, wh
|
||||
includes all built-in web exceptions. You can add more exception handling methods, and
|
||||
use a protected method to map any exception to a `ProblemDetail`.
|
||||
|
||||
You can register `ErrorResponse` interceptors through the
|
||||
xref:web/webflux/config.adoc[WebFlux Config] with a `WebFluxConfigurer`. Use that to intercept
|
||||
any RFC 7807 response and take some action.
|
||||
|
||||
|
||||
|
||||
[[webflux-ann-rest-exceptions-non-standard]]
|
||||
|
||||
@@ -44,6 +44,11 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: When using Spring Boot, you may want to use `@Configuration` classes of type `WebFluxConfigurer` but without
|
||||
`@EnableWebFlux` to keep Spring Boot WebFlux customizations. See more details in
|
||||
xref:#webflux-config-customize[the WebFlux config API section] and in
|
||||
{spring-boot-docs}/web.html#web.reactive.webflux.auto-configuration[the dedicated Spring Boot documentation].
|
||||
|
||||
The preceding example registers a number of Spring WebFlux
|
||||
xref:web/webflux/dispatcher-handler.adoc#webflux-special-bean-types[infrastructure beans] and adapts to dependencies
|
||||
available on the classpath -- for JSON, XML, and others.
|
||||
@@ -64,7 +69,6 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
// Implement configuration methods...
|
||||
@@ -76,7 +80,6 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
// Implement configuration methods...
|
||||
@@ -102,7 +105,6 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
@@ -118,7 +120,6 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
override fun addFormatters(registry: FormatterRegistry) {
|
||||
@@ -140,7 +141,6 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
@@ -157,7 +157,6 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
override fun addFormatters(registry: FormatterRegistry) {
|
||||
@@ -194,7 +193,6 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
@@ -210,7 +208,6 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
override fun getValidator(): Validator {
|
||||
@@ -279,7 +276,6 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
@@ -294,7 +290,6 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
override fun configureContentTypeResolver(builder: RequestedContentTypeResolverBuilder) {
|
||||
@@ -319,7 +314,6 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
@@ -334,7 +328,6 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) {
|
||||
@@ -376,7 +369,6 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
@@ -391,7 +383,6 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
override fun configureViewResolvers(registry: ViewResolverRegistry) {
|
||||
@@ -412,7 +403,6 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
|
||||
@@ -437,7 +427,6 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
override fun configureViewResolvers(registry: ViewResolverRegistry) {
|
||||
@@ -463,7 +452,6 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
|
||||
@@ -480,7 +468,6 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
override fun configureViewResolvers(registry: ViewResolverRegistry) {
|
||||
@@ -503,7 +490,6 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
|
||||
@@ -524,7 +510,6 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
|
||||
@@ -565,7 +550,6 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
@@ -583,7 +567,6 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
override fun addResourceHandlers(registry: ResourceHandlerRegistry) {
|
||||
@@ -616,7 +599,6 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
@@ -635,7 +617,6 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
override fun addResourceHandlers(registry: ResourceHandlerRegistry) {
|
||||
@@ -695,7 +676,6 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
@@ -711,7 +691,6 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
@@ -756,7 +735,6 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
@@ -772,7 +750,6 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
@@ -810,7 +787,6 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
@@ -827,7 +803,6 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -20,7 +20,7 @@ Java::
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan("org.example.web") // <1>
|
||||
public class WebConfig {
|
||||
public class WebConfiguration {
|
||||
|
||||
// ...
|
||||
}
|
||||
@@ -33,7 +33,7 @@ Kotlin::
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan("org.example.web") // <1>
|
||||
class WebConfig {
|
||||
class WebConfiguration {
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
@@ -781,7 +781,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ClassPathResource index = new ClassPathResource("static/index.html");
|
||||
List<String> extensions = Arrays.asList("js", "css", "ico", "png", "jpg", "gif");
|
||||
List<String> extensions = List.of("js", "css", "ico", "png", "jpg", "gif");
|
||||
RequestPredicate spaPredicate = path("/api/**").or(path("/error")).or(pathExtension(extensions::contains)).negate();
|
||||
RouterFunction<ServerResponse> redirectToIndex = route()
|
||||
.resource(spaPredicate, index)
|
||||
|
||||
@@ -445,6 +445,13 @@ directly. For example:
|
||||
}
|
||||
----
|
||||
|
||||
The following `ThreadLocalAccessor` implementations are provided out of the box:
|
||||
|
||||
* `LocaleContextThreadLocalAccessor` -- propagates `LocaleContext` via `LocaleContextHolder`
|
||||
* `RequestAttributesThreadLocalAccessor` -- propagates `RequestAttributes` via `RequestContextHolder`
|
||||
|
||||
The above are not registered automatically. You need to register them via `ContextRegistry.getInstance()` on startup.
|
||||
|
||||
For more details, see the
|
||||
https://micrometer.io/docs/contextPropagation[documentation] of the Micrometer Context
|
||||
Propagation library.
|
||||
|
||||
@@ -44,6 +44,10 @@ has an `@ExceptionHandler` method that handles any `ErrorResponse` exception, wh
|
||||
includes all built-in web exceptions. You can add more exception handling methods, and
|
||||
use a protected method to map any exception to a `ProblemDetail`.
|
||||
|
||||
You can register `ErrorResponse` interceptors through the
|
||||
xref:web/webmvc/mvc-config.adoc[MVC Config] with a `WebMvcConfigurer`. Use that to intercept
|
||||
any RFC 7807 response and take some action.
|
||||
|
||||
|
||||
|
||||
[[mvc-ann-rest-exceptions-non-standard]]
|
||||
@@ -183,7 +187,7 @@ Message codes and arguments for each error are also resolved via `MessageSource`
|
||||
|===
|
||||
|
||||
NOTE: Unlike other exceptions, the message arguments for
|
||||
`MethodArgumentValidException` and `HandlerMethodValidationException` are baed on a list of
|
||||
`MethodArgumentValidException` and `HandlerMethodValidationException` are based on a list of
|
||||
`MessageSourceResolvable` errors that can also be customized through a
|
||||
xref:core/beans/context-introduction.adoc#context-functionality-messagesource[MessageSource]
|
||||
resource bundle. See
|
||||
|
||||
@@ -12,30 +12,7 @@ For advanced mode, you can remove `@EnableWebMvc` and extend directly from
|
||||
`DelegatingWebMvcConfiguration` instead of implementing `WebMvcConfigurer`,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class WebConfig extends DelegatingWebMvcConfiguration {
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class WebConfig : DelegatingWebMvcConfiguration() {
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./WebConfiguration[tag=snippet,indent=0]
|
||||
|
||||
You can keep existing methods in `WebConfig`, but you can now also override bean declarations
|
||||
from the base class, and you can still have any number of other `WebMvcConfigurer` implementations on
|
||||
|
||||
@@ -5,39 +5,7 @@ The MVC namespace does not have an advanced mode. If you need to customize a pro
|
||||
a bean that you cannot change otherwise, you can use the `BeanPostProcessor` lifecycle
|
||||
hook of the Spring `ApplicationContext`, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Component
|
||||
public class MyPostProcessor implements BeanPostProcessor {
|
||||
|
||||
public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Component
|
||||
class MyPostProcessor : BeanPostProcessor {
|
||||
|
||||
override fun postProcessBeforeInitialization(bean: Any, name: String): Any {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
include-code::./MyPostProcessor[tag=snippet,indent=0]
|
||||
|
||||
Note that you need to declare `MyPostProcessor` as a bean, either explicitly in XML or
|
||||
by letting it be detected through a `<component-scan/>` declaration.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -13,59 +13,9 @@ strategy over path extensions. See
|
||||
xref:web/webmvc/mvc-controller/ann-requestmapping.adoc#mvc-ann-requestmapping-suffix-pattern-match[Suffix Match] and xref:web/webmvc/mvc-controller/ann-requestmapping.adoc#mvc-ann-requestmapping-rfd[Suffix Match and RFD] for
|
||||
more details.
|
||||
|
||||
In Java configuration, you can customize requested content type resolution, as the
|
||||
following example shows:
|
||||
You can customize requested content type resolution, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
|
||||
configurer.mediaType("json", MediaType.APPLICATION_JSON);
|
||||
configurer.mediaType("xml", MediaType.APPLICATION_XML);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
class WebConfig : WebMvcConfigurer {
|
||||
|
||||
override fun configureContentNegotiation(configurer: ContentNegotiationConfigurer) {
|
||||
configurer.mediaType("json", MediaType.APPLICATION_JSON)
|
||||
configurer.mediaType("xml", MediaType.APPLICATION_XML)
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
The following example shows how to achieve the same configuration in XML:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager"/>
|
||||
|
||||
<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
|
||||
<property name="mediaTypes">
|
||||
<value>
|
||||
json=application/json
|
||||
xml=application/xml
|
||||
</value>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
include-code::./WebConfiguration[tag=snippet,indent=0]
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -6,119 +6,16 @@
|
||||
By default, formatters for various number and date types are installed, along with support
|
||||
for customization via `@NumberFormat` and `@DateTimeFormat` on fields.
|
||||
|
||||
To register custom formatters and converters in Java config, use the following:
|
||||
To register custom formatters and converters, use the following:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addFormatters(FormatterRegistry registry) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
class WebConfig : WebMvcConfigurer {
|
||||
|
||||
override fun addFormatters(registry: FormatterRegistry) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
To do the same in XML config, use the following:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:mvc="http://www.springframework.org/schema/mvc"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/mvc
|
||||
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
|
||||
|
||||
<mvc:annotation-driven conversion-service="conversionService"/>
|
||||
|
||||
<bean id="conversionService"
|
||||
class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
|
||||
<property name="converters">
|
||||
<set>
|
||||
<bean class="org.example.MyConverter"/>
|
||||
</set>
|
||||
</property>
|
||||
<property name="formatters">
|
||||
<set>
|
||||
<bean class="org.example.MyFormatter"/>
|
||||
<bean class="org.example.MyAnnotationFormatterFactory"/>
|
||||
</set>
|
||||
</property>
|
||||
<property name="formatterRegistrars">
|
||||
<set>
|
||||
<bean class="org.example.MyFormatterRegistrar"/>
|
||||
</set>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
----
|
||||
include-code::./WebConfiguration[tag=snippet,indent=0]
|
||||
|
||||
By default Spring MVC considers the request Locale when parsing and formatting date
|
||||
values. This works for forms where dates are represented as Strings with "input" form
|
||||
fields. For "date" and "time" form fields, however, browsers use a fixed format defined
|
||||
in the HTML spec. For such cases date and time formatting can be customized as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addFormatters(FormatterRegistry registry) {
|
||||
DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
|
||||
registrar.setUseIsoFormat(true);
|
||||
registrar.registerFormatters(registry);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
class WebConfig : WebMvcConfigurer {
|
||||
|
||||
override fun addFormatters(registry: FormatterRegistry) {
|
||||
val registrar = DateTimeFormatterRegistrar()
|
||||
registrar.setUseIsoFormat(true)
|
||||
registrar.registerFormatters(registry)
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./DateTimeWebConfiguration[tag=snippet,indent=0]
|
||||
|
||||
NOTE: See xref:core/validation/format.adoc#format-FormatterRegistrar-SPI[the `FormatterRegistrar` SPI]
|
||||
and the `FormattingConversionServiceFactoryBean` for more information on when to use
|
||||
|
||||
@@ -6,33 +6,7 @@
|
||||
In Java configuration, you can implement the `WebMvcConfigurer` interface, as the
|
||||
following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
// Implement configuration methods...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
class WebConfig : WebMvcConfigurer {
|
||||
|
||||
// Implement configuration methods...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
include-code::./WebConfiguration[tag=snippet,indent=0]
|
||||
|
||||
In XML, you can check attributes and sub-elements of `<mvc:annotation-driven/>`. You can
|
||||
view the https://schema.spring.io/mvc/spring-mvc.xsd[Spring MVC XML schema] or use
|
||||
|
||||
+2
-80
@@ -15,44 +15,7 @@ lower than that of the `DefaultServletHttpRequestHandler`, which is `Integer.MAX
|
||||
|
||||
The following example shows how to enable the feature by using the default setup:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
|
||||
configurer.enable();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
class WebConfig : WebMvcConfigurer {
|
||||
|
||||
override fun configureDefaultServletHandling(configurer: DefaultServletHandlerConfigurer) {
|
||||
configurer.enable()
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows how to achieve the same configuration in XML:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<mvc:default-servlet-handler/>
|
||||
----
|
||||
include-code::./WebConfiguration[tag=snippet,indent=0]
|
||||
|
||||
The caveat to overriding the `/` Servlet mapping is that the `RequestDispatcher` for the
|
||||
default Servlet must be retrieved by name rather than by path. The
|
||||
@@ -63,45 +26,4 @@ If the default Servlet has been custom-configured with a different name, or if a
|
||||
different Servlet container is being used where the default Servlet name is unknown,
|
||||
then you must explicitly provide the default Servlet's name, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
|
||||
configurer.enable("myCustomDefaultServlet");
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
class WebConfig : WebMvcConfigurer {
|
||||
|
||||
override fun configureDefaultServletHandling(configurer: DefaultServletHandlerConfigurer) {
|
||||
configurer.enable("myCustomDefaultServlet")
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
The following example shows how to achieve the same configuration in XML:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<mvc:default-servlet-handler default-servlet-name="myCustomDefaultServlet"/>
|
||||
----
|
||||
|
||||
|
||||
|
||||
include-code::./CustomDefaultServletConfiguration[tag=snippet,indent=0]
|
||||
|
||||
@@ -3,50 +3,11 @@
|
||||
|
||||
[.small]#xref:web/webflux/config.adoc#webflux-config-enable[See equivalent in the Reactive stack]#
|
||||
|
||||
In Java configuration, you can use the `@EnableWebMvc` annotation to enable MVC
|
||||
configuration, as the following example shows:
|
||||
You can use the `@EnableWebMvc` annotation to enable MVC configuration with programmatic configuration, or `<mvc:annotation-driven>` with XML configuration, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class WebConfig {
|
||||
}
|
||||
----
|
||||
include-code::./WebConfiguration[tag=snippet,indent=0]
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
class WebConfig
|
||||
----
|
||||
======
|
||||
|
||||
In XML configuration, you can use the `<mvc:annotation-driven>` element to enable MVC
|
||||
configuration, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:mvc="http://www.springframework.org/schema/mvc"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/mvc
|
||||
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
|
||||
|
||||
<mvc:annotation-driven/>
|
||||
|
||||
</beans>
|
||||
----
|
||||
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}/web.html#web.servlet.spring-mvc.auto-configuration[the dedicated Spring Boot documentation].
|
||||
|
||||
The preceding example registers a number of Spring MVC
|
||||
xref:web/webmvc/mvc-servlet/special-bean-types.adoc[infrastructure beans] and adapts to dependencies
|
||||
|
||||
@@ -1,56 +1,9 @@
|
||||
[[mvc-config-interceptors]]
|
||||
= Interceptors
|
||||
|
||||
In Java configuration, you can register interceptors to apply to incoming requests, as
|
||||
the following example shows:
|
||||
You can register interceptors to apply to incoming requests, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(new LocaleChangeInterceptor());
|
||||
registry.addInterceptor(new ThemeChangeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**");
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
class WebConfig : WebMvcConfigurer {
|
||||
|
||||
override fun addInterceptors(registry: InterceptorRegistry) {
|
||||
registry.addInterceptor(LocaleChangeInterceptor())
|
||||
registry.addInterceptor(ThemeChangeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**")
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows how to achieve the same configuration in XML:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<mvc:interceptors>
|
||||
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/>
|
||||
<mvc:interceptor>
|
||||
<mvc:mapping path="/**"/>
|
||||
<mvc:exclude-mapping path="/admin/**"/>
|
||||
<bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor"/>
|
||||
</mvc:interceptor>
|
||||
</mvc:interceptors>
|
||||
----
|
||||
include-code::./WebConfiguration[tag=snippet,indent=0]
|
||||
|
||||
NOTE: Interceptors are not ideally suited as a security layer due to the potential
|
||||
for a mismatch with annotated controller path matching, which can also match trailing
|
||||
|
||||
@@ -15,48 +15,10 @@ Boot application, prefer to use the {spring-boot-docs}/web.html#web.servlet.spri
|
||||
mechanism. Or alternatively, use `extendMessageConverters` to modify message converters
|
||||
at the end.
|
||||
|
||||
The following example adds XML and Jackson JSON converters with a customized
|
||||
`ObjectMapper` instead of the default ones:
|
||||
The following example adds XML and Jackson JSON converters with a customized `ObjectMapper`
|
||||
instead of the default ones:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class WebConfiguration implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
|
||||
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
|
||||
.indentOutput(true)
|
||||
.dateFormat(new SimpleDateFormat("yyyy-MM-dd"))
|
||||
.modulesToInstall(new ParameterNamesModule());
|
||||
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
|
||||
converters.add(new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build()));
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
class WebConfiguration : WebMvcConfigurer {
|
||||
|
||||
override fun configureMessageConverters(converters: MutableList<HttpMessageConverter<*>>) {
|
||||
val builder = Jackson2ObjectMapperBuilder()
|
||||
.indentOutput(true)
|
||||
.dateFormat(SimpleDateFormat("yyyy-MM-dd"))
|
||||
.modulesToInstall(ParameterNamesModule())
|
||||
converters.add(MappingJackson2HttpMessageConverter(builder.build()))
|
||||
converters.add(MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build()))
|
||||
----
|
||||
======
|
||||
include-code::./WebConfiguration[tag=snippet,indent=0]
|
||||
|
||||
In the preceding example,
|
||||
{spring-framework-api}/http/converter/json/Jackson2ObjectMapperBuilder.html[`Jackson2ObjectMapperBuilder`]
|
||||
@@ -76,7 +38,7 @@ It also automatically registers the following well-known modules if they are det
|
||||
* {jackson-github-org}/jackson-datatype-joda[jackson-datatype-joda]: Support for Joda-Time types.
|
||||
* {jackson-github-org}/jackson-datatype-jsr310[jackson-datatype-jsr310]: Support for Java 8 Date and Time API types.
|
||||
* {jackson-github-org}/jackson-datatype-jdk8[jackson-datatype-jdk8]: Support for other Java 8 types, such as `Optional`.
|
||||
* {jackson-github-org}/jackson-module-kotlin[`jackson-module-kotlin`]: Support for Kotlin classes and data classes.
|
||||
* {jackson-github-org}/jackson-module-kotlin[jackson-module-kotlin]: Support for Kotlin classes and data classes.
|
||||
|
||||
NOTE: Enabling indentation with Jackson XML support requires
|
||||
https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.codehaus.woodstox%22%20AND%20a%3A%22woodstox-core-asl%22[`woodstox-core-asl`]
|
||||
@@ -86,29 +48,3 @@ Other interesting Jackson modules are available:
|
||||
|
||||
* https://github.com/zalando/jackson-datatype-money[jackson-datatype-money]: Support for `javax.money` types (unofficial module).
|
||||
* {jackson-github-org}/jackson-datatype-hibernate[jackson-datatype-hibernate]: Support for Hibernate-specific types and properties (including lazy-loading aspects).
|
||||
|
||||
The following example shows how to achieve the same configuration in XML:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<mvc:annotation-driven>
|
||||
<mvc:message-converters>
|
||||
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
|
||||
<property name="objectMapper" ref="objectMapper"/>
|
||||
</bean>
|
||||
<bean class="org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter">
|
||||
<property name="objectMapper" ref="xmlMapper"/>
|
||||
</bean>
|
||||
</mvc:message-converters>
|
||||
</mvc:annotation-driven>
|
||||
|
||||
<bean id="objectMapper" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"
|
||||
p:indentOutput="true"
|
||||
p:simpleDateFormat="yyyy-MM-dd"
|
||||
p:modulesToInstall="com.fasterxml.jackson.module.paramnames.ParameterNamesModule"/>
|
||||
|
||||
<bean id="xmlMapper" parent="objectMapper" p:createXmlMapper="true"/>
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -7,61 +7,6 @@ You can customize options related to path matching and treatment of the URL.
|
||||
For details on the individual options, see the
|
||||
{spring-framework-api}/web/servlet/config/annotation/PathMatchConfigurer.html[`PathMatchConfigurer`] javadoc.
|
||||
|
||||
The following example shows how to customize path matching in Java configuration:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void configurePathMatch(PathMatchConfigurer configurer) {
|
||||
configurer.addPathPrefix("/api", HandlerTypePredicate.forAnnotation(RestController.class));
|
||||
}
|
||||
|
||||
private PathPatternParser patternParser() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
class WebConfig : WebMvcConfigurer {
|
||||
|
||||
override fun configurePathMatch(configurer: PathMatchConfigurer) {
|
||||
configurer.addPathPrefix("/api", HandlerTypePredicate.forAnnotation(RestController::class.java))
|
||||
}
|
||||
|
||||
fun patternParser(): PathPatternParser {
|
||||
//...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows how to customize path matching in XML configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<mvc:annotation-driven>
|
||||
<mvc:path-matching
|
||||
path-helper="pathHelper"
|
||||
path-matcher="pathMatcher"/>
|
||||
</mvc:annotation-driven>
|
||||
|
||||
<bean id="pathHelper" class="org.example.app.MyPathHelper"/>
|
||||
<bean id="pathMatcher" class="org.example.app.MyPathMatcher"/>
|
||||
----
|
||||
|
||||
|
||||
The following example shows how to customize path matching:
|
||||
|
||||
include-code::./WebConfiguration[tag=snippet,indent=0]
|
||||
|
||||
@@ -13,52 +13,9 @@ expiration to ensure maximum use of the browser cache and a reduction in HTTP re
|
||||
made by the browser. The `Last-Modified` information is deduced from `Resource#lastModified`
|
||||
so that HTTP conditional requests are supported with `"Last-Modified"` headers.
|
||||
|
||||
The following listing shows how to do so with Java configuration:
|
||||
The following listing shows how to do so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
registry.addResourceHandler("/resources/**")
|
||||
.addResourceLocations("/public", "classpath:/static/")
|
||||
.setCacheControl(CacheControl.maxAge(Duration.ofDays(365)));
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
class WebConfig : WebMvcConfigurer {
|
||||
|
||||
override fun addResourceHandlers(registry: ResourceHandlerRegistry) {
|
||||
registry.addResourceHandler("/resources/**")
|
||||
.addResourceLocations("/public", "classpath:/static/")
|
||||
.setCacheControl(CacheControl.maxAge(Duration.ofDays(365)))
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows how to achieve the same configuration in XML:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<mvc:resources mapping="/resources/**"
|
||||
location="/public, classpath:/static/"
|
||||
cache-period="31556926" />
|
||||
----
|
||||
include-code::./WebConfiguration[tag=snippet,indent=0]
|
||||
|
||||
See also
|
||||
xref:web/webmvc/mvc-caching.adoc#mvc-caching-static-resources[HTTP caching support for static resources].
|
||||
@@ -73,60 +30,9 @@ computed from the content, a fixed application version, or other. A
|
||||
`ContentVersionStrategy` (MD5 hash) is a good choice -- with some notable exceptions, such as
|
||||
JavaScript resources used with a module loader.
|
||||
|
||||
The following example shows how to use `VersionResourceResolver` in Java configuration:
|
||||
The following example shows how to use `VersionResourceResolver`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
registry.addResourceHandler("/resources/**")
|
||||
.addResourceLocations("/public/")
|
||||
.resourceChain(true)
|
||||
.addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
class WebConfig : WebMvcConfigurer {
|
||||
|
||||
override fun addResourceHandlers(registry: ResourceHandlerRegistry) {
|
||||
registry.addResourceHandler("/resources/**")
|
||||
.addResourceLocations("/public/")
|
||||
.resourceChain(true)
|
||||
.addResolver(VersionResourceResolver().addContentVersionStrategy("/**"))
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows how to achieve the same configuration in XML:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<mvc:resources mapping="/resources/**" location="/public/">
|
||||
<mvc:resource-chain resource-cache="true">
|
||||
<mvc:resolvers>
|
||||
<mvc:version-resolver>
|
||||
<mvc:content-version-strategy patterns="/**"/>
|
||||
</mvc:version-resolver>
|
||||
</mvc:resolvers>
|
||||
</mvc:resource-chain>
|
||||
</mvc:resources>
|
||||
----
|
||||
include-code::./VersionedConfiguration[tag=snippet,indent=0]
|
||||
|
||||
You can then use `ResourceUrlProvider` to rewrite URLs and apply the full chain of resolvers and
|
||||
transformers -- for example, to insert versions. The MVC configuration provides a `ResourceUrlProvider`
|
||||
|
||||
@@ -8,93 +8,15 @@ on the classpath (for example, Hibernate Validator), the `LocalValidatorFactoryB
|
||||
registered as a global xref:core/validation/validator.adoc[Validator] for use with `@Valid` and
|
||||
`@Validated` on controller method arguments.
|
||||
|
||||
In Java configuration, you can customize the global `Validator` instance, as the
|
||||
You can customize the global `Validator` instance, as the
|
||||
following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public Validator getValidator() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
class WebConfig : WebMvcConfigurer {
|
||||
|
||||
override fun getValidator(): Validator {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows how to achieve the same configuration in XML:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:mvc="http://www.springframework.org/schema/mvc"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/mvc
|
||||
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
|
||||
|
||||
<mvc:annotation-driven validator="globalValidator"/>
|
||||
|
||||
</beans>
|
||||
----
|
||||
include-code::./WebConfiguration[tag=snippet,indent=0]
|
||||
|
||||
Note that you can also register `Validator` implementations locally, as the following
|
||||
example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Controller
|
||||
public class MyController {
|
||||
|
||||
@InitBinder
|
||||
protected void initBinder(WebDataBinder binder) {
|
||||
binder.addValidators(new FooValidator());
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Controller
|
||||
class MyController {
|
||||
|
||||
@InitBinder
|
||||
protected fun initBinder(binder: WebDataBinder) {
|
||||
binder.addValidators(FooValidator())
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./MyController[tag=snippet,indent=0]
|
||||
|
||||
TIP: If you need to have a `LocalValidatorFactoryBean` injected somewhere, create a bean and
|
||||
mark it with `@Primary` in order to avoid conflict with the one declared in the MVC configuration.
|
||||
|
||||
@@ -5,47 +5,9 @@ This is a shortcut for defining a `ParameterizableViewController` that immediate
|
||||
forwards to a view when invoked. You can use it in static cases when there is no Java controller
|
||||
logic to run before the view generates the response.
|
||||
|
||||
The following example of Java configuration forwards a request for `/` to a view called `home`:
|
||||
The following example forwards a request for `/` to a view called `home`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addViewControllers(ViewControllerRegistry registry) {
|
||||
registry.addViewController("/").setViewName("home");
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
class WebConfig : WebMvcConfigurer {
|
||||
|
||||
override fun addViewControllers(registry: ViewControllerRegistry) {
|
||||
registry.addViewController("/").setViewName("home")
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The following example achieves the same thing as the preceding example, but with XML, by
|
||||
using the `<mvc:view-controller>` element:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<mvc:view-controller path="/" view-name="home"/>
|
||||
----
|
||||
include-code::./WebConfiguration[tag=snippet,indent=0]
|
||||
|
||||
If an `@RequestMapping` method is mapped to a URL for any HTTP method then a view
|
||||
controller cannot be used to handle the same URL. This is because a match by URL to an
|
||||
|
||||
@@ -5,127 +5,12 @@
|
||||
|
||||
The MVC configuration simplifies the registration of view resolvers.
|
||||
|
||||
The following Java configuration example configures content negotiation view
|
||||
resolution by using JSP and Jackson as a default `View` for JSON rendering:
|
||||
The following example configures content negotiation view resolution by using JSP and Jackson as a
|
||||
default `View` for JSON rendering:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureViewResolvers(ViewResolverRegistry registry) {
|
||||
registry.enableContentNegotiation(new MappingJackson2JsonView());
|
||||
registry.jsp();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
class WebConfig : WebMvcConfigurer {
|
||||
|
||||
override fun configureViewResolvers(registry: ViewResolverRegistry) {
|
||||
registry.enableContentNegotiation(MappingJackson2JsonView())
|
||||
registry.jsp()
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
The following example shows how to achieve the same configuration in XML:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<mvc:view-resolvers>
|
||||
<mvc:content-negotiation>
|
||||
<mvc:default-views>
|
||||
<bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
|
||||
</mvc:default-views>
|
||||
</mvc:content-negotiation>
|
||||
<mvc:jsp/>
|
||||
</mvc:view-resolvers>
|
||||
----
|
||||
include-code::./WebConfiguration[tag=snippet,indent=0]
|
||||
|
||||
Note, however, that FreeMarker, Groovy Markup, and script templates also require
|
||||
configuration of the underlying view technology.
|
||||
|
||||
The MVC namespace provides dedicated elements. The following example works with FreeMarker:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<mvc:view-resolvers>
|
||||
<mvc:content-negotiation>
|
||||
<mvc:default-views>
|
||||
<bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
|
||||
</mvc:default-views>
|
||||
</mvc:content-negotiation>
|
||||
<mvc:freemarker cache="false"/>
|
||||
</mvc:view-resolvers>
|
||||
|
||||
<mvc:freemarker-configurer>
|
||||
<mvc:template-loader-path location="/freemarker"/>
|
||||
</mvc:freemarker-configurer>
|
||||
----
|
||||
|
||||
In Java configuration, you can add the respective `Configurer` bean,
|
||||
as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureViewResolvers(ViewResolverRegistry registry) {
|
||||
registry.enableContentNegotiation(new MappingJackson2JsonView());
|
||||
registry.freeMarker().cache(false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FreeMarkerConfigurer freeMarkerConfigurer() {
|
||||
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
|
||||
configurer.setTemplateLoaderPath("/freemarker");
|
||||
return configurer;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
class WebConfig : WebMvcConfigurer {
|
||||
|
||||
override fun configureViewResolvers(registry: ViewResolverRegistry) {
|
||||
registry.enableContentNegotiation(MappingJackson2JsonView())
|
||||
registry.freeMarker().cache(false)
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun freeMarkerConfigurer() = FreeMarkerConfigurer().apply {
|
||||
setTemplateLoaderPath("/freemarker")
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
configuration of the underlying view technology. The following example works with FreeMarker:
|
||||
|
||||
include-code::./FreeMarkerConfiguration[tag=snippet,indent=0]
|
||||
|
||||
@@ -12,54 +12,7 @@ annotated class, indicating its role as a web component.
|
||||
To enable auto-detection of such `@Controller` beans, you can add component scanning to
|
||||
your Java configuration, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan("org.example.web")
|
||||
public class WebConfig {
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan("org.example.web")
|
||||
class WebConfig {
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows the XML configuration equivalent of the preceding example:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:p="http://www.springframework.org/schema/p"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
https://www.springframework.org/schema/context/spring-context.xsd">
|
||||
|
||||
<context:component-scan base-package="org.example.web"/>
|
||||
|
||||
<!-- ... -->
|
||||
|
||||
</beans>
|
||||
----
|
||||
include-code::./WebConfiguration[tag=snippet,indent=0]
|
||||
|
||||
`@RestController` is a xref:core/beans/classpath-scanning.adoc#beans-meta-annotations[composed annotation] that is
|
||||
itself meta-annotated with `@Controller` and `@ResponseBody` to indicate a controller whose
|
||||
|
||||
@@ -82,49 +82,9 @@ https://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html[narrated tes
|
||||
[[websocket-fallback-sockjs-enable]]
|
||||
== Enabling SockJS
|
||||
|
||||
You can enable SockJS through Java configuration, as the following example shows:
|
||||
You can enable SockJS through configuration, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSocket
|
||||
public class WebSocketConfig implements WebSocketConfigurer {
|
||||
|
||||
@Override
|
||||
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
|
||||
registry.addHandler(myHandler(), "/myHandler").withSockJS();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public WebSocketHandler myHandler() {
|
||||
return new MyHandler();
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
The following example shows the XML configuration equivalent of the preceding example:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes,attributes"]
|
||||
----
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/websocket
|
||||
https://www.springframework.org/schema/websocket/spring-websocket.xsd">
|
||||
|
||||
<websocket:handlers>
|
||||
<websocket:mapping path="/myHandler" handler="myHandler"/>
|
||||
<websocket:sockjs/>
|
||||
</websocket:handlers>
|
||||
|
||||
<bean id="myHandler" class="org.springframework.samples.MyHandler"/>
|
||||
|
||||
</beans>
|
||||
----
|
||||
include-code::./WebSocketConfiguration[tag=snippet,indent=0]
|
||||
|
||||
The preceding example is for use in Spring MVC applications and should be included in the
|
||||
configuration of a xref:web/webmvc/mvc-servlet.adoc[`DispatcherServlet`]. However, Spring's WebSocket
|
||||
|
||||
@@ -16,69 +16,12 @@ Creating a WebSocket server is as simple as implementing `WebSocketHandler` or,
|
||||
likely, extending either `TextWebSocketHandler` or `BinaryWebSocketHandler`. The following
|
||||
example uses `TextWebSocketHandler`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
import org.springframework.web.socket.TextMessage;
|
||||
include-code::./MyHandler[tag=snippet,indent=0]
|
||||
|
||||
public class MyHandler extends TextWebSocketHandler {
|
||||
|
||||
@Override
|
||||
public void handleTextMessage(WebSocketSession session, TextMessage message) {
|
||||
// ...
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
There is dedicated WebSocket Java configuration and XML namespace support for mapping the preceding
|
||||
There is dedicated WebSocket programmatic configuration and XML namespace support for mapping the preceding
|
||||
WebSocket handler to a specific URL, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocket;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSocket
|
||||
public class WebSocketConfig implements WebSocketConfigurer {
|
||||
|
||||
@Override
|
||||
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
|
||||
registry.addHandler(myHandler(), "/myHandler");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public WebSocketHandler myHandler() {
|
||||
return new MyHandler();
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
The following example shows the XML configuration equivalent of the preceding example:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes,attributes"]
|
||||
----
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/websocket
|
||||
https://www.springframework.org/schema/websocket/spring-websocket.xsd">
|
||||
|
||||
<websocket:handlers>
|
||||
<websocket:mapping path="/myHandler" handler="myHandler"/>
|
||||
</websocket:handlers>
|
||||
|
||||
<bean id="myHandler" class="org.springframework.samples.MyHandler"/>
|
||||
|
||||
</beans>
|
||||
----
|
||||
include-code::./WebSocketConfiguration[tag=snippet,indent=0]
|
||||
|
||||
The preceding example is for use in Spring MVC applications and should be included
|
||||
in the configuration of a xref:web/webmvc/mvc-servlet.adoc[`DispatcherServlet`]. However, Spring's
|
||||
@@ -104,45 +47,7 @@ You can use such an interceptor to preclude the handshake or to make any attribu
|
||||
available to the `WebSocketSession`. The following example uses a built-in interceptor
|
||||
to pass HTTP session attributes to the WebSocket session:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSocket
|
||||
public class WebSocketConfig implements WebSocketConfigurer {
|
||||
|
||||
@Override
|
||||
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
|
||||
registry.addHandler(new MyHandler(), "/myHandler")
|
||||
.addInterceptors(new HttpSessionHandshakeInterceptor());
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
The following example shows the XML configuration equivalent of the preceding example:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes,attributes"]
|
||||
----
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/websocket
|
||||
https://www.springframework.org/schema/websocket/spring-websocket.xsd">
|
||||
|
||||
<websocket:handlers>
|
||||
<websocket:mapping path="/myHandler" handler="myHandler"/>
|
||||
<websocket:handshake-interceptors>
|
||||
<bean class="org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor"/>
|
||||
</websocket:handshake-interceptors>
|
||||
</websocket:handlers>
|
||||
|
||||
<bean id="myHandler" class="org.springframework.samples.MyHandler"/>
|
||||
|
||||
</beans>
|
||||
----
|
||||
include-code::./WebSocketConfiguration[tag=snippet,indent=0]
|
||||
|
||||
A more advanced option is to extend the `DefaultHandshakeHandler` that performs
|
||||
the steps of the WebSocket handshake, including validating the client origin,
|
||||
@@ -236,69 +141,17 @@ You can configure of the underlying WebSocket server such as input message buffe
|
||||
idle timeout, and more.
|
||||
|
||||
For Jakarta WebSocket servers, you can add a `ServletServerContainerFactoryBean` to your
|
||||
Java configuration. For example:
|
||||
configuration. For example:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Bean
|
||||
public ServletServerContainerFactoryBean createWebSocketContainer() {
|
||||
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
|
||||
container.setMaxTextMessageBufferSize(8192);
|
||||
container.setMaxBinaryMessageBufferSize(8192);
|
||||
return container;
|
||||
}
|
||||
----
|
||||
|
||||
Or to your XML configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes,attributes"]
|
||||
----
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/websocket
|
||||
https://www.springframework.org/schema/websocket/spring-websocket.xsd">
|
||||
|
||||
<bean class="org.springframework...ServletServerContainerFactoryBean">
|
||||
<property name="maxTextMessageBufferSize" value="8192"/>
|
||||
<property name="maxBinaryMessageBufferSize" value="8192"/>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
----
|
||||
include-code::./WebSocketConfiguration[tag=snippet,indent=0]
|
||||
|
||||
NOTE: For client Jakarta WebSocket configuration, use
|
||||
ContainerProvider.getWebSocketContainer() in Java configuration, or
|
||||
ContainerProvider.getWebSocketContainer() in programmatic configuration, or
|
||||
`WebSocketContainerFactoryBean` in XML.
|
||||
|
||||
For Jetty, you can supply a `Consumer` callback to configure the WebSocket server:
|
||||
For Jetty, you can supply a callback to configure the WebSocket server:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSocket
|
||||
public class WebSocketConfig implements WebSocketConfigurer {
|
||||
|
||||
@Override
|
||||
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
|
||||
registry.addHandler(echoWebSocketHandler(), "/echo").setHandshakeHandler(handshakeHandler());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DefaultHandshakeHandler handshakeHandler() {
|
||||
JettyRequestUpgradeStrategy strategy = new JettyRequestUpgradeStrategy();
|
||||
strategy.addWebSocketConfigurer(configurable -> {
|
||||
policy.setInputBufferSize(8192);
|
||||
policy.setIdleTimeout(600000);
|
||||
});
|
||||
return new DefaultHandshakeHandler(strategy);
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
include-code::./JettyWebSocketConfiguration[tag=snippet,indent=0]
|
||||
|
||||
TIP: When using STOMP over WebSocket, you will also need to configure
|
||||
xref:web/websocket/stomp/server-config.adoc[STOMP WebSocket transport]
|
||||
@@ -331,51 +184,4 @@ The three possible behaviors are:
|
||||
|
||||
You can configure WebSocket and SockJS allowed origins, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocket;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSocket
|
||||
public class WebSocketConfig implements WebSocketConfigurer {
|
||||
|
||||
@Override
|
||||
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
|
||||
registry.addHandler(myHandler(), "/myHandler").setAllowedOrigins("https://mydomain.com");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public WebSocketHandler myHandler() {
|
||||
return new MyHandler();
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
The following example shows the XML configuration equivalent of the preceding example:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes,attributes"]
|
||||
----
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/websocket
|
||||
https://www.springframework.org/schema/websocket/spring-websocket.xsd">
|
||||
|
||||
<websocket:handlers allowed-origins="https://mydomain.com">
|
||||
<websocket:mapping path="/myHandler" handler="myHandler" />
|
||||
</websocket:handlers>
|
||||
|
||||
<bean id="myHandler" class="org.springframework.samples.MyHandler"/>
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
||||
include-code::./WebSocketConfiguration[tag=snippet,indent=0]
|
||||
|
||||
+1
-23
@@ -40,29 +40,7 @@ the user header on the CONNECT `Message`. Spring notes and saves the authenticat
|
||||
user and associate it with subsequent STOMP messages on the same session. The following
|
||||
example shows how to register a custom authentication interceptor:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSocketMessageBroker
|
||||
public class MyConfig implements WebSocketMessageBrokerConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureClientInboundChannel(ChannelRegistration registration) {
|
||||
registration.interceptors(new ChannelInterceptor() {
|
||||
@Override
|
||||
public Message<?> preSend(Message<?> message, MessageChannel channel) {
|
||||
StompHeaderAccessor accessor =
|
||||
MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
|
||||
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
|
||||
Authentication user = ... ; // access authentication header(s)
|
||||
accessor.setUser(user);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
----
|
||||
include-code::./WebSocketConfiguration[tag=snippet,indent=0]
|
||||
|
||||
Also, note that, when you use Spring Security's authorization for messages, at present,
|
||||
you need to ensure that the authentication `ChannelInterceptor` config is ordered
|
||||
|
||||
@@ -105,5 +105,20 @@ it handle ERROR frames in addition to the `handleException` callback for
|
||||
exceptions from the handling of messages and `handleTransportError` for
|
||||
transport-level errors including `ConnectionLostException`.
|
||||
|
||||
You can use the `inboundMessageSizeLimit` and `outboundMessageSizeLimit` properties of
|
||||
`WebSocketStompClient` to limit the maximum size of inbound and outbound WebSocket
|
||||
messages. When an outbound STOMP message exceeds the limit, it is split into partial frames,
|
||||
which the receiver would have to reassemble. By default, there is no size limit for outbound
|
||||
messages. When an inbound STOMP message size exceeds the configured limit, a
|
||||
`StompConversionException` is thrown. The default size limit for inbound messages is `64KB`.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
WebSocketClient webSocketClient = new StandardWebSocketClient();
|
||||
WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient);
|
||||
stompClient.setInboundMessageSizeLimit(64 * 1024); // 64KB
|
||||
stompClient.setOutboundMessageSizeLimit(64 * 1024); // 64KB
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
||||
+2
-72
@@ -62,42 +62,7 @@ documentation of the XML schema for important additional details.
|
||||
|
||||
The following example shows a possible configuration:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSocketMessageBroker
|
||||
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
|
||||
registration.setSendTimeLimit(15 * 1000).setSendBufferSizeLimit(512 * 1024);
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
The following example shows the XML configuration equivalent of the preceding example:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes,attributes"]
|
||||
----
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/websocket
|
||||
https://www.springframework.org/schema/websocket/spring-websocket.xsd">
|
||||
|
||||
<websocket:message-broker>
|
||||
<websocket:transport send-timeout="15000" send-buffer-size="524288" />
|
||||
<!-- ... -->
|
||||
</websocket:message-broker>
|
||||
|
||||
</beans>
|
||||
----
|
||||
include-code::./WebSocketConfiguration[tag=snippet,indent=0]
|
||||
|
||||
You can also use the WebSocket transport configuration shown earlier to configure the
|
||||
maximum allowed size for incoming STOMP messages. In theory, a WebSocket
|
||||
@@ -115,42 +80,7 @@ minimum.
|
||||
|
||||
The following example shows one possible configuration:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSocketMessageBroker
|
||||
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
|
||||
registration.setMessageSizeLimit(128 * 1024);
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
The following example shows the XML configuration equivalent of the preceding example:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes,attributes"]
|
||||
----
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/websocket
|
||||
https://www.springframework.org/schema/websocket/spring-websocket.xsd">
|
||||
|
||||
<websocket:message-broker>
|
||||
<websocket:transport message-size="131072" />
|
||||
<!-- ... -->
|
||||
</websocket:message-broker>
|
||||
|
||||
</beans>
|
||||
----
|
||||
include-code::./MessageSizeLimitWebSocketConfiguration[tag=snippet,indent=0]
|
||||
|
||||
An important point about scaling involves using multiple application instances.
|
||||
Currently, you cannot do that with the simple broker.
|
||||
|
||||
@@ -6,65 +6,14 @@ When messages are routed to `@MessageMapping` methods, they are matched with
|
||||
This is a good convention in web applications and similar to HTTP URLs. However, if
|
||||
you are more used to messaging conventions, you can switch to using dot (`.`) as the separator.
|
||||
|
||||
The following example shows how to do so in Java configuration:
|
||||
The following example shows how to do so:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSocketMessageBroker
|
||||
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
|
||||
|
||||
// ...
|
||||
|
||||
@Override
|
||||
public void configureMessageBroker(MessageBrokerRegistry registry) {
|
||||
registry.setPathMatcher(new AntPathMatcher("."));
|
||||
registry.enableStompBrokerRelay("/queue", "/topic");
|
||||
registry.setApplicationDestinationPrefixes("/app");
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
The following example shows the XML configuration equivalent of the preceding example:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes,attributes"]
|
||||
----
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/websocket
|
||||
https://www.springframework.org/schema/websocket/spring-websocket.xsd">
|
||||
|
||||
<websocket:message-broker application-destination-prefix="/app" path-matcher="pathMatcher">
|
||||
<websocket:stomp-endpoint path="/stomp"/>
|
||||
<websocket:stomp-broker-relay prefix="/topic,/queue" />
|
||||
</websocket:message-broker>
|
||||
|
||||
<bean id="pathMatcher" class="org.springframework.util.AntPathMatcher">
|
||||
<constructor-arg index="0" value="."/>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
----
|
||||
include-code::./WebSocketConfiguration[tag=snippet,indent=0]
|
||||
|
||||
After that, a controller can use a dot (`.`) as the separator in `@MessageMapping` methods,
|
||||
as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Controller
|
||||
@MessageMapping("red")
|
||||
public class RedController {
|
||||
|
||||
@MessageMapping("blue.{green}")
|
||||
public void handleGreen(@DestinationVariable String green) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
include-code::./RedController[tag=snippet,indent=0]
|
||||
|
||||
The client can now send a message to `/app/red.blue.green123`.
|
||||
|
||||
|
||||
@@ -5,56 +5,7 @@ STOMP over WebSocket support is available in the `spring-messaging` and
|
||||
`spring-websocket` modules. Once you have those dependencies, you can expose a STOMP
|
||||
endpoint over WebSocket, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
|
||||
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSocketMessageBroker
|
||||
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
|
||||
|
||||
@Override
|
||||
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
||||
registry.addEndpoint("/portfolio"); // <1>
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureMessageBroker(MessageBrokerRegistry config) {
|
||||
config.setApplicationDestinationPrefixes("/app"); // <2>
|
||||
config.enableSimpleBroker("/topic", "/queue"); // <3>
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
<1> `/portfolio` is the HTTP URL for the endpoint to which a WebSocket (or SockJS)
|
||||
client needs to connect for the WebSocket handshake.
|
||||
<2> STOMP messages whose destination header begins with `/app` are routed to
|
||||
`@MessageMapping` methods in `@Controller` classes.
|
||||
<3> Use the built-in message broker for subscriptions and broadcasting and
|
||||
route messages whose destination header begins with `/topic` or `/queue` to the broker.
|
||||
|
||||
|
||||
The following example shows the XML configuration equivalent of the preceding example:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes,attributes"]
|
||||
----
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/websocket
|
||||
https://www.springframework.org/schema/websocket/spring-websocket.xsd">
|
||||
|
||||
<websocket:message-broker application-destination-prefix="/app">
|
||||
<websocket:stomp-endpoint path="/portfolio" />
|
||||
<websocket:simple-broker prefix="/topic, /queue"/>
|
||||
</websocket:message-broker>
|
||||
|
||||
</beans>
|
||||
----
|
||||
include-code::./WebSocketConfiguration[tag=snippet,indent=0]
|
||||
|
||||
NOTE: For the built-in simple broker, the `/topic` and `/queue` prefixes do not have any special
|
||||
meaning. They are merely a convention to differentiate between pub-sub versus point-to-point
|
||||
|
||||
+1
-21
@@ -36,27 +36,7 @@ connectivity is lost, to the same host and port. If you wish to supply multiple
|
||||
on each attempt to connect, you can configure a supplier of addresses, instead of a
|
||||
fixed host and port. The following example shows how to do that:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSocketMessageBroker
|
||||
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
|
||||
|
||||
// ...
|
||||
|
||||
@Override
|
||||
public void configureMessageBroker(MessageBrokerRegistry registry) {
|
||||
registry.enableStompBrokerRelay("/queue/", "/topic/").setTcpClient(createTcpClient());
|
||||
registry.setApplicationDestinationPrefixes("/app");
|
||||
}
|
||||
|
||||
private ReactorNettyTcpClient<byte[]> createTcpClient() {
|
||||
return new ReactorNettyTcpClient<>(
|
||||
client -> client.addressSupplier(() -> ... ),
|
||||
new StompReactorNettyCodec());
|
||||
}
|
||||
}
|
||||
----
|
||||
include-code::./WebSocketConfiguration[tag=snippet,indent=0]
|
||||
|
||||
You can also configure the STOMP broker relay with a `virtualHost` property.
|
||||
The value of this property is set as the `host` header of every `CONNECT` frame
|
||||
|
||||
@@ -15,48 +15,7 @@ and run it with STOMP support enabled. Then you can enable the STOMP broker rela
|
||||
|
||||
The following example configuration enables a full-featured broker:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSocketMessageBroker
|
||||
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
|
||||
|
||||
@Override
|
||||
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
||||
registry.addEndpoint("/portfolio").withSockJS();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureMessageBroker(MessageBrokerRegistry registry) {
|
||||
registry.enableStompBrokerRelay("/topic", "/queue");
|
||||
registry.setApplicationDestinationPrefixes("/app");
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
The following example shows the XML configuration equivalent of the preceding example:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes,attributes"]
|
||||
----
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/websocket
|
||||
https://www.springframework.org/schema/websocket/spring-websocket.xsd">
|
||||
|
||||
<websocket:message-broker application-destination-prefix="/app">
|
||||
<websocket:stomp-endpoint path="/portfolio" />
|
||||
<websocket:sockjs/>
|
||||
</websocket:stomp-endpoint>
|
||||
<websocket:stomp-broker-relay prefix="/topic,/queue" />
|
||||
</websocket:message-broker>
|
||||
|
||||
</beans>
|
||||
----
|
||||
include-code::./WebSocketConfiguration[tag=snippet,indent=0]
|
||||
|
||||
The STOMP broker relay in the preceding configuration is a Spring
|
||||
{spring-framework-api}/messaging/MessageHandler.html[`MessageHandler`]
|
||||
|
||||
@@ -17,29 +17,4 @@ declared in the built-in WebSocket configuration, however, you'll' need `@Lazy`
|
||||
a cycle between the built-in WebSocket configuration and your
|
||||
`WebSocketMessageBrokerConfigurer`. For example:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSocketMessageBroker
|
||||
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
|
||||
|
||||
private TaskScheduler messageBrokerTaskScheduler;
|
||||
|
||||
@Autowired
|
||||
public void setMessageBrokerTaskScheduler(@Lazy TaskScheduler taskScheduler) {
|
||||
this.messageBrokerTaskScheduler = taskScheduler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureMessageBroker(MessageBrokerRegistry registry) {
|
||||
registry.enableSimpleBroker("/queue/", "/topic/")
|
||||
.setHeartbeatValue(new long[] {10000, 20000})
|
||||
.setTaskScheduler(this.messageBrokerTaskScheduler);
|
||||
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
|
||||
include-code::./WebSocketConfiguration[tag=snippet,indent=0]
|
||||
|
||||
@@ -6,35 +6,12 @@ of a STOMP connection but not for every client message. Applications can also re
|
||||
`ChannelInterceptor` to intercept any message and in any part of the processing chain.
|
||||
The following example shows how to intercept inbound messages from clients:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSocketMessageBroker
|
||||
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureClientInboundChannel(ChannelRegistration registration) {
|
||||
registration.interceptors(new MyChannelInterceptor());
|
||||
}
|
||||
}
|
||||
----
|
||||
include-code::./WebSocketConfiguration[tag=snippet,indent=0]
|
||||
|
||||
A custom `ChannelInterceptor` can use `StompHeaderAccessor` or `SimpMessageHeaderAccessor`
|
||||
to access information about the message, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public class MyChannelInterceptor implements ChannelInterceptor {
|
||||
|
||||
@Override
|
||||
public Message<?> preSend(Message<?> message, MessageChannel channel) {
|
||||
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
|
||||
StompCommand command = accessor.getStompCommand();
|
||||
// ...
|
||||
return message;
|
||||
}
|
||||
}
|
||||
----
|
||||
include-code::./MyChannelInterceptor[tag=snippet,indent=0]
|
||||
|
||||
Applications can also implement `ExecutorChannelInterceptor`, which is a sub-interface
|
||||
of `ChannelInterceptor` with callbacks in the thread in which the messages are handled.
|
||||
|
||||
@@ -60,33 +60,9 @@ to broadcast to subscribed clients.
|
||||
|
||||
We can trace the flow through a simple example. Consider the following example, which sets up a server:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSocketMessageBroker
|
||||
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
|
||||
include-code::./WebSocketConfiguration[tag=snippet,indent=0]
|
||||
|
||||
@Override
|
||||
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
||||
registry.addEndpoint("/portfolio");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureMessageBroker(MessageBrokerRegistry registry) {
|
||||
registry.setApplicationDestinationPrefixes("/app");
|
||||
registry.enableSimpleBroker("/topic");
|
||||
}
|
||||
}
|
||||
|
||||
@Controller
|
||||
public class GreetingController {
|
||||
|
||||
@MessageMapping("/greeting")
|
||||
public String handle(String greeting) {
|
||||
return "[" + getTimestamp() + ": " + greeting;
|
||||
}
|
||||
}
|
||||
----
|
||||
include-code::./GreetingController[tag=snippet,indent=0]
|
||||
|
||||
The preceding example supports the following flow:
|
||||
|
||||
|
||||
@@ -8,40 +8,7 @@ not match the exact order of publication.
|
||||
|
||||
To enable ordered publishing, set the `setPreservePublishOrder` flag as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSocketMessageBroker
|
||||
public class MyConfig implements WebSocketMessageBrokerConfigurer {
|
||||
|
||||
@Override
|
||||
protected void configureMessageBroker(MessageBrokerRegistry registry) {
|
||||
// ...
|
||||
registry.setPreservePublishOrder(true);
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
The following example shows the XML configuration equivalent of the preceding example:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes,attributes"]
|
||||
----
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/websocket
|
||||
https://www.springframework.org/schema/websocket/spring-websocket.xsd">
|
||||
|
||||
<websocket:message-broker preserve-publish-order="true">
|
||||
<!-- ... -->
|
||||
</websocket:message-broker>
|
||||
|
||||
</beans>
|
||||
----
|
||||
include-code::./PublishOrderWebSocketConfiguration[tag=snippet,indent=0]
|
||||
|
||||
When the flag is set, messages within the same client session are published to the
|
||||
`clientOutboundChannel` one at a time, so that the order of publication is guaranteed.
|
||||
@@ -54,15 +21,4 @@ of handling may not match the exact order in which they were received.
|
||||
|
||||
To enable ordered publishing, set the `setPreserveReceiveOrder` flag as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSocketMessageBroker
|
||||
public class MyConfig implements WebSocketMessageBrokerConfigurer {
|
||||
|
||||
@Override
|
||||
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
||||
registry.setPreserveReceiveOrder(true);
|
||||
}
|
||||
}
|
||||
----
|
||||
include-code::./ReceiveOrderWebSocketConfiguration[tag=snippet,indent=0]
|
||||
|
||||
@@ -10,43 +10,9 @@ under the WebSocket section.
|
||||
|
||||
For Jetty WebSocket servers, customize the `JettyRequestUpgradeStrategy` as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSocketMessageBroker
|
||||
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
|
||||
|
||||
@Override
|
||||
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
||||
registry.addEndpoint("/portfolio").setHandshakeHandler(handshakeHandler());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DefaultHandshakeHandler handshakeHandler() {
|
||||
JettyRequestUpgradeStrategy strategy = new JettyRequestUpgradeStrategy();
|
||||
strategy.addWebSocketConfigurer(configurable -> {
|
||||
policy.setInputBufferSize(4 * 8192);
|
||||
policy.setIdleTimeout(600000);
|
||||
});
|
||||
return new DefaultHandshakeHandler(strategy);
|
||||
}
|
||||
}
|
||||
----
|
||||
include-code::./JettyWebSocketConfiguration[tag=snippet,indent=0]
|
||||
|
||||
In addition to WebSocket server properties, there are also STOMP WebSocket transport properties
|
||||
to customize as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebSocketMessageBroker
|
||||
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureWebSocketTransport(WebSocketTransportRegistration registry) {
|
||||
registry.setMessageSizeLimit(4 * 8192);
|
||||
registry.setTimeToFirstMessage(30000);
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
include-code::./WebSocketConfiguration[tag=snippet,indent=0]
|
||||
|
||||
+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.core.aop.aopajltwspring;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.EnableLoadTimeWeaving;
|
||||
|
||||
// tag::snippet[]
|
||||
@Configuration
|
||||
@EnableLoadTimeWeaving
|
||||
public class ApplicationConfiguration {
|
||||
}
|
||||
// end::snippet[]
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.core.aop.aopajltwspring;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.EnableLoadTimeWeaving;
|
||||
import org.springframework.context.annotation.LoadTimeWeavingConfigurer;
|
||||
import org.springframework.instrument.classloading.LoadTimeWeaver;
|
||||
import org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver;
|
||||
|
||||
// tag::snippet[]
|
||||
@Configuration
|
||||
@EnableLoadTimeWeaving
|
||||
public class CustomWeaverConfiguration implements LoadTimeWeavingConfigurer {
|
||||
|
||||
@Override
|
||||
public LoadTimeWeaver getLoadTimeWeaver() {
|
||||
return new ReflectiveLoadTimeWeaver();
|
||||
}
|
||||
}
|
||||
// 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.core.aop.aopatconfigurable;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.aspectj.EnableSpringConfigured;
|
||||
|
||||
// tag::snippet[]
|
||||
@Configuration
|
||||
@EnableSpringConfigured
|
||||
public class ApplicationConfiguration {
|
||||
}
|
||||
// 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.core.aop.ataspectj.aopaspectjsupport;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||
|
||||
// tag::snippet[]
|
||||
@Configuration
|
||||
@EnableAspectJAutoProxy
|
||||
public class ApplicationConfiguration {
|
||||
}
|
||||
// end::snippet[]
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.core.aop.ataspectj.aopataspectj;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
// tag::snippet[]
|
||||
public class ApplicationConfiguration {
|
||||
|
||||
@Bean
|
||||
public NotVeryUsefulAspect myAspect() {
|
||||
NotVeryUsefulAspect myAspect = new NotVeryUsefulAspect();
|
||||
// Configure properties of the aspect here
|
||||
return myAspect;
|
||||
}
|
||||
}
|
||||
// end::snippet[]
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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.core.aop.ataspectj.aopataspectj;
|
||||
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
|
||||
// tag::snippet[]
|
||||
@Aspect
|
||||
public class NotVeryUsefulAspect {
|
||||
}
|
||||
// end::snippet[]
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.core.aop.ataspectj.aopataspectjexample;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||
|
||||
// tag::snippet[]
|
||||
@Configuration
|
||||
@EnableAspectJAutoProxy
|
||||
public class ApplicationConfiguration {
|
||||
|
||||
@Bean
|
||||
public ConcurrentOperationExecutor concurrentOperationExecutor() {
|
||||
ConcurrentOperationExecutor executor = new ConcurrentOperationExecutor();
|
||||
executor.setMaxRetries(3);
|
||||
executor.setOrder(100);
|
||||
return executor;
|
||||
|
||||
}
|
||||
}
|
||||
// end::snippet[]
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.core.aop.ataspectj.aopataspectjexample;
|
||||
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.dao.PessimisticLockingFailureException;
|
||||
|
||||
// tag::snippet[]
|
||||
@Aspect
|
||||
public class ConcurrentOperationExecutor implements Ordered {
|
||||
|
||||
private static final int DEFAULT_MAX_RETRIES = 2;
|
||||
|
||||
private int maxRetries = DEFAULT_MAX_RETRIES;
|
||||
private int order = 1;
|
||||
|
||||
public void setMaxRetries(int maxRetries) {
|
||||
this.maxRetries = maxRetries;
|
||||
}
|
||||
|
||||
public int getOrder() {
|
||||
return this.order;
|
||||
}
|
||||
|
||||
public void setOrder(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
@Around("com.xyz.CommonPointcuts.businessService()")
|
||||
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
|
||||
int numAttempts = 0;
|
||||
PessimisticLockingFailureException lockFailureException;
|
||||
do {
|
||||
numAttempts++;
|
||||
try {
|
||||
return pjp.proceed();
|
||||
}
|
||||
catch(PessimisticLockingFailureException ex) {
|
||||
lockFailureException = ex;
|
||||
}
|
||||
} while(numAttempts <= this.maxRetries);
|
||||
throw lockFailureException;
|
||||
}
|
||||
}
|
||||
// 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.core.aop.ataspectj.aopataspectjexample.service;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
// tag::snippet[]
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
// marker annotation
|
||||
public @interface Idempotent {
|
||||
}
|
||||
// end::snippet[]
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.core.aop.ataspectj.aopataspectjexample.service;
|
||||
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class SampleService {
|
||||
|
||||
// tag::snippet[]
|
||||
@Around("execution(* com.xyz..service.*.*(..)) && " +
|
||||
"@annotation(com.xyz.service.Idempotent)")
|
||||
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
|
||||
// ...
|
||||
return pjp.proceed(pjp.getArgs());
|
||||
}
|
||||
// end::snippet[]
|
||||
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.core.aopapi.aopapipointcutsregex;
|
||||
|
||||
import org.springframework.aop.support.JdkRegexpMethodPointcut;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
// tag::snippet[]
|
||||
@Configuration
|
||||
public class JdkRegexpConfiguration {
|
||||
|
||||
@Bean
|
||||
public JdkRegexpMethodPointcut settersAndAbsquatulatePointcut() {
|
||||
JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
|
||||
pointcut.setPatterns(".*set.*", ".*absquatulate");
|
||||
return pointcut;
|
||||
}
|
||||
}
|
||||
// end::snippet[]
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.core.aopapi.aopapipointcutsregex;
|
||||
|
||||
import org.aopalliance.aop.Advice;
|
||||
|
||||
import org.springframework.aop.support.RegexpMethodPointcutAdvisor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
// tag::snippet[]
|
||||
@Configuration
|
||||
public class RegexpConfiguration {
|
||||
|
||||
@Bean
|
||||
public RegexpMethodPointcutAdvisor settersAndAbsquatulateAdvisor(Advice beanNameOfAopAllianceInterceptor) {
|
||||
RegexpMethodPointcutAdvisor advisor = new RegexpMethodPointcutAdvisor();
|
||||
advisor.setAdvice(beanNameOfAopAllianceInterceptor);
|
||||
advisor.setPatterns(".*set.*", ".*absquatulate");
|
||||
return advisor;
|
||||
}
|
||||
}
|
||||
// end::snippet[]
|
||||
|
||||
+3
-11
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 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,15 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.aop.framework;
|
||||
package org.springframework.docs.core.expressions.expressionsbeandef;
|
||||
|
||||
public class ClassWithConstructor {
|
||||
|
||||
public ClassWithConstructor(Object object) {
|
||||
|
||||
}
|
||||
|
||||
public void method() {
|
||||
|
||||
}
|
||||
public class CustomerPreferenceDao {
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.core.expressions.expressionsbeandef;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
||||
// tag::snippet[]
|
||||
public class FieldValueTestBean {
|
||||
|
||||
@Value("#{ systemProperties['user.region'] }")
|
||||
private String defaultLocale;
|
||||
|
||||
public void setDefaultLocale(String defaultLocale) {
|
||||
this.defaultLocale = defaultLocale;
|
||||
}
|
||||
|
||||
public String getDefaultLocale() {
|
||||
return this.defaultLocale;
|
||||
}
|
||||
}
|
||||
// end::snippet[]
|
||||
+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.core.expressions.expressionsbeandef;
|
||||
|
||||
public class MovieFinder {
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.core.expressions.expressionsbeandef;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
||||
// tag::snippet[]
|
||||
public class MovieRecommender {
|
||||
|
||||
private String defaultLocale;
|
||||
|
||||
private CustomerPreferenceDao customerPreferenceDao;
|
||||
|
||||
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao,
|
||||
@Value("#{systemProperties['user.country']}") String defaultLocale) {
|
||||
this.customerPreferenceDao = customerPreferenceDao;
|
||||
this.defaultLocale = defaultLocale;
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
// end::snippet[]
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.core.expressions.expressionsbeandef;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
||||
// tag::snippet[]
|
||||
public class NumberGuess {
|
||||
|
||||
private double randomNumber;
|
||||
|
||||
@Value("#{ T(java.lang.Math).random() * 100.0 }")
|
||||
public void setRandomNumber(double randomNumber) {
|
||||
this.randomNumber = randomNumber;
|
||||
}
|
||||
|
||||
public double getRandomNumber() {
|
||||
return randomNumber;
|
||||
}
|
||||
}
|
||||
// end::snippet[]
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.core.expressions.expressionsbeandef;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
||||
// tag::snippet[]
|
||||
public class PropertyValueTestBean {
|
||||
|
||||
private String defaultLocale;
|
||||
|
||||
@Value("#{ systemProperties['user.region'] }")
|
||||
public void setDefaultLocale(String defaultLocale) {
|
||||
this.defaultLocale = defaultLocale;
|
||||
}
|
||||
|
||||
public String getDefaultLocale() {
|
||||
return this.defaultLocale;
|
||||
}
|
||||
}
|
||||
// end::snippet[]
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.core.expressions.expressionsbeandef;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
||||
// tag::snippet[]
|
||||
public class ShapeGuess {
|
||||
|
||||
private double initialShapeSeed;
|
||||
|
||||
@Value("#{ numberGuess.randomNumber }")
|
||||
public void setInitialShapeSeed(double initialShapeSeed) {
|
||||
this.initialShapeSeed = initialShapeSeed;
|
||||
}
|
||||
|
||||
public double getInitialShapeSeed() {
|
||||
return initialShapeSeed;
|
||||
}
|
||||
}
|
||||
// end::snippet[]
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.core.expressions.expressionsbeandef;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
||||
// tag::snippet[]
|
||||
public class SimpleMovieLister {
|
||||
|
||||
private MovieFinder movieFinder;
|
||||
private String defaultLocale;
|
||||
|
||||
@Autowired
|
||||
public void configure(MovieFinder movieFinder,
|
||||
@Value("#{ systemProperties['user.region'] }") String defaultLocale) {
|
||||
this.movieFinder = movieFinder;
|
||||
this.defaultLocale = defaultLocale;
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
// end::snippet[]
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.core.validation.formatconfiguringformattingglobaldatetimeformat;
|
||||
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.format.datetime.DateFormatter;
|
||||
import org.springframework.format.datetime.DateFormatterRegistrar;
|
||||
import org.springframework.format.datetime.standard.DateTimeFormatterRegistrar;
|
||||
import org.springframework.format.number.NumberFormatAnnotationFormatterFactory;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
import org.springframework.format.support.FormattingConversionService;
|
||||
|
||||
// tag::snippet[]
|
||||
@Configuration
|
||||
public class ApplicationConfiguration {
|
||||
|
||||
@Bean
|
||||
public FormattingConversionService conversionService() {
|
||||
|
||||
// Use the DefaultFormattingConversionService but do not register defaults
|
||||
DefaultFormattingConversionService conversionService =
|
||||
new DefaultFormattingConversionService(false);
|
||||
|
||||
// Ensure @NumberFormat is still supported
|
||||
conversionService.addFormatterForFieldAnnotation(
|
||||
new NumberFormatAnnotationFormatterFactory());
|
||||
|
||||
// Register JSR-310 date conversion with a specific global format
|
||||
DateTimeFormatterRegistrar dateTimeRegistrar = new DateTimeFormatterRegistrar();
|
||||
dateTimeRegistrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"));
|
||||
dateTimeRegistrar.registerFormatters(conversionService);
|
||||
|
||||
// Register date conversion with a specific global format
|
||||
DateFormatterRegistrar dateRegistrar = new DateFormatterRegistrar();
|
||||
dateRegistrar.setFormatter(new DateFormatter("yyyyMMdd"));
|
||||
dateRegistrar.registerFormatters(conversionService);
|
||||
|
||||
return conversionService;
|
||||
}
|
||||
}
|
||||
// end::snippet[]
|
||||
+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;
|
||||
|
||||
public interface CorporateEventDao {
|
||||
}
|
||||
+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;
|
||||
|
||||
public interface CorporateEventRepository {
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
public class JdbcCorporateEventDao implements CorporateEventDao {
|
||||
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
// JDBC-backed implementations of the methods on the CorporateEventDao follow...
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package org.springframework.docs.dataaccess.jdbc.jdbcJdbcTemplateidioms;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.apache.commons.dbcp2.BasicDataSource;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class JdbcCorporateEventDaoConfiguration {
|
||||
|
||||
// tag::snippet[]
|
||||
@Bean
|
||||
JdbcCorporateEventDao corporateEventDao(DataSource dataSource) {
|
||||
return new JdbcCorporateEventDao();
|
||||
}
|
||||
|
||||
@Bean(destroyMethod = "close")
|
||||
BasicDataSource dataSource() {
|
||||
BasicDataSource dataSource = new BasicDataSource();
|
||||
dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
|
||||
dataSource.setUrl("jdbc:hsqldb:hsql://localhost:");
|
||||
dataSource.setUsername("sa");
|
||||
dataSource.setPassword("");
|
||||
return dataSource;
|
||||
}
|
||||
// end::snippet[]
|
||||
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
// tag::snippet[]
|
||||
@Repository
|
||||
public class JdbcCorporateEventRepository implements CorporateEventRepository {
|
||||
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
// Implicitly autowire the DataSource constructor parameter
|
||||
public JdbcCorporateEventRepository(DataSource dataSource) {
|
||||
this.jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
// JDBC-backed implementations of the methods on the CorporateEventRepository follow...
|
||||
}
|
||||
// end::snippet[]
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user