mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
468 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 68537d6d5b | |||
| 57b838ddda | |||
| 8c784085d2 | |||
| c0a1e1718e | |||
| fe29e734ae | |||
| 50c3a62589 | |||
| a936a6a8ce | |||
| 8d112b8514 | |||
| f8cb0fa2a0 | |||
| c56c16d7ba | |||
| df1f8139cc | |||
| 9624ea392a | |||
| 682a4d5353 | |||
| 4419d56178 | |||
| 244c97993b | |||
| 9d28fe90f5 | |||
| d2a4ac519c | |||
| dbbebf541d | |||
| fe73c630da | |||
| 3456fd054f | |||
| 5fd75dd27b | |||
| 6c29a5779e | |||
| e9413b93c6 | |||
| 4cc02fe3bc | |||
| b1cf832c28 | |||
| 34e5ce9360 | |||
| 375114defa | |||
| 00c2c1d2a1 | |||
| 7c9fc575ff | |||
| c859211f7a | |||
| 4dbe9d6709 | |||
| acedbfbaba | |||
| 5d6f151031 | |||
| a09495d4e9 | |||
| 5cab6a1f3a | |||
| a02a017e6e | |||
| 9f2f93129b | |||
| 475ac6ef5d | |||
| 79f43041ad | |||
| edb4a3467a | |||
| 024d02225c | |||
| 9305a64a50 | |||
| 40672c3715 | |||
| 7c50464bba | |||
| 0d10d4beee | |||
| f60bec986f | |||
| 6825a842b5 | |||
| 7ace9aa429 | |||
| 8979ac789f | |||
| 9cb4c5565a | |||
| 08f38c52c7 | |||
| afa936e985 | |||
| 3677d3597b | |||
| 76bc7deb8e | |||
| 6c3cb5d2e0 | |||
| 3ce71932c0 | |||
| 3a9c7524f0 | |||
| ce66b251ab | |||
| 626a7fc52a | |||
| 3d8455b257 | |||
| fd38c23699 | |||
| e47418c948 | |||
| d6de374424 | |||
| b437b7be34 | |||
| 2d56505ea9 | |||
| 8a44b6445d | |||
| 6d24e62e83 | |||
| 2d62be8590 | |||
| 2e1374b459 | |||
| bb6150e44e | |||
| 38a4f23f16 | |||
| 6739ca82ce | |||
| 88e6544d9d | |||
| 1999c78350 | |||
| afd67a0deb | |||
| dcaddddbc5 | |||
| ce3be72e7f | |||
| 3e3f046dc9 | |||
| 5847454aa4 | |||
| 569df6eecc | |||
| 979118c1eb | |||
| f87a87e29d | |||
| 0619e19140 | |||
| ac521a366a | |||
| 9a4df5a97d | |||
| f54e1ef880 | |||
| a3efcabe0c | |||
| dd3dc2d0d0 | |||
| 2578e3894e | |||
| 5335778d8d | |||
| 616e7779e0 | |||
| 84c7f07364 | |||
| ac429a4ef7 | |||
| 12d4dc1bae | |||
| 4da2499613 | |||
| 18896aceca | |||
| d7824c7831 | |||
| f91b2249b3 | |||
| 33b329e13a | |||
| b23316302d | |||
| dc843adb45 | |||
| 9fcafd2650 | |||
| ccbb4bdd27 | |||
| 5b79a57f14 | |||
| 189d4e3e4c | |||
| 79a1fcb099 | |||
| 026be04b75 | |||
| 45ae00fda3 | |||
| 3e2f58cdd2 | |||
| 72926c29f9 | |||
| f5c1e2ffa1 | |||
| 67df0756cd | |||
| 17bc3facaa | |||
| 1de9426df0 | |||
| 27fdf56ed5 | |||
| 55392dfbd7 | |||
| ca9439657e | |||
| 5819d76d98 | |||
| 431ae03447 | |||
| a7ec6b4a2a | |||
| e5ff54955f | |||
| 9c0b28ffdc | |||
| 08e7f5a292 | |||
| 5f458e2578 | |||
| 7584dfc79a | |||
| dc2c33e0ab | |||
| dbf360997a | |||
| b719a87061 | |||
| 97e557c886 | |||
| 89c170b095 | |||
| 482482f62b | |||
| a2a676241c | |||
| 28602ad012 | |||
| 8e9dc3590d | |||
| b8a7aa9395 | |||
| cb63164593 | |||
| 9f061f1eec | |||
| 1d4bf58e8d | |||
| ce85fdc5c7 | |||
| 7851994a17 | |||
| 84bc1dfa89 | |||
| 159a3e71f2 | |||
| adbba712d6 | |||
| e050c37158 | |||
| eff1a1a664 | |||
| 8838e0d825 | |||
| 0d21f0b1d4 | |||
| 41aeb07585 | |||
| 70aaec2a8d | |||
| 3029b7b9be | |||
| 708f600afe | |||
| f7418704de | |||
| 7440afe571 | |||
| bc583ea74f | |||
| dcda127b60 | |||
| ac6385025b | |||
| e170c16b02 | |||
| 4beb05ddb3 | |||
| 06fd6ecf1f | |||
| b8827d8e11 | |||
| 23a60aeb27 | |||
| 58fbf60d2d | |||
| ebca9f726f | |||
| 5fd73f0bae | |||
| 16937c7ce5 | |||
| c0c9ba5c2c | |||
| 4d6249811e | |||
| 3a08d643ed | |||
| a74b86e812 | |||
| 7c9dca3d2e | |||
| 64219783f6 | |||
| 846a54aef4 | |||
| 9e735d4f0f | |||
| 0ddb394fd0 | |||
| f5a39308a0 | |||
| e4ceb80678 | |||
| 64b89429cb | |||
| 8e843c242a | |||
| 635f010de3 | |||
| aac1ed6ee5 | |||
| d5b0782700 | |||
| ebe717ff6c | |||
| 89c7c6e9dd | |||
| 9c6fd3ed06 | |||
| e564a0de46 | |||
| 17f1eadb6f | |||
| 09991f2492 | |||
| 15931532cd | |||
| aa9f445422 | |||
| b578ff1296 | |||
| cf9fc69d6b | |||
| 04321d0577 | |||
| 67c141f15b | |||
| ad4e0d9ad7 | |||
| c13dfc5144 | |||
| b7d53ac9a4 | |||
| f9884e08af | |||
| 9819cb3eb5 | |||
| 9b0929c392 | |||
| af21ebe6c6 | |||
| 0b446a94c5 | |||
| 5dfa61eb0b | |||
| b267547fb4 | |||
| e0f60dc09d | |||
| 8783075e40 | |||
| f2cdced501 | |||
| 33a0e65135 | |||
| e3fb0a39a2 | |||
| 57fb072215 | |||
| 75046bbea0 | |||
| 8e2eeb5364 | |||
| 556863d401 | |||
| 6a6a35a0b9 | |||
| 7da6e93597 | |||
| 963274a915 | |||
| d72df5ace4 | |||
| 767f59a3ae | |||
| da088e58c3 | |||
| 5502e61519 | |||
| ab9bea1d93 | |||
| e6397c8a38 | |||
| 1e47f31210 | |||
| 57b6f7e494 | |||
| d37ef61b30 | |||
| 3f148c2aaa | |||
| fa1ef1dc85 | |||
| 58872c79ee | |||
| 24f18275dd | |||
| 8f94c4e933 | |||
| 20c79e1481 | |||
| fb0aa5abb3 | |||
| 86a40716bd | |||
| 7f48d1b55a | |||
| fd84b0997a | |||
| 0e8838db90 | |||
| 94fc24b6fb | |||
| ab0d1c395d | |||
| 047f660572 | |||
| 22323072fd | |||
| a34b1af49e | |||
| 1ace42f245 | |||
| 0502d18e3d | |||
| c4c786596f | |||
| c3d123fef7 | |||
| e4b25ab480 | |||
| 1680502313 | |||
| e725e3a51b | |||
| 2fe75c1a24 | |||
| 9ebd1e8d64 | |||
| 64de6de725 | |||
| c3e8ff9ce7 | |||
| 5448e81021 | |||
| f7e07322ab | |||
| 37217a63e1 | |||
| ad5c636aff | |||
| ebdc82b86b | |||
| a12c9a0430 | |||
| 0d14c3bc1a | |||
| 8be542016b | |||
| 3a585f8c29 | |||
| c0e38a2f1e | |||
| 5de1460f88 | |||
| 312db36849 | |||
| 46875c91ce | |||
| 86305c45a9 | |||
| 1342837686 | |||
| 012b8f18c6 | |||
| 5ea310452c | |||
| a4956dfe26 | |||
| 0415975dd1 | |||
| afb8a0d1b1 | |||
| 48abd493fe | |||
| 3d571e8ab5 | |||
| c7cfdfc26c | |||
| 4d27aee7cf | |||
| 63f0beb209 | |||
| 8dbdfb0449 | |||
| d5fb5d029b | |||
| 3738a45658 | |||
| fd3e99c7ee | |||
| 4396801933 | |||
| a516ed8739 | |||
| 88cac6a4a3 | |||
| 11df955d88 | |||
| 77832a6da9 | |||
| ce1f6cf0bf | |||
| c702cd418e | |||
| 4bcc24372a | |||
| ae7cff35dc | |||
| 8282be682c | |||
| b81a291c85 | |||
| 3348e74ab8 | |||
| e2832ea596 | |||
| 74f58198fd | |||
| 42b16591ec | |||
| 62cf2f0a4f | |||
| 32e1a6452a | |||
| 55295faff0 | |||
| 254c3725e2 | |||
| d5ff232246 | |||
| a644245e0e | |||
| 697292ba0c | |||
| 5366ac84e6 | |||
| b71db12c43 | |||
| ab571e3562 | |||
| 5e42a6eb0a | |||
| 0bfddbc9cb | |||
| 511dab1ade | |||
| d4623a393b | |||
| c2c3be4ee3 | |||
| 270f8526d1 | |||
| 4171e35546 | |||
| bea505d32e | |||
| 5c28b56823 | |||
| d2b54e2611 | |||
| f8a58f2bc4 | |||
| f6e1fb3150 | |||
| 14c7b9bd08 | |||
| 11f3edc352 | |||
| fbd4463f20 | |||
| a3a94950f6 | |||
| b51a14ff92 | |||
| 929d5567b8 | |||
| beaa7ba0e3 | |||
| 1a839f5103 | |||
| 8df4f927fd | |||
| 5965917d16 | |||
| 57cfb94f1f | |||
| b5b80d0a07 | |||
| e5cfbae3fd | |||
| 5d2ca11315 | |||
| 858394b720 | |||
| 6e08c56076 | |||
| fb6d3f5b5c | |||
| 6fe5652783 | |||
| ca68bbc9ff | |||
| e7bcb48e57 | |||
| 433b1c480c | |||
| 0b08246760 | |||
| c7bdfbea4f | |||
| 618989d317 | |||
| 7fe78b745f | |||
| 98f152e8d5 | |||
| d74191427e | |||
| 4c69892f39 | |||
| c79ae0c842 | |||
| 46fc28fd1a | |||
| f8a1dac593 | |||
| 9712bb62fd | |||
| e85f6342a2 | |||
| 7199782d45 | |||
| 5ed4a14e2b | |||
| b27735d4a6 | |||
| dc5a773b2b | |||
| 2b6f3caff4 | |||
| 83eb8ac0ea | |||
| 9b38e43c17 | |||
| 284cb12f8f | |||
| 67644a28b6 | |||
| 1b57f2bda5 | |||
| a1a140f7d5 | |||
| 4caf3c8ce0 | |||
| 485c80fcf3 | |||
| aae46263cc | |||
| 39d4d2041a | |||
| 5ac97b16a8 | |||
| f457c17c78 | |||
| b0b2bbc857 | |||
| 69f47e7700 | |||
| 52af5c2b38 | |||
| 404661d5cb | |||
| f07a4587bb | |||
| 114d6a9256 | |||
| 73a18046c3 | |||
| c899af0e03 | |||
| 098c924e32 | |||
| 4289a64e24 | |||
| 7615762039 | |||
| d601f3196a | |||
| 92a6e7ddcd | |||
| c79474d269 | |||
| f4bc9ffb98 | |||
| e124e802a3 | |||
| 348cc01284 | |||
| 9f7a510f90 | |||
| f64397d822 | |||
| 918edaba2e | |||
| 5214bd3093 | |||
| 752ef6f456 | |||
| bea1980e22 | |||
| f3360e4ee2 | |||
| 8330bd424b | |||
| 507c9697fb | |||
| 52c073724d | |||
| de07ad47f1 | |||
| 93ea2e1df9 | |||
| a7bf14b364 | |||
| 7899fa3b0a | |||
| c1da2a4e73 | |||
| db53b618c1 | |||
| 913163884a | |||
| 4a555aaef1 | |||
| e9ada8d15c | |||
| 63d841664c | |||
| 9779d25d98 | |||
| 89d0c6b962 | |||
| 20da0835b3 | |||
| 4f232a9003 | |||
| c24a51323d | |||
| 69d022712b | |||
| aa1f3d1681 | |||
| b366cd352e | |||
| ac667a1e2b | |||
| a09f93768a | |||
| 0ccd2f8b87 | |||
| b1fdb148d0 | |||
| 955ca4d146 | |||
| d5732fed45 | |||
| 4c7b04e32a | |||
| 74ec10bf3a | |||
| dd6cb1b728 | |||
| 1e4c10cef1 | |||
| e47978e168 | |||
| 52e967a525 | |||
| 1de9d123a5 | |||
| ad60164911 | |||
| c49b0825f9 | |||
| fdec9f0adc | |||
| a88dbbec98 | |||
| 61d8774da0 | |||
| c2b16f1ffb | |||
| 6af94dffe5 | |||
| 2147175898 | |||
| 052542254a | |||
| 1bb723b9e0 | |||
| db02c96c55 | |||
| 164141a63e | |||
| 0a731413de | |||
| 6e5dd64025 | |||
| 650875468c | |||
| 6d2a25b877 | |||
| 2847621928 | |||
| 3afc6a5079 | |||
| 6c8fb6c204 | |||
| 8e9a553517 | |||
| 537d539b1d | |||
| 476b9cfc99 | |||
| 352087a637 | |||
| 33d33802a8 | |||
| 9ec937c843 | |||
| 8e5eb84da1 | |||
| 902f8dd1d8 | |||
| 22f8ea61fb | |||
| 8ce1f14554 | |||
| 9a3cb628af | |||
| b0a4c5d72c | |||
| 8b110c9971 | |||
| 21d3a7529c | |||
| 9c7b471634 | |||
| 45d45c2989 | |||
| 4ff18741fd | |||
| b7e99fbe30 | |||
| a27f2e994b | |||
| 284cf3ecf2 | |||
| cbf25b704d | |||
| 8391897a94 | |||
| a3c89092e1 | |||
| e77c426eeb |
@@ -3,6 +3,8 @@ name: Backport Bot
|
||||
on:
|
||||
issues:
|
||||
types: [labeled]
|
||||
pull_request:
|
||||
types: [labeled]
|
||||
push:
|
||||
branches:
|
||||
- '*.x'
|
||||
@@ -13,6 +15,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ spring-test/test-output/
|
||||
|
||||
# Maven artifacts
|
||||
pom.xml
|
||||
target/
|
||||
/target/
|
||||
|
||||
# Eclipse artifacts, including WTP generated manifests
|
||||
bin
|
||||
|
||||
@@ -30,5 +30,7 @@ Andy Clement <aclement@vmware.com> <andrew.clement@gmail.com>
|
||||
Sam Brannen <sbrannen@vmware.com>
|
||||
Sam Brannen <sbrannen@vmware.com> <sbrannen@pivotal.io>
|
||||
Sam Brannen <sbrannen@vmware.com> <sam@sambrannen.com>
|
||||
Simon Basle <sbasle@vmware.com>
|
||||
Simon Baslé <sbasle@vmware.com>
|
||||
<dmitry.katsubo@gmail.com> <dmitry.katsubo@gmai.com>
|
||||
Nick Williams <nicholas@nicholaswilliams.net>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# Enable auto-env through the sdkman_auto_env config
|
||||
# Add key=value pairs of SDKs to use below
|
||||
java=17.0.5-librca
|
||||
java=17.0.6-librca
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [](https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-5.3.x?groups=Build") [](https://ge.spring.io/scans?search.rootProjectNames=spring)
|
||||
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [](https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-6.0.x?groups=Build") [](https://ge.spring.io/scans?search.rootProjectNames=spring)
|
||||
|
||||
This is the home of the Spring Framework: the foundation for all [Spring projects](https://spring.io/projects). Collectively the Spring Framework and the family of Spring projects are often referred to simply as "Spring".
|
||||
|
||||
|
||||
+8
-8
@@ -1,5 +1,5 @@
|
||||
plugins {
|
||||
id 'io.spring.nohttp' version '0.0.10'
|
||||
id 'io.spring.nohttp' version '0.0.11'
|
||||
id 'io.freefair.aspectj' version '6.5.0.3' apply false
|
||||
// kotlinVersion is managed in gradle.properties
|
||||
id 'org.jetbrains.kotlin.plugin.serialization' version "${kotlinVersion}" apply false
|
||||
@@ -10,7 +10,7 @@ plugins {
|
||||
id 'com.github.ben-manes.versions' version '0.42.0'
|
||||
id 'com.github.johnrengelman.shadow' version '7.1.2' apply false
|
||||
id 'de.undercouch.download' version '5.1.0'
|
||||
id 'me.champeau.jmh' version '0.6.6' apply false
|
||||
id 'me.champeau.jmh' version '0.6.8' apply false
|
||||
}
|
||||
|
||||
ext {
|
||||
@@ -78,7 +78,7 @@ configure([rootProject] + javaProjects) { project ->
|
||||
}
|
||||
|
||||
checkstyle {
|
||||
toolVersion = "10.4"
|
||||
toolVersion = "10.7.0"
|
||||
configDirectory.set(rootProject.file("src/checkstyle"))
|
||||
}
|
||||
|
||||
@@ -121,18 +121,18 @@ configure([rootProject] + javaProjects) { project ->
|
||||
"https://docs.jboss.org/jbossas/javadoc/7.1.2.Final/",
|
||||
"https://www.eclipse.org/aspectj/doc/released/aspectj5rt-api/",
|
||||
"https://www.quartz-scheduler.org/api/2.3.0/",
|
||||
"https://fasterxml.github.io/jackson-core/javadoc/2.10/",
|
||||
"https://fasterxml.github.io/jackson-databind/javadoc/2.10/",
|
||||
"https://fasterxml.github.io/jackson-dataformat-xml/javadoc/2.10/",
|
||||
"https://www.javadoc.io/doc/com.fasterxml.jackson.core/jackson-core/2.14.1/",
|
||||
"https://www.javadoc.io/doc/com.fasterxml.jackson.core/jackson-databind/2.14.1/",
|
||||
"https://www.javadoc.io/doc/com.fasterxml.jackson.dataformat/jackson-dataformat-xml/2.14.1/",
|
||||
"https://hc.apache.org/httpcomponents-client-5.2.x/current/httpclient5/apidocs/",
|
||||
"https://projectreactor.io/docs/test/release/api/",
|
||||
"https://junit.org/junit4/javadoc/4.13.2/",
|
||||
// TODO Uncomment link to JUnit 5 docs once we have sorted out
|
||||
// the following warning in the build.
|
||||
//
|
||||
// warning: The code being documented uses packages in the unnamed module, but the packages defined in https://junit.org/junit5/docs/5.9.1/api/ are in named modules.
|
||||
// warning: The code being documented uses packages in the unnamed module, but the packages defined in https://junit.org/junit5/docs/5.9.2/api/ are in named modules.
|
||||
//
|
||||
// "https://junit.org/junit5/docs/5.9.1/api/",
|
||||
// "https://junit.org/junit5/docs/5.9.2/api/",
|
||||
"https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/",
|
||||
"https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/",
|
||||
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -40,7 +40,6 @@ class TestConventions {
|
||||
}
|
||||
|
||||
private void configureTestConventions(Project project) {
|
||||
project.getPlugins().apply(TestRetryPlugin.class);
|
||||
project.getTasks().withType(Test.class,
|
||||
(test) -> project.getPlugins().withType(TestRetryPlugin.class, (testRetryPlugin) -> {
|
||||
TestRetryTaskExtension testRetry = test.getExtensions().getByType(TestRetryTaskExtension.class);
|
||||
|
||||
@@ -17,4 +17,4 @@ changelog:
|
||||
- "type: dependency-upgrade"
|
||||
contributors:
|
||||
exclude:
|
||||
names: ["bclozel", "jhoeller", "poutsma", "rstoyanchev", "sbrannen", "sdeleuze", "snicoll"]
|
||||
names: ["bclozel", "jhoeller", "poutsma", "rstoyanchev", "sbrannen", "sdeleuze", "snicoll", "simonbasle"]
|
||||
|
||||
@@ -6,7 +6,6 @@ RUN ./setup.sh
|
||||
|
||||
ENV JAVA_HOME /opt/openjdk/java17
|
||||
ENV JDK17 /opt/openjdk/java17
|
||||
ENV JDK18 /opt/openjdk/java18
|
||||
ENV JDK19 /opt/openjdk/java19
|
||||
|
||||
ENV PATH $JAVA_HOME/bin:$PATH
|
||||
|
||||
@@ -3,13 +3,10 @@ set -e
|
||||
|
||||
case "$1" in
|
||||
java17)
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/17.0.5+8/bellsoft-jdk17.0.5+8-linux-amd64.tar.gz"
|
||||
;;
|
||||
java18)
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/18.0.2.1%2B1/bellsoft-jdk18.0.2.1+1-linux-amd64.tar.gz"
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/17.0.6+10/bellsoft-jdk17.0.6+10-linux-amd64.tar.gz"
|
||||
;;
|
||||
java19)
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/19.0.1%2B11/bellsoft-jdk19.0.1+11-linux-amd64.tar.gz"
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/19.0.2+9/bellsoft-jdk19.0.2+9-linux-amd64.tar.gz"
|
||||
;;
|
||||
*)
|
||||
echo $"Unknown java version"
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ curl https://raw.githubusercontent.com/spring-io/concourse-java-scripts/v0.0.4/c
|
||||
|
||||
mkdir -p /opt/openjdk
|
||||
pushd /opt/openjdk > /dev/null
|
||||
for jdk in java17 java18 java19
|
||||
for jdk in java17 java19
|
||||
do
|
||||
JDK_URL=$( /get-jdk-url.sh $jdk )
|
||||
mkdir $jdk
|
||||
|
||||
@@ -8,7 +8,4 @@ milestone: "6.0.x"
|
||||
build-name: "spring-framework"
|
||||
pipeline-name: "spring-framework"
|
||||
concourse-url: "https://ci.spring.io"
|
||||
registry-mirror-host: docker.repo.spring.io
|
||||
registry-mirror-username: ((artifactory-username))
|
||||
registry-mirror-password: ((artifactory-password))
|
||||
task-timeout: 1h00m
|
||||
|
||||
+14
-44
@@ -23,11 +23,6 @@ anchors:
|
||||
docker-resource-source: &docker-resource-source
|
||||
username: ((docker-hub-username))
|
||||
password: ((docker-hub-password))
|
||||
tag: ((milestone))
|
||||
registry-mirror-vars: ®istry-mirror-vars
|
||||
registry-mirror-host: ((registry-mirror-host))
|
||||
registry-mirror-username: ((registry-mirror-username))
|
||||
registry-mirror-password: ((registry-mirror-password))
|
||||
slack-fail-params: &slack-fail-params
|
||||
text: >
|
||||
:concourse-failed: <https://ci.spring.io/teams/${BUILD_TEAM_NAME}/pipelines/${BUILD_PIPELINE_NAME}/jobs/${BUILD_JOB_NAME}/builds/${BUILD_NAME}|${BUILD_PIPELINE_NAME} ${BUILD_JOB_NAME} failed!>
|
||||
@@ -48,31 +43,37 @@ resource_types:
|
||||
- name: registry-image
|
||||
type: registry-image
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: concourse/registry-image-resource
|
||||
tag: 1.5.0
|
||||
- name: artifactory-resource
|
||||
type: registry-image
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: springio/artifactory-resource
|
||||
tag: 0.0.17
|
||||
tag: 0.0.18
|
||||
- name: github-release
|
||||
type: registry-image
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: concourse/github-release-resource
|
||||
tag: 1.5.5
|
||||
- name: github-status-resource
|
||||
type: registry-image
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: dpb587/github-status-resource
|
||||
tag: master
|
||||
- name: pull-request
|
||||
type: registry-image
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: teliaoss/github-pr-resource
|
||||
tag: v0.23.0
|
||||
- name: slack-notification
|
||||
type: registry-image
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: cfcommunity/slack-notification-resource
|
||||
tag: latest
|
||||
resources:
|
||||
@@ -94,6 +95,7 @@ resources:
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: ((docker-hub-organization))/spring-framework-ci
|
||||
tag: ((milestone))
|
||||
- name: every-morning
|
||||
type: time
|
||||
icon: alarm
|
||||
@@ -125,14 +127,6 @@ resources:
|
||||
access_token: ((github-ci-status-token))
|
||||
branch: ((branch))
|
||||
context: build
|
||||
- name: repo-status-jdk18-build
|
||||
type: github-status-resource
|
||||
icon: eye-check-outline
|
||||
source:
|
||||
repository: ((github-repo-name))
|
||||
access_token: ((github-ci-status-token))
|
||||
branch: ((branch))
|
||||
context: jdk18-build
|
||||
- name: repo-status-jdk19-build
|
||||
type: github-status-resource
|
||||
icon: eye-check-outline
|
||||
@@ -176,7 +170,7 @@ jobs:
|
||||
image: ci-image
|
||||
vars:
|
||||
ci-image-name: ci-image
|
||||
<<: *registry-mirror-vars
|
||||
<<: *docker-resource-source
|
||||
- put: ci-image
|
||||
params:
|
||||
image: ci-image/image.tar
|
||||
@@ -237,34 +231,6 @@ jobs:
|
||||
"zip.type": "schema"
|
||||
get_params:
|
||||
threads: 8
|
||||
- name: jdk18-build
|
||||
serial: true
|
||||
public: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
- get: every-morning
|
||||
trigger: true
|
||||
- put: repo-status-jdk18-build
|
||||
params: { state: "pending", commit: "git-repo" }
|
||||
- do:
|
||||
- task: check-project
|
||||
image: ci-image
|
||||
file: git-repo/ci/tasks/check-project.yml
|
||||
privileged: true
|
||||
timeout: ((task-timeout))
|
||||
params:
|
||||
TEST_TOOLCHAIN: 18
|
||||
<<: *build-project-task-params
|
||||
on_failure:
|
||||
do:
|
||||
- put: repo-status-jdk18-build
|
||||
params: { state: "failure", commit: "git-repo" }
|
||||
- put: slack-alert
|
||||
params:
|
||||
<<: *slack-fail-params
|
||||
- put: repo-status-jdk18-build
|
||||
params: { state: "success", commit: "git-repo" }
|
||||
- name: jdk19-build
|
||||
serial: true
|
||||
public: true
|
||||
@@ -365,6 +331,7 @@ jobs:
|
||||
params:
|
||||
RELEASE_TYPE: M
|
||||
<<: *github-task-params
|
||||
<<: *docker-resource-source
|
||||
- put: github-pre-release
|
||||
params:
|
||||
<<: *changelog-task-params
|
||||
@@ -403,6 +370,7 @@ jobs:
|
||||
file: git-repo/ci/tasks/promote-version.yml
|
||||
params:
|
||||
RELEASE_TYPE: RC
|
||||
<<: *docker-resource-source
|
||||
<<: *artifactory-task-params
|
||||
- task: generate-changelog
|
||||
file: git-repo/ci/tasks/generate-changelog.yml
|
||||
@@ -447,6 +415,7 @@ jobs:
|
||||
file: git-repo/ci/tasks/promote-version.yml
|
||||
params:
|
||||
RELEASE_TYPE: RELEASE
|
||||
<<: *docker-resource-source
|
||||
<<: *artifactory-task-params
|
||||
<<: *sonatype-task-params
|
||||
- name: create-github-release
|
||||
@@ -464,6 +433,7 @@ jobs:
|
||||
file: git-repo/ci/tasks/generate-changelog.yml
|
||||
params:
|
||||
RELEASE_TYPE: RELEASE
|
||||
<<: *docker-resource-source
|
||||
<<: *github-task-params
|
||||
- put: github-release
|
||||
params:
|
||||
@@ -471,7 +441,7 @@ jobs:
|
||||
|
||||
groups:
|
||||
- name: "builds"
|
||||
jobs: ["build", "jdk18-build", "jdk19-build"]
|
||||
jobs: ["build", "jdk19-build"]
|
||||
- name: "releases"
|
||||
jobs: ["stage-milestone", "stage-rc", "stage-release", "promote-milestone", "promote-rc", "promote-release", "create-github-release"]
|
||||
- name: "ci-images"
|
||||
|
||||
@@ -4,6 +4,6 @@ set -e
|
||||
source $(dirname $0)/common.sh
|
||||
|
||||
pushd git-repo > /dev/null
|
||||
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Porg.gradle.java.installations.fromEnv=JDK17,JDK18 \
|
||||
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Porg.gradle.java.installations.fromEnv=JDK17,JDK19 \
|
||||
-PmainToolchain=${MAIN_TOOLCHAIN} -PtestToolchain=${TEST_TOOLCHAIN} --no-daemon --max-workers=4 check
|
||||
popd > /dev/null
|
||||
|
||||
@@ -5,10 +5,8 @@ image_resource:
|
||||
source:
|
||||
repository: concourse/oci-build-task
|
||||
tag: 0.10.0
|
||||
registry_mirror:
|
||||
host: ((registry-mirror-host))
|
||||
username: ((registry-mirror-username))
|
||||
password: ((registry-mirror-password))
|
||||
username: ((docker-hub-username))
|
||||
password: ((docker-hub-password))
|
||||
inputs:
|
||||
- name: ci-images-git-repo
|
||||
outputs:
|
||||
|
||||
@@ -5,6 +5,8 @@ image_resource:
|
||||
source:
|
||||
repository: springio/github-changelog-generator
|
||||
tag: '0.0.8'
|
||||
username: ((docker-hub-username))
|
||||
password: ((docker-hub-password))
|
||||
inputs:
|
||||
- name: git-repo
|
||||
- name: artifactory-repo
|
||||
|
||||
@@ -5,6 +5,8 @@ image_resource:
|
||||
source:
|
||||
repository: springio/concourse-release-scripts
|
||||
tag: '0.3.4'
|
||||
username: ((docker-hub-username))
|
||||
password: ((docker-hub-password))
|
||||
inputs:
|
||||
- name: git-repo
|
||||
- name: artifactory-repo
|
||||
|
||||
@@ -13,15 +13,12 @@ configurations {
|
||||
dependencies {
|
||||
api(project(":spring-context"))
|
||||
api(project(":spring-web"))
|
||||
api("jakarta.servlet:jakarta.servlet-api")
|
||||
|
||||
implementation(project(":spring-core-test"))
|
||||
implementation("org.assertj:assertj-core")
|
||||
}
|
||||
|
||||
checkstyle {
|
||||
sourceSets = []
|
||||
}
|
||||
|
||||
jar {
|
||||
enabled = false
|
||||
}
|
||||
@@ -31,13 +28,7 @@ javadoc {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
asciidoctorExtensions "io.spring.asciidoctor.backends:spring-asciidoctor-backends:0.0.3"
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url "https://repo.spring.io/release"
|
||||
}
|
||||
asciidoctorExtensions "io.spring.asciidoctor.backends:spring-asciidoctor-backends:0.0.5"
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,18 +107,23 @@ asciidoctor {
|
||||
sources {
|
||||
include '*.adoc'
|
||||
}
|
||||
outputDir "$buildDir/docs/ref-docs/html5"
|
||||
outputOptions {
|
||||
backends "spring-html"
|
||||
}
|
||||
logDocuments = true
|
||||
resources {
|
||||
from(sourceDir) {
|
||||
include 'images/*.png'
|
||||
}
|
||||
}
|
||||
outputDir "$buildDir/docs/ref-docs/html5"
|
||||
outputOptions {
|
||||
backends "spring-html"
|
||||
}
|
||||
forkOptions {
|
||||
jvmArgs += ["--add-opens", "java.base/sun.nio.ch=ALL-UNNAMED", "--add-opens", "java.base/java.io=ALL-UNNAMED"]
|
||||
}
|
||||
logDocuments = true
|
||||
}
|
||||
|
||||
asciidoctor.mustRunAfter "check"
|
||||
|
||||
/**
|
||||
* Generate the Spring Framework Reference documentation from "src/docs/asciidoc"
|
||||
* in "build/docs/ref-docs/pdf".
|
||||
@@ -247,7 +243,6 @@ task distZip(type: Zip, dependsOn: [docsZip, schemaZip]) {
|
||||
|
||||
distZip.mustRunAfter moduleProjects.check
|
||||
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
@@ -256,4 +251,4 @@ publishing {
|
||||
artifact distZip
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
:chomp: default headers packages
|
||||
// Spring Portfolio
|
||||
:docs-site: https://docs.spring.io
|
||||
:docs-spring-boot: {docs-site}/spring-boot/docs/current/reference
|
||||
:docs-spring-gemfire: {docs-site}/spring-gemfire/docs/current/reference
|
||||
:docs-spring-security: {docs-site}/spring-security/reference
|
||||
// spring-asciidoctor-backends Settings
|
||||
:chomp: default headers packages
|
||||
:fold: all
|
||||
// Spring Framework
|
||||
:docs-spring-framework: {docs-site}/spring-framework/docs/{spring-version}
|
||||
:api-spring-framework: {docs-spring-framework}/javadoc-api/org/springframework
|
||||
@@ -7,10 +13,6 @@
|
||||
:docs-kotlin: {docdir}/../../main/kotlin/org/springframework/docs
|
||||
:docs-resources: {docdir}/../../main/resources
|
||||
:spring-framework-main-code: https://github.com/spring-projects/spring-framework/tree/main
|
||||
// Spring portfolio Links
|
||||
:docs-spring-boot: {docs-site}/spring-boot/docs/current/reference
|
||||
:docs-spring-gemfire: {docs-site}/spring-gemfire/docs/current/reference
|
||||
:docs-spring-security: {docs-site}/spring-security/reference
|
||||
// Third-party Links
|
||||
:docs-graalvm: https://www.graalvm.org/22.3/reference-manual
|
||||
:gh-rsocket: https://github.com/rsocket
|
||||
|
||||
@@ -23,7 +23,7 @@ Spring's pointcut model enables pointcut reuse independent of advice types. You
|
||||
target different advice with the same pointcut.
|
||||
|
||||
The `org.springframework.aop.Pointcut` interface is the central interface, used to
|
||||
target advices to particular classes and methods. The complete interface follows:
|
||||
target advice to particular classes and methods. The complete interface follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -843,7 +843,7 @@ created by the implementation of the `getObject()` method in the `ProxyFactoryBe
|
||||
method creates an AOP proxy that wraps a target object.
|
||||
|
||||
One of the most important benefits of using a `ProxyFactoryBean` or another IoC-aware
|
||||
class to create AOP proxies is that advices and pointcuts can also be
|
||||
class to create AOP proxies is that advice and pointcuts can also be
|
||||
managed by IoC. This is a powerful feature, enabling certain approaches that are hard to
|
||||
achieve with other AOP frameworks. For example, an advice may itself reference
|
||||
application objects (besides the target, which should be available in any AOP
|
||||
@@ -901,7 +901,7 @@ to be applied. You can find an example of using this feature in <<aop-global-adv
|
||||
* singleton: Whether or not the factory should return a single object, no matter how
|
||||
often the `getObject()` method is called. Several `FactoryBean` implementations offer
|
||||
such a method. The default value is `true`. If you want to use stateful advice - for
|
||||
example, for stateful mixins - use prototype advices along with a singleton value of
|
||||
example, for stateful mixins - use prototype advice along with a singleton value of
|
||||
`false`.
|
||||
|
||||
|
||||
@@ -965,7 +965,7 @@ Consider a simple example of `ProxyFactoryBean` in action. This example involves
|
||||
the example.
|
||||
* An `Advisor` and an `Interceptor` used to provide advice.
|
||||
* An AOP proxy bean definition to specify the target object (the `personTarget` bean),
|
||||
the interfaces to proxy, and the advices to apply.
|
||||
the interfaces to proxy, and the advice to apply.
|
||||
|
||||
The following listing shows the example:
|
||||
|
||||
@@ -1100,12 +1100,15 @@ subclass is used to implement the Decorator pattern, weaving in the advice.
|
||||
CGLIB proxying should generally be transparent to users. However, there are some issues
|
||||
to consider:
|
||||
|
||||
* `Final` methods cannot be advised, as they cannot be overridden.
|
||||
* There is no need to add CGLIB to your classpath. As of Spring 3.2, CGLIB is repackaged
|
||||
and included in the spring-core JAR. In other words, CGLIB-based AOP works "`out of
|
||||
the box`", as do JDK dynamic proxies.
|
||||
* `final` classes cannot be proxied, because they cannot be extended.
|
||||
* `final` methods cannot be advised, because they cannot be overridden.
|
||||
* `private` methods cannot be advised, because they cannot be overridden.
|
||||
|
||||
There is little performance difference between CGLIB proxying and dynamic proxies.
|
||||
NOTE: There is no need to add CGLIB to your classpath. CGLIB is repackaged and included
|
||||
in the `spring-core` JAR. In other words, CGLIB-based AOP works "out of the box", as do
|
||||
JDK dynamic proxies.
|
||||
|
||||
There is little performance difference between CGLIB proxies and dynamic proxies.
|
||||
Performance should not be a decisive consideration in this case.
|
||||
|
||||
|
||||
@@ -1239,7 +1242,7 @@ The first step is to construct an object of type
|
||||
object, as in the preceding example, or specify the interfaces to be proxied in an alternate
|
||||
constructor.
|
||||
|
||||
You can add advices (with interceptors as a specialized kind of advice), advisors, or both
|
||||
You can add advice (with interceptors as a specialized kind of advice), advisors, or both
|
||||
and manipulate them for the life of the `ProxyFactory`. If you add an
|
||||
`IntroductionInterceptionAroundAdvisor`, you can cause the proxy to implement additional
|
||||
interfaces.
|
||||
@@ -1468,7 +1471,7 @@ Using this mechanism involves:
|
||||
|
||||
* Specifying a `DefaultAdvisorAutoProxyCreator` bean definition.
|
||||
* Specifying any number of advisors in the same or related contexts. Note that these
|
||||
must be advisors, not interceptors or other advices. This is necessary,
|
||||
must be advisors, not interceptors or other advice. This is necessary,
|
||||
because there must be a pointcut to evaluate, to check the eligibility of each advice
|
||||
to candidate bean definitions.
|
||||
|
||||
@@ -1731,7 +1734,7 @@ of target source, as the following example shows:
|
||||
|
||||
NOTE: `ThreadLocal` instances come with serious issues (potentially resulting in memory leaks) when
|
||||
incorrectly using them in multi-threaded and multi-classloader environments. You
|
||||
should always consider wrapping a threadlocal in some other class and never directly use
|
||||
should always consider wrapping a `ThreadLocal` in some other class and never directly use
|
||||
the `ThreadLocal` itself (except in the wrapper class). Also, you should
|
||||
always remember to correctly set and unset (where the latter simply involves a call to
|
||||
`ThreadLocal.set(null)`) the resource local to the thread. Unsetting should be done in
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,9 @@ Applying such optimizations early implies the following restrictions:
|
||||
* The classpath is fixed and fully defined at build time.
|
||||
* The beans defined in your application cannot change at runtime, meaning:
|
||||
** `@Profile`, in particular profile-specific configuration needs to be chosen at build time.
|
||||
** Environment properties that impact the presence of a bean (`@Conditional`) are only considered at build time.
|
||||
** `Environment` properties that impact the presence of a bean (`@Conditional`) are only considered at build time.
|
||||
* Bean definitions with instance suppliers (lambdas or method references) cannot be transformed ahead-of-time (see related https://github.com/spring-projects/spring-framework/issues/29555[spring-framework#29555] issue).
|
||||
* The return type of methods annotated with `@Bean` should be the most specific type possible (typically the concrete class, not an interface) in order to support proper type inference without invoking the corresponding `@Bean` method at build time.
|
||||
|
||||
When these restrictions are in place, it becomes possible to perform ahead-of-time processing at build time and generate additional assets.
|
||||
A Spring AOT processed application typically generates:
|
||||
@@ -72,7 +74,7 @@ These are:
|
||||
* `SmartInstantiationAwareBeanPostProcessor` implementations determine a more precise bean type if necessary.
|
||||
This makes sure to create any proxy that will be required at runtime.
|
||||
|
||||
One this part completes, the `BeanFactory` contains the bean definitions that are necessary for the application to run. It does not trigger bean instantiation but allows the AOT engine to inspect the beans that will be created at runtime.
|
||||
Once this part completes, the `BeanFactory` contains the bean definitions that are necessary for the application to run. It does not trigger bean instantiation but allows the AOT engine to inspect the beans that will be created at runtime.
|
||||
|
||||
[[core.aot.bean-factory-initialization-contributions]]
|
||||
== Bean Factory Initialization AOT Contributions
|
||||
|
||||
@@ -123,7 +123,7 @@ easy to do in Spring. You do not actually have to do anything or know anything a
|
||||
the Spring internals (or even about classes such as the `FieldRetrievingFactoryBean`).
|
||||
The following example enumeration shows how easy injecting an enum value is:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package jakarta.persistence;
|
||||
@@ -134,7 +134,7 @@ The following example enumeration shows how easy injecting an enum value is:
|
||||
EXTENDED
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package jakarta.persistence
|
||||
@@ -148,7 +148,7 @@ The following example enumeration shows how easy injecting an enum value is:
|
||||
|
||||
Now consider the following setter of type `PersistenceContextType` and the corresponding bean definition:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package example;
|
||||
@@ -162,7 +162,7 @@ Now consider the following setter of type `PersistenceContextType` and the corre
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package example
|
||||
@@ -796,7 +796,7 @@ element results in a single `SimpleDateFormat` bean definition). Spring features
|
||||
number of convenience classes that support this scenario. In the following example, we
|
||||
use the `NamespaceHandlerSupport` class:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package org.springframework.samples.xml;
|
||||
@@ -810,7 +810,7 @@ use the `NamespaceHandlerSupport` class:
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package org.springframework.samples.xml
|
||||
@@ -847,7 +847,7 @@ responsible for parsing one distinct top-level XML element defined in the schema
|
||||
the parser, we' have access to the XML element (and thus to its subelements, too) so that
|
||||
we can parse our custom XML content, as you can see in the following example:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package org.springframework.samples.xml;
|
||||
@@ -884,7 +884,7 @@ the basic grunt work of creating a single `BeanDefinition`.
|
||||
<2> We supply the `AbstractSingleBeanDefinitionParser` superclass with the type that our
|
||||
single `BeanDefinition` represents.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package org.springframework.samples.xml
|
||||
@@ -1056,7 +1056,7 @@ setter method for the `components` property. This makes it hard (or rather impos
|
||||
to configure a bean definition for the `Component` class by using setter injection.
|
||||
The following listing shows the `Component` class:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package com.foo;
|
||||
@@ -1069,7 +1069,7 @@ The following listing shows the `Component` class:
|
||||
private String name;
|
||||
private List<Component> components = new ArrayList<Component> ();
|
||||
|
||||
// mmm, there is no setter method for the 'components'
|
||||
// there is no setter method for the 'components'
|
||||
public void addComponent(Component component) {
|
||||
this.components.add(component);
|
||||
}
|
||||
@@ -1087,7 +1087,7 @@ The following listing shows the `Component` class:
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package com.foo
|
||||
@@ -1099,7 +1099,7 @@ The following listing shows the `Component` class:
|
||||
var name: String? = null
|
||||
private val components = ArrayList<Component>()
|
||||
|
||||
// mmm, there is no setter method for the 'components'
|
||||
// there is no setter method for the 'components'
|
||||
fun addComponent(component: Component) {
|
||||
this.components.add(component)
|
||||
}
|
||||
@@ -1114,7 +1114,7 @@ The typical solution to this issue is to create a custom `FactoryBean` that expo
|
||||
setter property for the `components` property. The following listing shows such a custom
|
||||
`FactoryBean`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package com.foo;
|
||||
@@ -1154,7 +1154,7 @@ setter property for the `components` property. The following listing shows such
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package com.foo
|
||||
@@ -1226,7 +1226,7 @@ listing shows:
|
||||
Again following <<core.appendix.xsd-custom-introduction, the process described earlier>>,
|
||||
we then create a custom `NamespaceHandler`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package com.foo;
|
||||
@@ -1240,7 +1240,7 @@ we then create a custom `NamespaceHandler`:
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package com.foo
|
||||
@@ -1259,7 +1259,7 @@ Next up is the custom `BeanDefinitionParser`. Remember that we are creating
|
||||
a `BeanDefinition` that describes a `ComponentFactoryBean`. The following
|
||||
listing shows our custom `BeanDefinitionParser` implementation:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package com.foo;
|
||||
@@ -1308,7 +1308,7 @@ listing shows our custom `BeanDefinitionParser` implementation:
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package com.foo
|
||||
@@ -1403,14 +1403,14 @@ the named JCache for us. We can also modify the existing `BeanDefinition` for th
|
||||
`'checkingAccountService'` so that it has a dependency on this new
|
||||
JCache-initializing `BeanDefinition`. The following listing shows our `JCacheInitializer`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package com.foo;
|
||||
|
||||
public class JCacheInitializer {
|
||||
|
||||
private String name;
|
||||
private final String name;
|
||||
|
||||
public JCacheInitializer(String name) {
|
||||
this.name = name;
|
||||
@@ -1421,7 +1421,7 @@ JCache-initializing `BeanDefinition`. The following listing shows our `JCacheIni
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package com.foo
|
||||
@@ -1453,7 +1453,7 @@ the XSD schema that describes the custom attribute, as follows:
|
||||
|
||||
Next, we need to create the associated `NamespaceHandler`, as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package com.foo;
|
||||
@@ -1469,7 +1469,7 @@ Next, we need to create the associated `NamespaceHandler`, as follows:
|
||||
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package com.foo
|
||||
@@ -1490,7 +1490,7 @@ Next, we need to create the parser. Note that, in this case, because we are goin
|
||||
an XML attribute, we write a `BeanDefinitionDecorator` rather than a `BeanDefinitionParser`.
|
||||
The following listing shows our `BeanDefinitionDecorator` implementation:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package com.foo;
|
||||
@@ -1544,7 +1544,7 @@ The following listing shows our `BeanDefinitionDecorator` implementation:
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package com.foo
|
||||
|
||||
@@ -524,15 +524,13 @@ container that hosts the bean. A bean usually has only one identifier. However,
|
||||
requires more than one, the extra ones can be considered aliases.
|
||||
|
||||
In XML-based configuration metadata, you use the `id` attribute, the `name` attribute, or
|
||||
both to specify the bean identifiers. The `id` attribute lets you specify
|
||||
exactly one id. Conventionally, these names are alphanumeric ('myBean',
|
||||
'someService', etc.), but they can contain special characters as well. If you want to
|
||||
introduce other aliases for the bean, you can also specify them in the `name`
|
||||
attribute, separated by a comma (`,`), semicolon (`;`), or white space. As a
|
||||
historical note, in versions prior to Spring 3.1, the `id` attribute was
|
||||
defined as an `xsd:ID` type, which constrained possible characters. As of 3.1,
|
||||
it is defined as an `xsd:string` type. Note that bean `id` uniqueness is still
|
||||
enforced by the container, though no longer by XML parsers.
|
||||
both to specify bean identifiers. The `id` attribute lets you specify exactly one `id`.
|
||||
Conventionally, these names are alphanumeric ('myBean', 'someService', etc.), but they
|
||||
can contain special characters as well. If you want to introduce other aliases for the
|
||||
bean, you can also specify them in the `name` attribute, separated by a comma (`,`),
|
||||
semicolon (`;`), or white space. Although the `id` attribute is defined as an
|
||||
`xsd:string` type, bean `id` uniqueness is enforced by the container, though not by XML
|
||||
parsers.
|
||||
|
||||
You are not required to supply a `name` or an `id` for a bean. If you do not supply a
|
||||
`name` or `id` explicitly, the container generates a unique name for that bean. However,
|
||||
@@ -605,7 +603,7 @@ creating a namespace), yet they refer to the same bean.
|
||||
|
||||
.Java-configuration
|
||||
****
|
||||
If you use Javaconfiguration, the `@Bean` annotation can be used to provide aliases.
|
||||
If you use Java Configuration, the `@Bean` annotation can be used to provide aliases.
|
||||
See <<beans-java-bean-annotation>> for details.
|
||||
****
|
||||
|
||||
@@ -951,7 +949,7 @@ order in which the constructor arguments are defined in a bean definition is the
|
||||
in which those arguments are supplied to the appropriate constructor when the bean is
|
||||
being instantiated. Consider the following class:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package x.y;
|
||||
@@ -963,7 +961,7 @@ being instantiated. Consider the following class:
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package x.y
|
||||
@@ -995,7 +993,7 @@ case with the preceding example). When a simple type is used, such as
|
||||
`<value>true</value>`, Spring cannot determine the type of the value, and so cannot match
|
||||
by type without help. Consider the following class:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package examples;
|
||||
@@ -1014,7 +1012,7 @@ by type without help. Consider the following class:
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package examples
|
||||
@@ -1079,7 +1077,7 @@ https://download.oracle.com/javase/8/docs/api/java/beans/ConstructorProperties.h
|
||||
JDK annotation to explicitly name your constructor arguments. The sample class would
|
||||
then have to look as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package examples;
|
||||
@@ -1095,7 +1093,7 @@ then have to look as follows:
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package examples
|
||||
@@ -2278,10 +2276,9 @@ and by <<beans-factory-client,making a `getBean("B")` call to the container>> as
|
||||
typically new) bean B instance every time bean A needs it. The following example
|
||||
shows this approach:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages",fold="none"]
|
||||
.Java
|
||||
----
|
||||
// a class that uses a stateful Command-style class to perform some processing
|
||||
package fiona.apple;
|
||||
|
||||
// Spring-API imports
|
||||
@@ -2289,6 +2286,10 @@ shows this approach:
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
|
||||
/**
|
||||
* A class that uses a stateful Command-style class to perform
|
||||
* some processing.
|
||||
*/
|
||||
public class CommandManager implements ApplicationContextAware {
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
@@ -2312,16 +2313,17 @@ shows this approach:
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages",fold="none"]
|
||||
.Kotlin
|
||||
----
|
||||
// a class that uses a stateful Command-style class to perform some processing
|
||||
package fiona.apple
|
||||
|
||||
// Spring-API imports
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.context.ApplicationContextAware
|
||||
|
||||
// A class that uses a stateful Command-style class to perform
|
||||
// some processing.
|
||||
class CommandManager : ApplicationContextAware {
|
||||
|
||||
private lateinit var applicationContext: ApplicationContext
|
||||
@@ -2384,7 +2386,7 @@ Spring container dynamically overrides the implementation of the `createCommand(
|
||||
method. The `CommandManager` class does not have any Spring dependencies, as
|
||||
the reworked example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages",fold="none"]
|
||||
.Java
|
||||
----
|
||||
package fiona.apple;
|
||||
@@ -2405,7 +2407,7 @@ the reworked example shows:
|
||||
protected abstract Command createCommand();
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages",fold="none"]
|
||||
.Kotlin
|
||||
----
|
||||
package fiona.apple
|
||||
@@ -2704,8 +2706,8 @@ The following table describes the supported scopes:
|
||||
the context of a web-aware Spring `ApplicationContext`.
|
||||
|===
|
||||
|
||||
NOTE: As of Spring 3.0, a thread scope is available but is not registered by default. For
|
||||
more information, see the documentation for
|
||||
NOTE: A thread scope is available but is not registered by default. For more information,
|
||||
see the documentation for
|
||||
{api-spring-framework}/context/support/SimpleThreadScope.html[`SimpleThreadScope`].
|
||||
For instructions on how to register this or any other custom scope, see
|
||||
<<beans-factory-scopes-custom-using>>.
|
||||
@@ -3981,7 +3983,7 @@ dependency type. The following table summarizes the most important `Aware` inter
|
||||
| <<aop-aj-ltw>>
|
||||
|
||||
| `MessageSourceAware`
|
||||
| Configured strategy for resolving messages (with support for parametrization and
|
||||
| Configured strategy for resolving messages (with support for parameterization and
|
||||
internationalization).
|
||||
| <<context-introduction>>
|
||||
|
||||
@@ -4216,7 +4218,7 @@ it is created by the container and prints the resulting string to the system con
|
||||
|
||||
The following listing shows the custom `BeanPostProcessor` implementation class definition:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package scripting;
|
||||
@@ -4236,9 +4238,11 @@ The following listing shows the custom `BeanPostProcessor` implementation class
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package scripting
|
||||
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor
|
||||
|
||||
class InstantiationTracingBeanPostProcessor : BeanPostProcessor {
|
||||
@@ -4590,26 +4594,24 @@ configuration becomes decentralized and harder to control.
|
||||
|
||||
No matter the choice, Spring can accommodate both styles and even mix them together.
|
||||
It is worth pointing out that through its <<beans-java, JavaConfig>> option, Spring lets
|
||||
annotations be used in a non-invasive way, without touching the target components
|
||||
source code and that, in terms of tooling, all configuration styles are supported by the
|
||||
https://spring.io/tools[Spring Tools for Eclipse].
|
||||
annotations be used in a non-invasive way, without touching the target components'
|
||||
source code and that, in terms of tooling, all configuration styles are supported by
|
||||
https://spring.io/tools[Spring Tools] for Eclipse, Visual Studio Code, and Theia.
|
||||
****
|
||||
|
||||
An alternative to XML setup is provided by annotation-based configuration, which relies
|
||||
on the bytecode metadata for wiring up components instead of angle-bracket declarations.
|
||||
Instead of using XML to describe a bean wiring, the developer moves the configuration
|
||||
into the component class itself by using annotations on the relevant class, method, or
|
||||
field declaration. As mentioned in <<beans-factory-extension-bpp-examples-aabpp>>, using
|
||||
a `BeanPostProcessor` in conjunction with annotations is a common means of extending the
|
||||
Spring IoC container. For example, Spring 2.5 introduced an annotation-based approach to
|
||||
drive Spring's dependency injection. Essentially, the <<beans-autowired-annotation,
|
||||
`@Autowired`>> annotation provides the same capabilities as described in
|
||||
<<beans-factory-autowire>> but with more fine-grained control and wider applicability.
|
||||
Spring 2.5 also added support for JSR-250 annotations, such as `@PostConstruct` and
|
||||
`@PreDestroy`. Spring 3.0 added support for JSR-330 (Dependency Injection for Java)
|
||||
annotations contained in the `jakarta.inject` package such as `@Inject` and `@Named`.
|
||||
Details about those annotations can be found in the <<beans-standard-annotations,
|
||||
relevant section>>.
|
||||
on bytecode metadata for wiring up components instead of XML declarations. Instead of
|
||||
using XML to describe a bean wiring, the developer moves the configuration into the
|
||||
component class itself by using annotations on the relevant class, method, or field
|
||||
declaration. As mentioned in <<beans-factory-extension-bpp-examples-aabpp>>, using a
|
||||
`BeanPostProcessor` in conjunction with annotations is a common means of extending the
|
||||
Spring IoC container. For example, the <<beans-autowired-annotation, `@Autowired`>>
|
||||
annotation provides the same capabilities as described in <<beans-factory-autowire>> but
|
||||
with more fine-grained control and wider applicability. In addition, Spring provides
|
||||
support for JSR-250 annotations, such as `@PostConstruct` and `@PreDestroy`, as well as
|
||||
support for JSR-330 (Dependency Injection for Java) annotations contained in the
|
||||
`jakarta.inject` package such as `@Inject` and `@Named`. Details about those annotations
|
||||
can be found in the <<beans-standard-annotations, relevant section>>.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
@@ -5020,7 +5022,7 @@ through Java 8's `java.util.Optional`, as the following example shows:
|
||||
|
||||
As of Spring Framework 5.0, you can also use a `@Nullable` annotation (of any kind
|
||||
in any package -- for example, `javax.annotation.Nullable` from JSR-305) or just leverage
|
||||
Kotlin builtin null-safety support:
|
||||
Kotlin built-in null-safety support:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
@@ -6237,7 +6239,7 @@ simply to be added to the application's classpath like any other library.
|
||||
Most examples in this chapter use XML to specify the configuration metadata that produces
|
||||
each `BeanDefinition` within the Spring container. The previous section
|
||||
(<<beans-annotation-config>>) demonstrates how to provide a lot of the configuration
|
||||
metadata through source-level annotations. Even in those examples, however, the "`base`"
|
||||
metadata through source-level annotations. Even in those examples, however, the "base"
|
||||
bean definitions are explicitly defined in the XML file, while the annotations drive only
|
||||
the dependency injection. This section describes an option for implicitly detecting the
|
||||
candidate components by scanning the classpath. Candidate components are classes that
|
||||
@@ -6249,10 +6251,9 @@ the container.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Starting with Spring 3.0, many features provided by the Spring JavaConfig project are
|
||||
part of the core Spring Framework. This allows you to define beans using Java rather
|
||||
than using the traditional XML files. Take a look at the `@Configuration`, `@Bean`,
|
||||
`@Import`, and `@DependsOn` annotations for examples of how to use these new features.
|
||||
You can define beans using Java rather than using XML files. Take a look at the
|
||||
`@Configuration`, `@Bean`, `@Import`, and `@DependsOn` annotations for examples of how to
|
||||
use these features.
|
||||
====
|
||||
|
||||
|
||||
@@ -6324,7 +6325,7 @@ the `@RestController` annotation from Spring MVC is composed of `@Controller` an
|
||||
In addition, composed annotations can optionally redeclare attributes from
|
||||
meta-annotations to allow customization. This can be particularly useful when you
|
||||
want to only expose a subset of the meta-annotation's attributes. For example, Spring's
|
||||
`@SessionScope` annotation hardcodes the scope name to `session` but still allows
|
||||
`@SessionScope` annotation hard codes the scope name to `session` but still allows
|
||||
customization of the `proxyMode`. The following listing shows the definition of the
|
||||
`SessionScope` annotation:
|
||||
|
||||
@@ -7172,15 +7173,15 @@ arrangement (as though no index were present at all) by setting `spring.index.ig
|
||||
[[beans-standard-annotations]]
|
||||
== Using JSR 330 Standard Annotations
|
||||
|
||||
Starting with Spring 3.0, Spring offers support for JSR-330 standard annotations
|
||||
(Dependency Injection). Those annotations are scanned in the same way as the Spring
|
||||
annotations. To use them, you need to have the relevant jars in your classpath.
|
||||
Spring offers support for JSR-330 standard annotations (Dependency Injection). Those
|
||||
annotations are scanned in the same way as the Spring annotations. To use them, you need
|
||||
to have the relevant jars in your classpath.
|
||||
|
||||
[NOTE]
|
||||
=====
|
||||
If you use Maven, the `jakarta.inject` artifact is available in the standard Maven
|
||||
repository (
|
||||
https://repo1.maven.org/maven2/jakarta/inject/jakarta.inject-api/2.0.0/[https://repo1.maven.org/maven2/jakarta/inject/jakarta.inject-api/2.0.0/]).
|
||||
https://repo.maven.apache.org/maven2/jakarta/inject/jakarta.inject-api/2.0.0/[https://repo.maven.apache.org/maven2/jakarta/inject/jakarta.inject-api/2.0.0/]).
|
||||
You can add the following dependency to your file pom.xml:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
@@ -7562,7 +7563,7 @@ The simplest possible `@Configuration` class reads as follows:
|
||||
public class AppConfig {
|
||||
|
||||
@Bean
|
||||
public MyService myService() {
|
||||
public MyServiceImpl myService() {
|
||||
return new MyServiceImpl();
|
||||
}
|
||||
}
|
||||
@@ -7574,7 +7575,7 @@ The simplest possible `@Configuration` class reads as follows:
|
||||
class AppConfig {
|
||||
|
||||
@Bean
|
||||
fun myService(): MyService {
|
||||
fun myService(): MyServiceImpl {
|
||||
return MyServiceImpl()
|
||||
}
|
||||
}
|
||||
@@ -8611,22 +8612,24 @@ about singletons here.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
As of Spring 3.2, it is no longer necessary to add CGLIB to your classpath because CGLIB
|
||||
classes have been repackaged under `org.springframework.cglib` and included directly
|
||||
within the spring-core JAR.
|
||||
It is not necessary to add CGLIB to your classpath because CGLIB classes are repackaged
|
||||
under the `org.springframework.cglib` package and included directly within the
|
||||
`spring-core` JAR.
|
||||
====
|
||||
|
||||
[TIP]
|
||||
====
|
||||
There are a few restrictions due to the fact that CGLIB dynamically adds features at
|
||||
startup-time. In particular, configuration classes must not be final. However, as
|
||||
of 4.3, any constructors are allowed on configuration classes, including the use of
|
||||
`@Autowired` or a single non-default constructor declaration for default injection.
|
||||
startup-time. In particular, configuration classes must not be final. However, any
|
||||
constructors are allowed on configuration classes, including the use of `@Autowired` or a
|
||||
single non-default constructor declaration for default injection.
|
||||
|
||||
If you prefer to avoid any CGLIB-imposed limitations, consider declaring your `@Bean`
|
||||
methods on non-`@Configuration` classes (for example, on plain `@Component` classes instead).
|
||||
Cross-method calls between `@Bean` methods are not then intercepted, so you have
|
||||
to exclusively rely on dependency injection at the constructor or method level there.
|
||||
methods on non-`@Configuration` classes (for example, on plain `@Component` classes
|
||||
instead) or by annotating your configuration class with
|
||||
`@Configuration(proxyBeanMethods = false)`. Cross-method calls between `@Bean` methods
|
||||
are then not intercepted, so you have to exclusively rely on dependency injection at the
|
||||
constructor or method level there.
|
||||
====
|
||||
|
||||
|
||||
|
||||
@@ -744,25 +744,42 @@ topics:
|
||||
[[expressions-ref-literal]]
|
||||
=== Literal Expressions
|
||||
|
||||
The types of literal expressions supported are strings, numeric values (int, real, hex),
|
||||
boolean, and null. Strings are delimited by single quotation marks. To put a single quotation mark itself
|
||||
in a string, use two single quotation mark characters.
|
||||
SpEL supports the following types of literal expressions.
|
||||
|
||||
The following listing shows simple usage of literals. Typically, they are not used
|
||||
in isolation like this but, rather, as part of a more complex expression -- for example,
|
||||
using a literal on one side of a logical comparison operator.
|
||||
- strings
|
||||
- numeric values: integer (`int` or `long`), hexadecimal (`int` or `long`), real (`float`
|
||||
or `double`)
|
||||
- boolean values: `true` or `false`
|
||||
- null
|
||||
|
||||
Strings can delimited by single quotation marks (`'`) or double quotation marks (`"`). To
|
||||
include a single quotation mark within a string literal enclosed in single quotation
|
||||
marks, use two adjacent single quotation mark characters. Similarly, to include a double
|
||||
quotation mark within a string literal enclosed in double quotation marks, use two
|
||||
adjacent double quotation mark characters.
|
||||
|
||||
Numbers support the use of the negative sign, exponential notation, and decimal points.
|
||||
By default, real numbers are parsed by using `Double.parseDouble()`.
|
||||
|
||||
The following listing shows simple usage of literals. Typically, they are not used in
|
||||
isolation like this but, rather, as part of a more complex expression -- for example,
|
||||
using a literal on one side of a logical comparison operator or as an argument to a
|
||||
method.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
|
||||
// evals to "Hello World"
|
||||
// evaluates to "Hello World"
|
||||
String helloWorld = (String) parser.parseExpression("'Hello World'").getValue();
|
||||
|
||||
// evaluates to "Tony's Pizza"
|
||||
String pizzaParlor = (String) parser.parseExpression("'Tony''s Pizza'").getValue();
|
||||
|
||||
double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue();
|
||||
|
||||
// evals to 2147483647
|
||||
// evaluates to 2147483647
|
||||
int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue();
|
||||
|
||||
boolean trueValue = (Boolean) parser.parseExpression("true").getValue();
|
||||
@@ -774,12 +791,15 @@ using a literal on one side of a logical comparison operator.
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
|
||||
// evals to "Hello World"
|
||||
// evaluates to "Hello World"
|
||||
val helloWorld = parser.parseExpression("'Hello World'").value as String
|
||||
|
||||
// evaluates to "Tony's Pizza"
|
||||
val pizzaParlor = parser.parseExpression("'Tony''s Pizza'").value as String
|
||||
|
||||
val avogadrosNumber = parser.parseExpression("6.0221415E+23").value as Double
|
||||
|
||||
// evals to 2147483647
|
||||
// evaluates to 2147483647
|
||||
val maxValue = parser.parseExpression("0x7FFFFFFF").value as Int
|
||||
|
||||
val trueValue = parser.parseExpression("true").value as Boolean
|
||||
@@ -787,9 +807,6 @@ using a literal on one side of a logical comparison operator.
|
||||
val nullValue = parser.parseExpression("null").value
|
||||
----
|
||||
|
||||
Numbers support the use of the negative sign, exponential notation, and decimal points.
|
||||
By default, real numbers are parsed by using `Double.parseDouble()`.
|
||||
|
||||
|
||||
|
||||
[[expressions-properties-arrays]]
|
||||
@@ -804,7 +821,7 @@ Pupin's city of birth, we use the following expressions:
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
// evals to 1856
|
||||
// evaluates to 1856
|
||||
int year = (Integer) parser.parseExpression("birthdate.year + 1900").getValue(context);
|
||||
|
||||
String city = (String) parser.parseExpression("placeOfBirth.city").getValue(context);
|
||||
@@ -812,7 +829,7 @@ Pupin's city of birth, we use the following expressions:
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
// evals to 1856
|
||||
// evaluates to 1856
|
||||
val year = parser.parseExpression("birthdate.year + 1900").getValue(context) as Int
|
||||
|
||||
val city = parser.parseExpression("placeOfBirth.city").getValue(context) as String
|
||||
@@ -1961,7 +1978,7 @@ The definition of `TemplateParserContext` follows:
|
||||
|
||||
This section lists the classes used in the examples throughout this chapter.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Inventor.Java
|
||||
----
|
||||
package org.spring.samples.spel.inventor;
|
||||
@@ -2034,18 +2051,20 @@ This section lists the classes used in the examples throughout this chapter.
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Inventor.kt
|
||||
----
|
||||
class Inventor(
|
||||
var name: String,
|
||||
var nationality: String,
|
||||
var inventions: Array<String>? = null,
|
||||
var birthdate: Date = GregorianCalendar().time,
|
||||
var placeOfBirth: PlaceOfBirth? = null)
|
||||
package org.spring.samples.spel.inventor
|
||||
|
||||
class Inventor(
|
||||
var name: String,
|
||||
var nationality: String,
|
||||
var inventions: Array<String>? = null,
|
||||
var birthdate: Date = GregorianCalendar().time,
|
||||
var placeOfBirth: PlaceOfBirth? = null)
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.PlaceOfBirth.java
|
||||
----
|
||||
package org.spring.samples.spel.inventor;
|
||||
@@ -2081,13 +2100,15 @@ class Inventor(
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.PlaceOfBirth.kt
|
||||
----
|
||||
package org.spring.samples.spel.inventor
|
||||
|
||||
class PlaceOfBirth(var city: String, var country: String? = null) {
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Society.java
|
||||
----
|
||||
package org.spring.samples.spel.inventor;
|
||||
@@ -2130,7 +2151,7 @@ class Inventor(
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Society.kt
|
||||
----
|
||||
package org.spring.samples.spel.inventor
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
= Validation, Data Binding, and Type Conversion
|
||||
|
||||
There are pros and cons for considering validation as business logic, and Spring offers
|
||||
a design for validation (and data binding) that does not exclude either one of them.
|
||||
a design for validation and data binding that does not exclude either one of them.
|
||||
Specifically, validation should not be tied to the web tier and should be easy to localize,
|
||||
and it should be possible to plug in any available validator. Considering these concerns,
|
||||
Spring provides a `Validator` contract that is both basic and eminently usable
|
||||
@@ -15,18 +15,18 @@ provides the aptly named `DataBinder` to do exactly that. The `Validator` and th
|
||||
limited to the web layer.
|
||||
|
||||
The `BeanWrapper` is a fundamental concept in the Spring Framework and is used in a lot
|
||||
of places. However, you probably do not need to use the `BeanWrapper`
|
||||
directly. Because this is reference documentation, however, we felt that some explanation
|
||||
might be in order. We explain the `BeanWrapper` in this chapter, since, if you are
|
||||
going to use it at all, you are most likely do so when trying to bind data to objects.
|
||||
of places. However, you probably do not need to use the `BeanWrapper` directly. Because
|
||||
this is reference documentation, however, we feel that some explanation might be in
|
||||
order. We explain the `BeanWrapper` in this chapter, since, if you are going to use it at
|
||||
all, you are most likely do so when trying to bind data to objects.
|
||||
|
||||
Spring's `DataBinder` and the lower-level `BeanWrapper` both use `PropertyEditorSupport`
|
||||
implementations to parse and format property values. The `PropertyEditor` and
|
||||
`PropertyEditorSupport` types are part of the JavaBeans specification and are also
|
||||
explained in this chapter. Spring 3 introduced a `core.convert` package that provides a
|
||||
general type conversion facility, as well as a higher-level "`format`" package for
|
||||
formatting UI field values. You can use these packages as simpler alternatives to
|
||||
`PropertyEditorSupport` implementations. They are also discussed in this chapter.
|
||||
explained in this chapter. Spring's `core.convert` package provides a general type
|
||||
conversion facility, as well as a higher-level `format` package for formatting UI field
|
||||
values. You can use these packages as simpler alternatives to `PropertyEditorSupport`
|
||||
implementations. They are also discussed in this chapter.
|
||||
|
||||
Spring supports Java Bean Validation through setup infrastructure and an adaptor to
|
||||
Spring's own `Validator` contract. Applications can enable Bean Validation once globally,
|
||||
@@ -640,7 +640,7 @@ support for additional `PropertyEditor` instances to an `ApplicationContext`.
|
||||
Consider the following example, which defines a user class called `ExoticType` and
|
||||
another class called `DependsOnExoticType`, which needs `ExoticType` set as a property:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package example;
|
||||
@@ -663,7 +663,7 @@ another class called `DependsOnExoticType`, which needs `ExoticType` set as a pr
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package example
|
||||
@@ -689,12 +689,14 @@ string, which a `PropertyEditor` converts into an actual
|
||||
|
||||
The `PropertyEditor` implementation could look similar to the following:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
// converts string representation to ExoticType object
|
||||
package example;
|
||||
|
||||
import java.beans.PropertyEditorSupport;
|
||||
|
||||
// converts string representation to ExoticType object
|
||||
public class ExoticTypeEditor extends PropertyEditorSupport {
|
||||
|
||||
public void setAsText(String text) {
|
||||
@@ -702,14 +704,14 @@ The `PropertyEditor` implementation could look similar to the following:
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
// converts string representation to ExoticType object
|
||||
package example
|
||||
|
||||
import java.beans.PropertyEditorSupport
|
||||
|
||||
// converts string representation to ExoticType object
|
||||
class ExoticTypeEditor : PropertyEditorSupport() {
|
||||
|
||||
override fun setAsText(text: String) {
|
||||
@@ -752,7 +754,7 @@ instances for each bean creation attempt.
|
||||
|
||||
The following example shows how to create your own `PropertyEditorRegistrar` implementation:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package com.foo.editors.spring;
|
||||
@@ -768,7 +770,7 @@ The following example shows how to create your own `PropertyEditorRegistrar` imp
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package com.foo.editors.spring
|
||||
@@ -861,12 +863,12 @@ as needed.
|
||||
[[core-convert]]
|
||||
== Spring Type Conversion
|
||||
|
||||
Spring 3 introduced a `core.convert` package that provides a general type conversion
|
||||
system. The system defines an SPI to implement type conversion logic and an API
|
||||
to perform type conversions at runtime. Within a Spring container, you can use this system
|
||||
as an alternative to `PropertyEditor` implementations to convert externalized bean property value
|
||||
strings to the required property types. You can also use the public API anywhere in your
|
||||
application where type conversion is needed.
|
||||
The `core.convert` package provides a general type conversion system. The system defines
|
||||
an SPI to implement type conversion logic and an API to perform type conversions at
|
||||
runtime. Within a Spring container, you can use this system as an alternative to
|
||||
`PropertyEditor` implementations to convert externalized bean property value strings to
|
||||
the required property types. You can also use the public API anywhere in your application
|
||||
where type conversion is needed.
|
||||
|
||||
|
||||
|
||||
@@ -876,7 +878,7 @@ application where type conversion is needed.
|
||||
The SPI to implement type conversion logic is simple and strongly typed, as the following
|
||||
interface definition shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.core.convert.converter;
|
||||
|
||||
@@ -901,7 +903,7 @@ Several converter implementations are provided in the `core.convert.support` pac
|
||||
a convenience. These include converters from strings to numbers and other common types.
|
||||
The following listing shows the `StringToInteger` class, which is a typical `Converter` implementation:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.core.convert.support;
|
||||
|
||||
@@ -922,7 +924,7 @@ When you need to centralize the conversion logic for an entire class hierarchy
|
||||
(for example, when converting from `String` to `Enum` objects), you can implement
|
||||
`ConverterFactory`, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.core.convert.converter;
|
||||
|
||||
@@ -938,7 +940,7 @@ where T is a subclass of R.
|
||||
|
||||
Consider the `StringToEnumConverterFactory` as an example:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.core.convert.support;
|
||||
|
||||
@@ -975,7 +977,7 @@ context that you can use when you implement your conversion logic. Such context
|
||||
type conversion be driven by a field annotation or by generic information declared on a
|
||||
field signature. The following listing shows the interface definition of `GenericConverter`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.core.convert.converter;
|
||||
|
||||
@@ -1039,7 +1041,7 @@ might match only if the target entity type declares a static finder method (for
|
||||
`ConversionService` defines a unified API for executing type conversion logic at
|
||||
runtime. Converters are often run behind the following facade interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.core.convert;
|
||||
|
||||
@@ -1205,8 +1207,9 @@ web or desktop application. In such environments, you typically convert from `St
|
||||
to support the client postback process, as well as back to `String` to support the
|
||||
view rendering process. In addition, you often need to localize `String` values. The more
|
||||
general `core.convert` `Converter` SPI does not address such formatting requirements
|
||||
directly. To directly address them, Spring 3 introduced a convenient `Formatter` SPI that
|
||||
provides a simple and robust alternative to `PropertyEditor` implementations for client environments.
|
||||
directly. To directly address them, Spring provides a convenient `Formatter` SPI that
|
||||
provides a simple and robust alternative to `PropertyEditor` implementations for client
|
||||
environments.
|
||||
|
||||
In general, you can use the `Converter` SPI when you need to implement general-purpose type
|
||||
conversion logic -- for example, for converting between a `java.util.Date` and a `Long`.
|
||||
@@ -1222,7 +1225,7 @@ provides a unified type conversion API for both SPIs.
|
||||
The `Formatter` SPI to implement field formatting logic is simple and strongly typed. The
|
||||
following listing shows the `Formatter` interface definition:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.format;
|
||||
|
||||
@@ -1267,7 +1270,7 @@ a `java.text.DateFormat`.
|
||||
|
||||
The following `DateFormatter` is an example `Formatter` implementation:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package org.springframework.format.datetime;
|
||||
@@ -1301,7 +1304,7 @@ The following `DateFormatter` is an example `Formatter` implementation:
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
class DateFormatter(private val pattern: String) : Formatter<Date> {
|
||||
@@ -1333,7 +1336,7 @@ Field formatting can be configured by field type or annotation. To bind
|
||||
an annotation to a `Formatter`, implement `AnnotationFormatterFactory`. The following
|
||||
listing shows the definition of the `AnnotationFormatterFactory` interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.format;
|
||||
|
||||
@@ -1349,15 +1352,14 @@ listing shows the definition of the `AnnotationFormatterFactory` interface:
|
||||
|
||||
To create an implementation:
|
||||
|
||||
. Parameterize A to be the field `annotationType` with which you wish to associate
|
||||
. Parameterize `A` to be the field `annotationType` with which you wish to associate
|
||||
formatting logic -- for example `org.springframework.format.annotation.DateTimeFormat`.
|
||||
. Have `getFieldTypes()` return the types of fields on which the annotation can be used.
|
||||
. Have `getPrinter()` return a `Printer` to print the value of an annotated field.
|
||||
. Have `getParser()` return a `Parser` to parse a `clientValue` for an annotated field.
|
||||
|
||||
The following example `AnnotationFormatterFactory` implementation binds the `@NumberFormat`
|
||||
annotation to a formatter to let a number style or pattern be
|
||||
specified:
|
||||
annotation to a formatter to let a number style or pattern be specified:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
@@ -1365,10 +1367,12 @@ specified:
|
||||
public final class NumberFormatAnnotationFormatterFactory
|
||||
implements AnnotationFormatterFactory<NumberFormat> {
|
||||
|
||||
private static final Set<Class<?>> FIELD_TYPES = Set.of(Short.class,
|
||||
Integer.class, Long.class, Float.class, Double.class,
|
||||
BigDecimal.class, BigInteger.class);
|
||||
|
||||
public Set<Class<?>> getFieldTypes() {
|
||||
return new HashSet<Class<?>>(asList(new Class<?>[] {
|
||||
Short.class, Integer.class, Long.class, Float.class,
|
||||
Double.class, BigDecimal.class, BigInteger.class }));
|
||||
return FIELD_TYPES;
|
||||
}
|
||||
|
||||
public Printer<Number> getPrinter(NumberFormat annotation, Class<?> fieldType) {
|
||||
@@ -1382,16 +1386,13 @@ specified:
|
||||
private Formatter<Number> configureFormatterFrom(NumberFormat annotation, Class<?> fieldType) {
|
||||
if (!annotation.pattern().isEmpty()) {
|
||||
return new NumberStyleFormatter(annotation.pattern());
|
||||
} else {
|
||||
Style style = annotation.style();
|
||||
if (style == Style.PERCENT) {
|
||||
return new PercentStyleFormatter();
|
||||
} else if (style == Style.CURRENCY) {
|
||||
return new CurrencyStyleFormatter();
|
||||
} else {
|
||||
return new NumberStyleFormatter();
|
||||
}
|
||||
}
|
||||
// else
|
||||
return switch(annotation.style()) {
|
||||
case Style.PERCENT -> new PercentStyleFormatter();
|
||||
case Style.CURRENCY -> new CurrencyStyleFormatter();
|
||||
default -> new NumberStyleFormatter();
|
||||
};
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -1427,7 +1428,7 @@ specified:
|
||||
}
|
||||
----
|
||||
|
||||
To trigger formatting, you can annotate fields with @NumberFormat, as the following
|
||||
To trigger formatting, you can annotate fields with `@NumberFormat`, as the following
|
||||
example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
@@ -1489,7 +1490,7 @@ for use with Spring's `DataBinder` and the Spring Expression Language (SpEL).
|
||||
|
||||
The following listing shows the `FormatterRegistry` SPI:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.format;
|
||||
|
||||
@@ -1525,7 +1526,7 @@ these rules once, and they are applied whenever formatting is needed.
|
||||
`FormatterRegistrar` is an SPI for registering formatters and converters through the
|
||||
FormatterRegistry. The following listing shows its interface definition:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.format;
|
||||
|
||||
@@ -1577,20 +1578,22 @@ For example, the following Java configuration registers a global `yyyyMMdd` form
|
||||
public FormattingConversionService conversionService() {
|
||||
|
||||
// Use the DefaultFormattingConversionService but do not register defaults
|
||||
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false);
|
||||
DefaultFormattingConversionService conversionService =
|
||||
new DefaultFormattingConversionService(false);
|
||||
|
||||
// Ensure @NumberFormat is still supported
|
||||
conversionService.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());
|
||||
conversionService.addFormatterForFieldAnnotation(
|
||||
new NumberFormatAnnotationFormatterFactory());
|
||||
|
||||
// Register JSR-310 date conversion with a specific global format
|
||||
DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
|
||||
registrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"));
|
||||
registrar.registerFormatters(conversionService);
|
||||
DateTimeFormatterRegistrar dateTimeRegistrar = new DateTimeFormatterRegistrar();
|
||||
dateTimeRegistrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"));
|
||||
dateTimeRegistrar.registerFormatters(conversionService);
|
||||
|
||||
// Register date conversion with a specific global format
|
||||
DateFormatterRegistrar registrar = new DateFormatterRegistrar();
|
||||
registrar.setFormatter(new DateFormatter("yyyyMMdd"));
|
||||
registrar.registerFormatters(conversionService);
|
||||
DateFormatterRegistrar dateRegistrar = new DateFormatterRegistrar();
|
||||
dateRegistrar.setFormatter(new DateFormatter("yyyyMMdd"));
|
||||
dateRegistrar.registerFormatters(conversionService);
|
||||
|
||||
return conversionService;
|
||||
}
|
||||
@@ -1611,14 +1614,14 @@ For example, the following Java configuration registers a global `yyyyMMdd` form
|
||||
addFormatterForFieldAnnotation(NumberFormatAnnotationFormatterFactory())
|
||||
|
||||
// Register JSR-310 date conversion with a specific global format
|
||||
val registrar = DateTimeFormatterRegistrar()
|
||||
registrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"))
|
||||
registrar.registerFormatters(this)
|
||||
val dateTimeRegistrar = DateTimeFormatterRegistrar()
|
||||
dateTimeRegistrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"))
|
||||
dateTimeRegistrar.registerFormatters(this)
|
||||
|
||||
// Register date conversion with a specific global format
|
||||
val registrar = DateFormatterRegistrar()
|
||||
registrar.setFormatter(DateFormatter("yyyyMMdd"))
|
||||
registrar.registerFormatters(this)
|
||||
val dateRegistrar = DateFormatterRegistrar()
|
||||
dateRegistrar.setFormatter(DateFormatter("yyyyMMdd"))
|
||||
dateRegistrar.registerFormatters(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1956,9 +1959,9 @@ javadoc for more information on these options.
|
||||
[[validation-binder]]
|
||||
=== Configuring a `DataBinder`
|
||||
|
||||
Since Spring 3, you can configure a `DataBinder` instance with a `Validator`. Once
|
||||
configured, you can invoke the `Validator` by calling `binder.validate()`. Any validation
|
||||
`Errors` are automatically added to the binder's `BindingResult`.
|
||||
You can configure a `DataBinder` instance with a `Validator`. Once configured, you can
|
||||
invoke the `Validator` by calling `binder.validate()`. Any validation `Errors` are
|
||||
automatically added to the binder's `BindingResult`.
|
||||
|
||||
The following example shows how to use a `DataBinder` programmatically to invoke validation
|
||||
logic after binding to a target object:
|
||||
|
||||
@@ -129,7 +129,7 @@ Typically, you need an application server's JTA capability only if your applicat
|
||||
to handle transactions across multiple resources, which is not a requirement for many
|
||||
applications. Many high-end applications use a single, highly scalable database (such as
|
||||
Oracle RAC) instead. Stand-alone transaction managers (such as
|
||||
https://www.atomikos.com/[Atomikos Transactions] and https://jotm.ow2.org/[JOTM])
|
||||
https://www.atomikos.com/[Atomikos Transactions])
|
||||
are other options. Of course, you may need other application server capabilities, such as
|
||||
Java Message Service (JMS) and Jakarta EE Connector Architecture (JCA).
|
||||
|
||||
@@ -622,7 +622,7 @@ transactions being created and then rolled back in response to the
|
||||
`UnsupportedOperationException` instance. The following listing shows the `FooService`
|
||||
interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
// the service interface that we want to make transactional
|
||||
@@ -641,7 +641,7 @@ interface:
|
||||
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
// the service interface that we want to make transactional
|
||||
@@ -662,7 +662,7 @@ interface:
|
||||
|
||||
The following example shows an implementation of the preceding interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package x.y.service;
|
||||
@@ -690,7 +690,7 @@ The following example shows an implementation of the preceding interface:
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package x.y.service
|
||||
@@ -893,7 +893,7 @@ return type is reactive.
|
||||
The following listing shows a modified version of the previously used `FooService`, but
|
||||
this time the code uses reactive types:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
// the reactive service interface that we want to make transactional
|
||||
@@ -912,7 +912,7 @@ this time the code uses reactive types:
|
||||
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
// the reactive service interface that we want to make transactional
|
||||
@@ -933,7 +933,7 @@ this time the code uses reactive types:
|
||||
|
||||
The following example shows an implementation of the preceding interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package x.y.service;
|
||||
@@ -961,7 +961,7 @@ The following example shows an implementation of the preceding interface:
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package x.y.service
|
||||
@@ -1090,8 +1090,8 @@ application-specific `Exception` type by supplying an _exception pattern_ via th
|
||||
----
|
||||
<tx:advice id="txAdvice" transaction-manager="txManager">
|
||||
<tx:attributes>
|
||||
<tx:method name="get*" read-only="true" rollback-for="NoProductInStockException"/>
|
||||
<tx:method name="*"/>
|
||||
<tx:method name="get*" read-only="true" rollback-for="NoProductInStockException"/>
|
||||
<tx:method name="*"/>
|
||||
</tx:attributes>
|
||||
</tx:advice>
|
||||
----
|
||||
@@ -1105,8 +1105,8 @@ unhandled `InstrumentNotFoundException`:
|
||||
----
|
||||
<tx:advice id="txAdvice">
|
||||
<tx:attributes>
|
||||
<tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>
|
||||
<tx:method name="*"/>
|
||||
<tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>
|
||||
<tx:method name="*"/>
|
||||
</tx:attributes>
|
||||
</tx:advice>
|
||||
----
|
||||
@@ -1121,7 +1121,7 @@ attendant transaction:
|
||||
----
|
||||
<tx:advice id="txAdvice">
|
||||
<tx:attributes>
|
||||
<tx:method name="*" rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException"/>
|
||||
<tx:method name="*" rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException"/>
|
||||
</tx:attributes>
|
||||
</tx:advice>
|
||||
----
|
||||
@@ -2028,7 +2028,7 @@ configuration and AOP in general.
|
||||
|
||||
The following code shows the simple profiling aspect discussed earlier:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package x.y;
|
||||
@@ -2065,9 +2065,15 @@ The following code shows the simple profiling aspect discussed earlier:
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package x.y
|
||||
|
||||
import org.aspectj.lang.ProceedingJoinPoint
|
||||
import org.springframework.util.StopWatch
|
||||
import org.springframework.core.Ordered
|
||||
|
||||
class SimpleProfiler : Ordered {
|
||||
|
||||
private var order: Int = 0
|
||||
@@ -3480,6 +3486,7 @@ configure a `DataSource` in your Spring configuration file and then dependency-i
|
||||
that shared `DataSource` bean into your DAO classes. The `JdbcTemplate` is created in
|
||||
the setter for the `DataSource`. This leads to DAOs that resemble the following:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -3504,6 +3511,7 @@ the setter for the `DataSource`. This leads to DAOs that resemble the following:
|
||||
// JDBC-backed implementations of the methods on the CorporateEventDao follow...
|
||||
}
|
||||
----
|
||||
--
|
||||
|
||||
The following example shows the corresponding XML configuration:
|
||||
|
||||
@@ -3540,6 +3548,7 @@ support for dependency injection. In this case, you can annotate the class with
|
||||
(which makes it a candidate for component-scanning) and annotate the `DataSource` setter
|
||||
method with `@Autowired`. The following example shows how to do so:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -3574,6 +3583,7 @@ method with `@Autowired`. The following example shows how to do so:
|
||||
<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:
|
||||
@@ -4184,7 +4194,7 @@ To configure a `DriverManagerDataSource`:
|
||||
|
||||
. Obtain a connection with `DriverManagerDataSource` as you typically obtain a JDBC
|
||||
connection.
|
||||
. Specify the fully qualified classname of the JDBC driver so that the `DriverManager`
|
||||
. Specify the fully qualified class name of the JDBC driver so that the `DriverManager`
|
||||
can load the driver class.
|
||||
. Provide a URL that varies between JDBC drivers. (See the documentation for your driver
|
||||
for the correct value.)
|
||||
@@ -4342,7 +4352,7 @@ javadoc for more details.
|
||||
==== Using `DataSourceTransactionManager`
|
||||
|
||||
The `DataSourceTransactionManager` class is a `PlatformTransactionManager`
|
||||
implementation for single JDBC datasources. It binds a JDBC connection from the
|
||||
implementation for single JDBC data sources. It binds a JDBC connection from the
|
||||
specified data source to the currently executing thread, potentially allowing for one
|
||||
thread connection per data source.
|
||||
|
||||
@@ -5484,8 +5494,7 @@ example shows such a method:
|
||||
.Java
|
||||
----
|
||||
public List<Actor> searchForActors(int age, String namePattern) {
|
||||
List<Actor> actors = actorSearchMappingQuery.execute(age, namePattern);
|
||||
return actors;
|
||||
return actorSearchMappingQuery.execute(age, namePattern);
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
@@ -6822,7 +6831,7 @@ The following query uses a bind variable:
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val first = client.sql("SELECT id, name FROM person WHERE WHERE first_name = :fn")
|
||||
val first = client.sql("SELECT id, name FROM person WHERE first_name = :fn")
|
||||
.bind("fn", "Joe")
|
||||
.fetch().awaitSingle()
|
||||
----
|
||||
@@ -6847,7 +6856,7 @@ Without specifying further mapping details, queries return tabular results
|
||||
as `Map` whose keys are case-insensitive column names that map to their column value.
|
||||
|
||||
You can take control over result mapping by supplying a `Function<Row, T>` that gets
|
||||
called for each `Row` so it can can return arbitrary values (singular values,
|
||||
called for each `Row` so it can return arbitrary values (singular values,
|
||||
collections and maps, and objects).
|
||||
|
||||
The following example extracts the `name` column and emits its value:
|
||||
@@ -6963,7 +6972,7 @@ Consider the following query:
|
||||
SELECT id, name, state FROM table WHERE (name, age) IN (('John', 35), ('Ann', 50))
|
||||
----
|
||||
|
||||
The preceding query can be parametrized and run as follows:
|
||||
The preceding query can be parameterized and run as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
@@ -7078,6 +7087,7 @@ in your Spring configuration file and then dependency-inject
|
||||
that shared `ConnectionFactory` bean into your DAO classes. The `DatabaseClient` is created in
|
||||
the setter for the `ConnectionFactory`. This leads to DAOs that resemble the following:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -7102,12 +7112,14 @@ the setter for the `ConnectionFactory`. This leads to DAOs that resemble the fol
|
||||
// R2DBC-backed implementations of the methods on the CorporateEventDao follow...
|
||||
}
|
||||
----
|
||||
--
|
||||
|
||||
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 `@Component`
|
||||
(which makes it a candidate for component-scanning) and annotate the `ConnectionFactory` setter
|
||||
method with `@Autowired`. The following example shows how to do so:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -7142,6 +7154,7 @@ method with `@Autowired`. The following example shows how to do so:
|
||||
<1> Annotate the class with `@Component`.
|
||||
<2> Constructor injection of the `ConnectionFactory`.
|
||||
<3> Create a new `DatabaseClient` with the `ConnectionFactory`.
|
||||
--
|
||||
|
||||
Regardless of which of the above template initialization styles you choose to use (or
|
||||
not), it is seldom necessary to create a new instance of a `DatabaseClient` class each
|
||||
@@ -7205,7 +7218,7 @@ responsibility of the administrator who sets up the `ConnectionFactory`. You
|
||||
most likely fill both roles as you develop and test code, but you do not
|
||||
necessarily have to know how the production data source is configured.
|
||||
|
||||
When you use Spring's R2DBC layer, you can can configure your own with a
|
||||
When you use Spring's R2DBC layer, you can configure your own with a
|
||||
connection pool implementation provided by a third party. A popular
|
||||
implementation is R2DBC Pool (`r2dbc-pool`). Implementations in the Spring
|
||||
distribution are meant only for testing purposes and do not provide pooling.
|
||||
@@ -7279,7 +7292,7 @@ javadoc for more details.
|
||||
==== Using `R2dbcTransactionManager`
|
||||
|
||||
The `R2dbcTransactionManager` class is a `ReactiveTransactionManager` implementation for
|
||||
single R2DBC datasources. It binds an R2DBC connection from the specified connection factory
|
||||
single R2DBC data sources. It binds an R2DBC connection from the specified connection factory
|
||||
to the subscriber `Context`, potentially allowing for one subscriber connection for each
|
||||
connection factory.
|
||||
|
||||
@@ -8629,7 +8642,7 @@ given `javax.xml.transform.Result`. The result is a tagging interface that basic
|
||||
represents an XML output abstraction. Concrete implementations wrap various XML
|
||||
representations, as the following table indicates:
|
||||
|
||||
[[oxm-marshller-tbl]]
|
||||
[[oxm-marshaller-tbl]]
|
||||
|===
|
||||
| Result implementation| Wraps XML representation
|
||||
|
||||
@@ -8672,7 +8685,7 @@ This interface also has one method, which reads from the given
|
||||
with `Result`, `Source` is a tagging interface that has three concrete implementations. Each
|
||||
wraps a different XML representation, as the following table indicates:
|
||||
|
||||
[[oxm-unmarshller-tbl]]
|
||||
[[oxm-unmarshaller-tbl]]
|
||||
|===
|
||||
| Source implementation| Wraps XML representation
|
||||
|
||||
@@ -8866,9 +8879,10 @@ preamble of the XML configuration file. The following example shows how to do so
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:oxm="http://www.springframework.org/schema/oxm" <1>
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/oxm https://www.springframework.org/schema/oxm/spring-oxm.xsd"> <2>
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/oxm
|
||||
https://www.springframework.org/schema/oxm/spring-oxm.xsd"> <2>
|
||||
----
|
||||
<1> Reference the `oxm` schema.
|
||||
<2> Specify the `oxm` schema location.
|
||||
|
||||
@@ -38,12 +38,15 @@ are available to you:
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:aop="http://www.springframework.org/schema/aop"
|
||||
xmlns:tx="http://www.springframework.org/schema/tx" <1>
|
||||
xmlns:aop="http://www.springframework.org/schema/aop"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd <2>
|
||||
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/tx
|
||||
https://www.springframework.org/schema/tx/spring-tx.xsd <2>
|
||||
http://www.springframework.org/schema/aop
|
||||
https://www.springframework.org/schema/aop/spring-aop.xsd">
|
||||
|
||||
<!-- bean definitions here -->
|
||||
|
||||
@@ -79,8 +82,10 @@ the correct schema so that the elements in the `jdbc` namespace are available to
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:jdbc="http://www.springframework.org/schema/jdbc" <1>
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/jdbc https://www.springframework.org/schema/jdbc/spring-jdbc.xsd"> <2>
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/jdbc
|
||||
https://www.springframework.org/schema/jdbc/spring-jdbc.xsd"> <2>
|
||||
|
||||
<!-- bean definitions here -->
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
include::attributes.adoc[]
|
||||
|
||||
[horizontal]
|
||||
<<overview.adoc#overview, Overview>> :: history, design philosophy, feedback,
|
||||
getting started.
|
||||
<<overview.adoc#overview, Overview>> :: History, Design Philosophy, Feedback,
|
||||
Getting Started.
|
||||
<<core.adoc#spring-core, Core>> :: IoC Container, Events, Resources, i18n,
|
||||
Validation, Data Binding, Type Conversion, SpEL, AOP, AOT.
|
||||
<<testing.adoc#testing, Testing>> :: Mock Objects, TestContext Framework,
|
||||
@@ -16,13 +16,13 @@ STOMP Messaging.
|
||||
<<web-reactive.adoc#spring-webflux, Web Reactive>> :: Spring WebFlux, WebClient,
|
||||
WebSocket, RSocket.
|
||||
<<integration.adoc#spring-integration, Integration>> :: REST Clients, JMS, JCA, JMX,
|
||||
Email, Tasks, Scheduling, Caching.
|
||||
Email, Tasks, Scheduling, Caching, Observability.
|
||||
<<languages.adoc#languages, Languages>> :: Kotlin, Groovy, Dynamic Languages.
|
||||
<<appendix.adoc#appendix, Appendix>> :: Spring properties.
|
||||
https://github.com/spring-projects/spring-framework/wiki[*Wiki*] :: What's New,
|
||||
Upgrade Notes, Supported Versions, and other cross-version information.
|
||||
https://github.com/spring-projects/spring-framework/wiki[Wiki] :: What's New,
|
||||
Upgrade Notes, Supported Versions, additional cross-version information.
|
||||
|
||||
NOTE: This documentation is available in {docs-spring-framework}/reference/html/index.html[HTML] and {docs-spring-framework}/reference/pdf/spring-framework.pdf[PDF] formats.
|
||||
NOTE: This documentation is also available in {docs-spring-framework}/reference/pdf/spring-framework.pdf[PDF] format.
|
||||
|
||||
Rod Johnson, Juergen Hoeller, Keith Donald, Colin Sampaleanu, Rob Harrop, Thomas Risberg,
|
||||
Alef Arendsen, Darren Davison, Dmitriy Kopylenko, Mark Pollack, Thierry Templier, Erwin
|
||||
@@ -31,7 +31,7 @@ Brannen, Ramnivas Laddad, Arjen Poutsma, Chris Beams, Tareq Abedrabbo, Andy Clem
|
||||
Syer, Oliver Gierke, Rossen Stoyanchev, Phillip Webb, Rob Winch, Brian Clozel, Stephane
|
||||
Nicoll, Sebastien Deleuze, Jay Bryant, Mark Paluch
|
||||
|
||||
Copyright © 2002 - 2022 VMware, Inc. All Rights Reserved.
|
||||
Copyright © 2002 - 2023 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Copies of this document may be made for your own use and for distribution to others,
|
||||
provided that you do not charge any fee for such copies and further provided that each
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,303 @@
|
||||
[[mail]]
|
||||
= Email
|
||||
|
||||
This section describes how to send email with the Spring Framework.
|
||||
|
||||
.Library dependencies
|
||||
****
|
||||
The following JAR needs to be on the classpath of your application in order to use the
|
||||
Spring Framework's email support:
|
||||
|
||||
* The https://jakartaee.github.io/mail-api/[Jakarta Mail] library
|
||||
|
||||
This library is freely available on the web -- for example, in Maven Central as
|
||||
`com.sun.mail:jakarta.mail`. Please make sure to use the latest 2.x version (which uses
|
||||
the `jakarta.mail` package namespace) rather than Jakarta Mail 1.6.x (which uses the
|
||||
`javax.mail` package namespace).
|
||||
****
|
||||
|
||||
The Spring Framework provides a helpful utility library for sending email that shields
|
||||
you from the specifics of the underlying mailing system and is responsible for
|
||||
low-level resource handling on behalf of the client.
|
||||
|
||||
The `org.springframework.mail` package is the root level package for the Spring
|
||||
Framework's email support. The central interface for sending emails is the `MailSender`
|
||||
interface. A simple value object that encapsulates the properties of a simple mail such
|
||||
as `from` and `to` (plus many others) is the `SimpleMailMessage` class. This package
|
||||
also contains a hierarchy of checked exceptions that provide a higher level of
|
||||
abstraction over the lower level mail system exceptions, with the root exception being
|
||||
`MailException`. See the {api-spring-framework}/mail/MailException.html[javadoc]
|
||||
for more information on the rich mail exception hierarchy.
|
||||
|
||||
The `org.springframework.mail.javamail.JavaMailSender` interface adds specialized
|
||||
JavaMail features, such as MIME message support to the `MailSender` interface
|
||||
(from which it inherits). `JavaMailSender` also provides a callback interface called
|
||||
`org.springframework.mail.javamail.MimeMessagePreparator` for preparing a `MimeMessage`.
|
||||
|
||||
|
||||
|
||||
[[mail-usage]]
|
||||
== Usage
|
||||
|
||||
Assume that we have a business interface called `OrderManager`, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface OrderManager {
|
||||
|
||||
void placeOrder(Order order);
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Further assume that we have a requirement stating that an email message with an
|
||||
order number needs to be generated and sent to a customer who placed the relevant order.
|
||||
|
||||
|
||||
[[mail-usage-simple]]
|
||||
=== Basic `MailSender` and `SimpleMailMessage` Usage
|
||||
|
||||
The following example shows how to use `MailSender` and `SimpleMailMessage` to send an
|
||||
email when someone places an order:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import org.springframework.mail.MailException;
|
||||
import org.springframework.mail.MailSender;
|
||||
import org.springframework.mail.SimpleMailMessage;
|
||||
|
||||
public class SimpleOrderManager implements OrderManager {
|
||||
|
||||
private MailSender mailSender;
|
||||
private SimpleMailMessage templateMessage;
|
||||
|
||||
public void setMailSender(MailSender mailSender) {
|
||||
this.mailSender = mailSender;
|
||||
}
|
||||
|
||||
public void setTemplateMessage(SimpleMailMessage templateMessage) {
|
||||
this.templateMessage = templateMessage;
|
||||
}
|
||||
|
||||
public void placeOrder(Order order) {
|
||||
|
||||
// Do the business calculations...
|
||||
|
||||
// Call the collaborators to persist the order...
|
||||
|
||||
// Create a thread safe "copy" of the template message and customize it
|
||||
SimpleMailMessage msg = new SimpleMailMessage(this.templateMessage);
|
||||
msg.setTo(order.getCustomer().getEmailAddress());
|
||||
msg.setText(
|
||||
"Dear " + order.getCustomer().getFirstName()
|
||||
+ order.getCustomer().getLastName()
|
||||
+ ", thank you for placing order. Your order number is "
|
||||
+ order.getOrderNumber());
|
||||
try {
|
||||
this.mailSender.send(msg);
|
||||
}
|
||||
catch (MailException ex) {
|
||||
// simply log it and go on...
|
||||
System.err.println(ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
The following example shows the bean definitions for the preceding code:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
|
||||
<property name="host" value="mail.mycompany.example"/>
|
||||
</bean>
|
||||
|
||||
<!-- this is a template message that we can pre-load with default state -->
|
||||
<bean id="templateMessage" class="org.springframework.mail.SimpleMailMessage">
|
||||
<property name="from" value="customerservice@mycompany.example"/>
|
||||
<property name="subject" value="Your order"/>
|
||||
</bean>
|
||||
|
||||
<bean id="orderManager" class="com.mycompany.businessapp.support.SimpleOrderManager">
|
||||
<property name="mailSender" ref="mailSender"/>
|
||||
<property name="templateMessage" ref="templateMessage"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
|
||||
[[mail-usage-mime]]
|
||||
=== Using `JavaMailSender` and `MimeMessagePreparator`
|
||||
|
||||
This section describes another implementation of `OrderManager` that uses the `MimeMessagePreparator`
|
||||
callback interface. In the following example, the `mailSender` property is of type
|
||||
`JavaMailSender` so that we are able to use the JavaMail `MimeMessage` class:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.mail.Message;
|
||||
import jakarta.mail.MessagingException;
|
||||
import jakarta.mail.internet.InternetAddress;
|
||||
import jakarta.mail.internet.MimeMessage;
|
||||
|
||||
import jakarta.mail.internet.MimeMessage;
|
||||
import org.springframework.mail.MailException;
|
||||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
import org.springframework.mail.javamail.MimeMessagePreparator;
|
||||
|
||||
public class SimpleOrderManager implements OrderManager {
|
||||
|
||||
private JavaMailSender mailSender;
|
||||
|
||||
public void setMailSender(JavaMailSender mailSender) {
|
||||
this.mailSender = mailSender;
|
||||
}
|
||||
|
||||
public void placeOrder(final Order order) {
|
||||
// Do the business calculations...
|
||||
// Call the collaborators to persist the order...
|
||||
|
||||
MimeMessagePreparator preparator = new MimeMessagePreparator() {
|
||||
public void prepare(MimeMessage mimeMessage) throws Exception {
|
||||
mimeMessage.setRecipient(Message.RecipientType.TO,
|
||||
new InternetAddress(order.getCustomer().getEmailAddress()));
|
||||
mimeMessage.setFrom(new InternetAddress("mail@mycompany.example"));
|
||||
mimeMessage.setText("Dear " + order.getCustomer().getFirstName() + " " +
|
||||
order.getCustomer().getLastName() + ", thanks for your order. " +
|
||||
"Your order number is " + order.getOrderNumber() + ".");
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
this.mailSender.send(preparator);
|
||||
}
|
||||
catch (MailException ex) {
|
||||
// simply log it and go on...
|
||||
System.err.println(ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
NOTE: The mail code is a crosscutting concern and could well be a candidate for
|
||||
refactoring into a <<core.adoc#aop, custom Spring AOP aspect>>, which could then
|
||||
be run at appropriate joinpoints on the `OrderManager` target.
|
||||
|
||||
The Spring Framework's mail support ships with the standard JavaMail implementation.
|
||||
See the relevant javadoc for more information.
|
||||
|
||||
|
||||
|
||||
[[mail-javamail-mime]]
|
||||
== Using the JavaMail `MimeMessageHelper`
|
||||
|
||||
A class that comes in pretty handy when dealing with JavaMail messages is
|
||||
`org.springframework.mail.javamail.MimeMessageHelper`, which shields you from
|
||||
having to use the verbose JavaMail API. Using the `MimeMessageHelper`, it is
|
||||
pretty easy to create a `MimeMessage`, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// of course you would use DI in any real-world cases
|
||||
JavaMailSenderImpl sender = new JavaMailSenderImpl();
|
||||
sender.setHost("mail.host.com");
|
||||
|
||||
MimeMessage message = sender.createMimeMessage();
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message);
|
||||
helper.setTo("test@host.com");
|
||||
helper.setText("Thank you for ordering!");
|
||||
|
||||
sender.send(message);
|
||||
----
|
||||
|
||||
|
||||
[[mail-javamail-mime-attachments]]
|
||||
=== Sending Attachments and Inline Resources
|
||||
|
||||
Multipart email messages allow for both attachments and inline resources. Examples of
|
||||
inline resources include an image or a stylesheet that you want to use in your message but
|
||||
that you do not want displayed as an attachment.
|
||||
|
||||
[[mail-javamail-mime-attachments-attachment]]
|
||||
==== Attachments
|
||||
|
||||
The following example shows you how to use the `MimeMessageHelper` to send an email
|
||||
with a single JPEG image attachment:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
JavaMailSenderImpl sender = new JavaMailSenderImpl();
|
||||
sender.setHost("mail.host.com");
|
||||
|
||||
MimeMessage message = sender.createMimeMessage();
|
||||
|
||||
// use the true flag to indicate you need a multipart message
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true);
|
||||
helper.setTo("test@host.com");
|
||||
|
||||
helper.setText("Check out this image!");
|
||||
|
||||
// let's attach the infamous windows Sample file (this time copied to c:/)
|
||||
FileSystemResource file = new FileSystemResource(new File("c:/Sample.jpg"));
|
||||
helper.addAttachment("CoolImage.jpg", file);
|
||||
|
||||
sender.send(message);
|
||||
----
|
||||
|
||||
[[mail-javamail-mime-attachments-inline]]
|
||||
==== Inline Resources
|
||||
|
||||
The following example shows you how to use the `MimeMessageHelper` to send an email
|
||||
with an inline image:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
JavaMailSenderImpl sender = new JavaMailSenderImpl();
|
||||
sender.setHost("mail.host.com");
|
||||
|
||||
MimeMessage message = sender.createMimeMessage();
|
||||
|
||||
// use the true flag to indicate you need a multipart message
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true);
|
||||
helper.setTo("test@host.com");
|
||||
|
||||
// use the true flag to indicate the text included is HTML
|
||||
helper.setText("<html><body><img src='cid:identifier1234'></body></html>", true);
|
||||
|
||||
// let's include the infamous windows Sample file (this time copied to c:/)
|
||||
FileSystemResource res = new FileSystemResource(new File("c:/Sample.jpg"));
|
||||
helper.addInline("identifier1234", res);
|
||||
|
||||
sender.send(message);
|
||||
----
|
||||
|
||||
WARNING: Inline resources are added to the `MimeMessage` by using the specified `Content-ID`
|
||||
(`identifier1234` in the above example). The order in which you add the text
|
||||
and the resource are very important. Be sure to first add the text and then
|
||||
the resources. If you are doing it the other way around, it does not work.
|
||||
|
||||
|
||||
[[mail-templates]]
|
||||
=== Creating Email Content by Using a Templating Library
|
||||
|
||||
The code in the examples shown in the previous sections explicitly created the content of the email message,
|
||||
by using methods calls such as `message.setText(..)`. This is fine for simple cases, and it
|
||||
is okay in the context of the aforementioned examples, where the intent was to show you
|
||||
the very basics of the API.
|
||||
|
||||
In your typical enterprise application, though, developers often do not create the content
|
||||
of email messages by using the previously shown approach for a number of reasons:
|
||||
|
||||
* Creating HTML-based email content in Java code is tedious and error prone.
|
||||
* There is no clear separation between display logic and business logic.
|
||||
* Changing the display structure of the email content requires writing Java code,
|
||||
recompiling, redeploying, and so on.
|
||||
|
||||
Typically, the approach taken to address these issues is to use a template library (such
|
||||
as FreeMarker) to define the display structure of email content. This leaves your code
|
||||
tasked only with creating the data that is to be rendered in the email template and
|
||||
sending the email. It is definitely a best practice when the content of your email messages
|
||||
becomes even moderately complex, and, with the Spring Framework's support classes for
|
||||
FreeMarker, it becomes quite easy to do.
|
||||
|
||||
@@ -28,8 +28,10 @@ correct schema so that the elements in the `jee` namespace are available to you:
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:jee="http://www.springframework.org/schema/jee"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/jee https://www.springframework.org/schema/jee/spring-jee.xsd">
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/jee
|
||||
https://www.springframework.org/schema/jee/spring-jee.xsd">
|
||||
|
||||
<!-- bean definitions here -->
|
||||
|
||||
@@ -287,8 +289,10 @@ are available to you:
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:jms="http://www.springframework.org/schema/jms"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/jms https://www.springframework.org/schema/jms/spring-jms.xsd">
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/jms
|
||||
https://www.springframework.org/schema/jms/spring-jms.xsd">
|
||||
|
||||
<!-- bean definitions here -->
|
||||
|
||||
@@ -324,8 +328,10 @@ the correct schema so that the elements in the `cache` namespace are available t
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:cache="http://www.springframework.org/schema/cache"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/cache https://www.springframework.org/schema/cache/spring-cache.xsd">
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/cache
|
||||
https://www.springframework.org/schema/cache/spring-cache.xsd">
|
||||
|
||||
<!-- bean definitions here -->
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,193 @@
|
||||
[[integration.observability]]
|
||||
= Observability Support
|
||||
|
||||
Micrometer defines an https://micrometer.io/docs/observation[Observation concept that enables both Metrics and Traces] in applications.
|
||||
Metrics support offers a way to create timers, gauges or counters for collecting statistics about the runtime behavior of your application.
|
||||
Metrics can help you to track error rates, usage patterns, performance and more.
|
||||
Traces provide a holistic view of an entire system, crossing application boundaries; you can zoom in on particular user requests and follow their entire completion across applications.
|
||||
|
||||
Spring Framework instruments various parts of its own codebase to publish observations if an `ObservationRegistry` is configured.
|
||||
You can learn more about {docs-spring-boot}/html/actuator.html#actuator.metrics[configuring the observability infrastructure in Spring Boot].
|
||||
|
||||
[[integration.observability.concepts]]
|
||||
== Micrometer Observation concepts
|
||||
|
||||
If you are not familiar with Micrometer Observation, here's a quick summary of the new concepts you should know about.
|
||||
|
||||
* `Observation` is the actual recording of something happening in your application. This is processed by `ObservationHandler` implementations to produce metrics or traces.
|
||||
* Each observation has a corresponding `ObservationContext` implementation; this type holds all the relevant information for extracting metadata for it.
|
||||
In the case of an HTTP server observation, the context implementation could hold the HTTP request, the HTTP response, any Exception thrown during processing...
|
||||
* Each `Observation` holds `KeyValues` metadata. In the case of a server HTTP observation, this could be the HTTP request method, the HTTP response status...
|
||||
This metadata is contributed by `ObservationConvention` implementations which should declare the type of `ObservationContext` they support.
|
||||
* `KeyValues` are said to be "low cardinality" if there is a low, bounded number of possible values for the `KeyValue` tuple (HTTP method is a good example).
|
||||
Low cardinality values are contributed to metrics only.
|
||||
High cardinality values are on the other hand unbounded (for example, HTTP request URIs) and are only contributed to Traces.
|
||||
* An `ObservationDocumentation` documents all observations in a particular domain, listing the expected key names and their meaning.
|
||||
|
||||
|
||||
[[integration.observability.config]]
|
||||
== Configuring Observations
|
||||
|
||||
Global configuration options are available at the `ObservationRegistry#observationConfig()` level.
|
||||
Each instrumented component will provide two extension points:
|
||||
|
||||
* setting the `ObservationRegistry`; if not set, observations will not be recorded and will be no-ops
|
||||
* providing a custom `ObservationConvention` to change the default observation name and extracted `KeyValues`
|
||||
|
||||
|
||||
[[integration.observability.config.conventions]]
|
||||
=== Using custom Observation conventions
|
||||
|
||||
Let's take the example of the Spring MVC "http.server.requests" metrics instrumentation with the `ServerHttpObservationFilter`.
|
||||
This observation is using a `ServerRequestObservationConvention` with a `ServerRequestObservationContext`; custom conventions can be configured on the Servlet filter.
|
||||
If you would like to customize the metadata produced with the observation, you can extend the `DefaultServerRequestObservationConvention` for your requirements:
|
||||
|
||||
include::code:ExtendedServerRequestObservationConvention[]
|
||||
|
||||
If you want full control, you can then implement the entire convention contract for the observation you're interested in:
|
||||
|
||||
include::code:CustomServerRequestObservationConvention[]
|
||||
|
||||
You can also achieve similar goals using a custom `ObservationFilter` - adding or removing key values for an observation.
|
||||
Filters do not replace the default convention and are used as a post-processing component.
|
||||
|
||||
include::code:ServerRequestObservationFilter[]
|
||||
|
||||
You can configure `ObservationFilter` instances on the `ObservationRegistry`.
|
||||
|
||||
|
||||
[[integration.observability.http-server]]
|
||||
== HTTP Server instrumentation
|
||||
|
||||
HTTP server exchanges observations are created with the name `"http.server.requests"` for Servlet and Reactive applications.
|
||||
|
||||
[[integration.observability.http-server.servlet]]
|
||||
=== Servlet applications
|
||||
|
||||
Applications need to configure the `org.springframework.web.filter.ServerHttpObservationFilter` Servlet filter in their application.
|
||||
It is using the `org.springframework.http.server.observation.DefaultServerRequestObservationConvention` by default, backed by the `ServerRequestObservationContext`.
|
||||
|
||||
This will only record an observation as an error if the `Exception` has not been handled by the web Framework and has bubbled up to the Servlet filter.
|
||||
Typically, all exceptions handled by Spring MVC's `@ExceptionHandler` and <<web.adoc#mvc-ann-rest-exceptions,`ProblemDetail` support>> will not be recorded with the observation.
|
||||
You can, at any point during request processing, set the error field on the `ObservationContext` yourself:
|
||||
|
||||
include::code:UserController[]
|
||||
|
||||
By default, the following `KeyValues` are created:
|
||||
|
||||
.Low cardinality Keys
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`exception` _(required)_|Name of the exception thrown during the exchange, or `KeyValue#NONE_VALUE`} if no exception happened.
|
||||
|`method` _(required)_|Name of HTTP request method or `"none"` if the request was not received properly.
|
||||
|`outcome` _(required)_|Outcome of the HTTP server exchange.
|
||||
|`status` _(required)_|HTTP response raw status code, or `"UNKNOWN"` if no response was created.
|
||||
|`uri` _(required)_|URI pattern for the matching handler if available, falling back to `REDIRECTION` for 3xx responses, `NOT_FOUND` for 404 responses, `root` for requests with no path info, and `UNKNOWN` for all other requests.
|
||||
|===
|
||||
|
||||
.High cardinality Keys
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`http.url` _(required)_|HTTP request URI.
|
||||
|===
|
||||
|
||||
|
||||
[[integration.observability.http-server.reactive]]
|
||||
=== Reactive applications
|
||||
|
||||
Applications need to configure the `org.springframework.web.filter.reactive.ServerHttpObservationFilter` reactive `WebFilter` in their application.
|
||||
It is using the `org.springframework.http.server.reactive.observation.DefaultServerRequestObservationConvention` by default, backed by the `ServerRequestObservationContext`.
|
||||
|
||||
This will only record an observation as an error if the `Exception` has not been handled by the web Framework and has bubbled up to the `WebFilter`.
|
||||
Typically, all exceptions handled by Spring WebFlux's `@ExceptionHandler` and <<web.adoc#webflux-ann-rest-exceptions,`ProblemDetail` support>> will not be recorded with the observation.
|
||||
You can, at any point during request processing, set the error field on the `ObservationContext` yourself:
|
||||
|
||||
include::code:UserController[]
|
||||
|
||||
By default, the following `KeyValues` are created:
|
||||
|
||||
.Low cardinality Keys
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`exception` _(required)_|Name of the exception thrown during the exchange, or `"none"` if no exception happened.
|
||||
|`method` _(required)_|Name of HTTP request method or `"none"` if the request was not received properly.
|
||||
|`outcome` _(required)_|Outcome of the HTTP server exchange.
|
||||
|`status` _(required)_|HTTP response raw status code, or `"UNKNOWN"` if no response was created.
|
||||
|`uri` _(required)_|URI pattern for the matching handler if available, falling back to `REDIRECTION` for 3xx responses, `NOT_FOUND` for 404 responses, `root` for requests with no path info, and `UNKNOWN` for all other requests.
|
||||
|===
|
||||
|
||||
.High cardinality Keys
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`http.url` _(required)_|HTTP request URI.
|
||||
|===
|
||||
|
||||
|
||||
|
||||
[[integration.observability.http-client]]
|
||||
== HTTP Client instrumentation
|
||||
|
||||
HTTP client exchanges observations are created with the name `"http.client.requests"` for blocking and reactive clients.
|
||||
Unlike their server counterparts, the instrumentation is implemented directly in the client so the only required step is to configure an `ObservationRegistry` on the client.
|
||||
|
||||
[[integration.observability.http-client.resttemplate]]
|
||||
=== RestTemplate
|
||||
|
||||
Applications must configure an `ObservationRegistry` on `RestTemplate` instances to enable the instrumentation; without that, observations are "no-ops".
|
||||
Spring Boot will auto-configure `RestTemplateBuilder` beans with the observation registry already set.
|
||||
|
||||
Instrumentation is using the `org.springframework.http.client.observation.ClientRequestObservationConvention` by default, backed by the `ClientRequestObservationContext`.
|
||||
|
||||
.Low cardinality Keys
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`method` _(required)_|Name of HTTP request method or `"none"` if the request could not be created.
|
||||
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. Only the path part of the URI is considered.
|
||||
|`client.name` _(required)_|Client name derived from the request URI host.
|
||||
|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received.
|
||||
|`outcome` _(required)_|Outcome of the HTTP client exchange.
|
||||
|`exception` _(required)_|Name of the exception thrown during the exchange, or `"none"` if no exception happened.
|
||||
|===
|
||||
|
||||
.High cardinality Keys
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`http.url` _(required)_|HTTP request URI.
|
||||
|===
|
||||
|
||||
|
||||
|
||||
[[integration.observability.http-client.webclient]]
|
||||
=== WebClient
|
||||
|
||||
Applications must configure an `ObservationRegistry` on the `WebClient` builder to enable the instrumentation; without that, observations are "no-ops".
|
||||
Spring Boot will auto-configure `WebClient.Builder` beans with the observation registry already set.
|
||||
|
||||
Instrumentation is using the `org.springframework.web.reactive.function.client.ClientRequestObservationConvention` by default, backed by the `ClientRequestObservationContext`.
|
||||
|
||||
.Low cardinality Keys
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`method` _(required)_|Name of HTTP request method or `"none"` if the request could not be created.
|
||||
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided. Only the path part of the URI is considered.
|
||||
|`client.name` _(required)_|Client name derived from the request URI host.
|
||||
|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received.
|
||||
|`outcome` _(required)_|Outcome of the HTTP client exchange.
|
||||
|`exception` _(required)_|Name of the exception thrown during the exchange, or `"none"` if no exception happened.
|
||||
|===
|
||||
|
||||
.High cardinality Keys
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`http.url` _(required)_|HTTP request URI.
|
||||
|===
|
||||
|
||||
|
||||
@@ -0,0 +1,520 @@
|
||||
[[rest-client-access]]
|
||||
= REST Clients
|
||||
|
||||
The Spring Framework provides the following choices for making calls to REST endpoints:
|
||||
|
||||
* <<rest-webclient>> - non-blocking, reactive client w fluent API.
|
||||
* <<rest-resttemplate>> - synchronous client with template method API.
|
||||
* <<rest-http-interface>> - annotated interface with generated, dynamic proxy implementation.
|
||||
|
||||
|
||||
[[rest-webclient]]
|
||||
== `WebClient`
|
||||
|
||||
`WebClient` is a non-blocking, reactive client to perform HTTP requests. It was
|
||||
introduced in 5.0 and offers an alternative to the `RestTemplate`, with support for
|
||||
synchronous, asynchronous, and streaming scenarios.
|
||||
|
||||
`WebClient` supports the following:
|
||||
|
||||
* Non-blocking I/O.
|
||||
* Reactive Streams back pressure.
|
||||
* High concurrency with fewer hardware resources.
|
||||
* Functional-style, fluent API that takes advantage of Java 8 lambdas.
|
||||
* Synchronous and asynchronous interactions.
|
||||
* Streaming up to or streaming down from a server.
|
||||
|
||||
See <<web-reactive.adoc#webflux-client, WebClient>> for more details.
|
||||
|
||||
|
||||
|
||||
|
||||
[[rest-resttemplate]]
|
||||
== `RestTemplate`
|
||||
|
||||
The `RestTemplate` provides a higher level API over HTTP client libraries. It makes it
|
||||
easy to invoke REST endpoints in a single line. It exposes the following groups of
|
||||
overloaded methods:
|
||||
|
||||
NOTE: `RestTemplate` is in maintenance mode, with only requests for minor
|
||||
changes and bugs to be accepted. Please, consider using the
|
||||
<<web-reactive.adoc#webflux-client, WebClient>> instead.
|
||||
|
||||
[[rest-overview-of-resttemplate-methods-tbl]]
|
||||
.RestTemplate methods
|
||||
[cols="1,3"]
|
||||
|===
|
||||
| Method group | Description
|
||||
|
||||
| `getForObject`
|
||||
| Retrieves a representation via GET.
|
||||
|
||||
| `getForEntity`
|
||||
| Retrieves a `ResponseEntity` (that is, status, headers, and body) by using GET.
|
||||
|
||||
| `headForHeaders`
|
||||
| Retrieves all headers for a resource by using HEAD.
|
||||
|
||||
| `postForLocation`
|
||||
| Creates a new resource by using POST and returns the `Location` header from the response.
|
||||
|
||||
| `postForObject`
|
||||
| Creates a new resource by using POST and returns the representation from the response.
|
||||
|
||||
| `postForEntity`
|
||||
| Creates a new resource by using POST and returns the representation from the response.
|
||||
|
||||
| `put`
|
||||
| Creates or updates a resource by using PUT.
|
||||
|
||||
| `patchForObject`
|
||||
| Updates a resource by using PATCH and returns the representation from the response.
|
||||
Note that the JDK `HttpURLConnection` does not support `PATCH`, but Apache
|
||||
HttpComponents and others do.
|
||||
|
||||
| `delete`
|
||||
| Deletes the resources at the specified URI by using DELETE.
|
||||
|
||||
| `optionsForAllow`
|
||||
| Retrieves allowed HTTP methods for a resource by using ALLOW.
|
||||
|
||||
| `exchange`
|
||||
| More generalized (and less opinionated) version of the preceding methods that provides extra
|
||||
flexibility when needed. It accepts a `RequestEntity` (including HTTP method, URL, headers,
|
||||
and body as input) and returns a `ResponseEntity`.
|
||||
|
||||
These methods allow the use of `ParameterizedTypeReference` instead of `Class` to specify
|
||||
a response type with generics.
|
||||
|
||||
| `execute`
|
||||
| The most generalized way to perform a request, with full control over request
|
||||
preparation and response extraction through callback interfaces.
|
||||
|
||||
|===
|
||||
|
||||
[[rest-resttemplate-create]]
|
||||
=== Initialization
|
||||
|
||||
The default constructor uses `java.net.HttpURLConnection` to perform requests. You can
|
||||
switch to a different HTTP library with an implementation of `ClientHttpRequestFactory`.
|
||||
There is built-in support for the following:
|
||||
|
||||
* Apache HttpComponents
|
||||
* Netty
|
||||
* OkHttp
|
||||
|
||||
For example, to switch to Apache HttpComponents, you can use the following:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
|
||||
----
|
||||
|
||||
Each `ClientHttpRequestFactory` exposes configuration options specific to the underlying
|
||||
HTTP client library -- for example, for credentials, connection pooling, and other details.
|
||||
|
||||
TIP: Note that the `java.net` implementation for HTTP requests can raise an exception when
|
||||
accessing the status of a response that represents an error (such as 401). If this is an
|
||||
issue, switch to another HTTP client library.
|
||||
|
||||
NOTE: `RestTemplate` can be instrumented for observability, in order to produce metrics and traces.
|
||||
See the <<integration.adoc#integration.observability.http-client.resttemplate,RestTemplate Observability support>> section.
|
||||
|
||||
[[rest-resttemplate-uri]]
|
||||
==== URIs
|
||||
|
||||
Many of the `RestTemplate` methods accept a URI template and URI template variables,
|
||||
either as a `String` variable argument, or as `Map<String,String>`.
|
||||
|
||||
The following example uses a `String` variable argument:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
String result = restTemplate.getForObject(
|
||||
"https://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42", "21");
|
||||
----
|
||||
|
||||
The following example uses a `Map<String, String>`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
Map<String, String> vars = Collections.singletonMap("hotel", "42");
|
||||
|
||||
String result = restTemplate.getForObject(
|
||||
"https://example.com/hotels/{hotel}/rooms/{hotel}", String.class, vars);
|
||||
----
|
||||
|
||||
Keep in mind URI templates are automatically encoded, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
restTemplate.getForObject("https://example.com/hotel list", String.class);
|
||||
|
||||
// Results in request to "https://example.com/hotel%20list"
|
||||
----
|
||||
|
||||
You can use the `uriTemplateHandler` property of `RestTemplate` to customize how URIs
|
||||
are encoded. Alternatively, you can prepare a `java.net.URI` and pass it into one of
|
||||
the `RestTemplate` methods that accepts a `URI`.
|
||||
|
||||
For more details on working with and encoding URIs, see <<web.adoc#mvc-uri-building, URI Links>>.
|
||||
|
||||
[[rest-template-headers]]
|
||||
==== Headers
|
||||
|
||||
You can use the `exchange()` methods to specify request headers, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
String uriTemplate = "https://example.com/hotels/{hotel}";
|
||||
URI uri = UriComponentsBuilder.fromUriString(uriTemplate).build(42);
|
||||
|
||||
RequestEntity<Void> requestEntity = RequestEntity.get(uri)
|
||||
.header("MyRequestHeader", "MyValue")
|
||||
.build();
|
||||
|
||||
ResponseEntity<String> response = template.exchange(requestEntity, String.class);
|
||||
|
||||
String responseHeader = response.getHeaders().getFirst("MyResponseHeader");
|
||||
String body = response.getBody();
|
||||
----
|
||||
|
||||
You can obtain response headers through many `RestTemplate` method variants that return
|
||||
`ResponseEntity`.
|
||||
|
||||
[[rest-template-body]]
|
||||
=== Body
|
||||
|
||||
Objects passed into and returned from `RestTemplate` methods are converted to and from raw
|
||||
content with the help of an `HttpMessageConverter`.
|
||||
|
||||
On a POST, an input object is serialized to the request body, as the following example shows:
|
||||
|
||||
----
|
||||
URI location = template.postForLocation("https://example.com/people", person);
|
||||
----
|
||||
|
||||
You need not explicitly set the Content-Type header of the request. In most cases,
|
||||
you can find a compatible message converter based on the source `Object` type, and the chosen
|
||||
message converter sets the content type accordingly. If necessary, you can use the
|
||||
`exchange` methods to explicitly provide the `Content-Type` request header, and that, in
|
||||
turn, influences what message converter is selected.
|
||||
|
||||
On a GET, the body of the response is deserialized to an output `Object`, as the following example shows:
|
||||
|
||||
----
|
||||
Person person = restTemplate.getForObject("https://example.com/people/{id}", Person.class, 42);
|
||||
----
|
||||
|
||||
The `Accept` header of the request does not need to be explicitly set. In most cases,
|
||||
a compatible message converter can be found based on the expected response type, which
|
||||
then helps to populate the `Accept` header. If necessary, you can use the `exchange`
|
||||
methods to provide the `Accept` header explicitly.
|
||||
|
||||
By default, `RestTemplate` registers all built-in
|
||||
<<rest-message-conversion, message converters>>, depending on classpath checks that help
|
||||
to determine what optional conversion libraries are present. You can also set the message
|
||||
converters to use explicitly.
|
||||
|
||||
[[rest-message-conversion]]
|
||||
==== Message Conversion
|
||||
[.small]#<<web-reactive.adoc#webflux-codecs, See equivalent in the Reactive stack>>#
|
||||
|
||||
The `spring-web` module contains the `HttpMessageConverter` contract for reading and
|
||||
writing the body of HTTP requests and responses through `InputStream` and `OutputStream`.
|
||||
`HttpMessageConverter` instances are used on the client side (for example, in the `RestTemplate`) and
|
||||
on the server side (for example, in Spring MVC REST controllers).
|
||||
|
||||
Concrete implementations for the main media (MIME) types are provided in the framework
|
||||
and are, by default, registered with the `RestTemplate` on the client side and with
|
||||
`RequestMappingHandlerAdapter` on the server side (see
|
||||
<<web.adoc#mvc-config-message-converters, Configuring Message Converters>>).
|
||||
|
||||
The implementations of `HttpMessageConverter` are described in the following sections.
|
||||
For all converters, a default media type is used, but you can override it by setting the
|
||||
`supportedMediaTypes` bean property. The following table describes each implementation:
|
||||
|
||||
[[rest-message-converters-tbl]]
|
||||
.HttpMessageConverter Implementations
|
||||
[cols="1,3"]
|
||||
|===
|
||||
| MessageConverter | Description
|
||||
|
||||
| `StringHttpMessageConverter`
|
||||
| An `HttpMessageConverter` implementation that can read and write `String` instances from the HTTP
|
||||
request and response. By default, this converter supports all text media types
|
||||
(`text/{asterisk}`) and writes with a `Content-Type` of `text/plain`.
|
||||
|
||||
| `FormHttpMessageConverter`
|
||||
| An `HttpMessageConverter` implementation that can read and write form data from the HTTP
|
||||
request and response. By default, this converter reads and writes the
|
||||
`application/x-www-form-urlencoded` media type. Form data is read from and written into a
|
||||
`MultiValueMap<String, String>`. The converter can also write (but not read) multipart
|
||||
data read from a `MultiValueMap<String, Object>`. By default, `multipart/form-data` is
|
||||
supported. As of Spring Framework 5.2, additional multipart subtypes can be supported for
|
||||
writing form data. Consult the javadoc for `FormHttpMessageConverter` for further details.
|
||||
|
||||
| `ByteArrayHttpMessageConverter`
|
||||
| An `HttpMessageConverter` implementation that can read and write byte arrays from the
|
||||
HTTP request and response. By default, this converter supports all media types (`{asterisk}/{asterisk}`)
|
||||
and writes with a `Content-Type` of `application/octet-stream`. You can override this
|
||||
by setting the `supportedMediaTypes` property and overriding `getContentType(byte[])`.
|
||||
|
||||
| `MarshallingHttpMessageConverter`
|
||||
| An `HttpMessageConverter` implementation that can read and write XML by using Spring's
|
||||
`Marshaller` and `Unmarshaller` abstractions from the `org.springframework.oxm` package.
|
||||
This converter requires a `Marshaller` and `Unmarshaller` before it can be used. You can inject these
|
||||
through constructor or bean properties. By default, this converter supports
|
||||
`text/xml` and `application/xml`.
|
||||
|
||||
| `MappingJackson2HttpMessageConverter`
|
||||
| An `HttpMessageConverter` implementation that can read and write JSON by using Jackson's
|
||||
`ObjectMapper`. You can customize JSON mapping as needed through the use of Jackson's
|
||||
provided annotations. When you need further control (for cases where custom JSON
|
||||
serializers/deserializers need to be provided for specific types), you can inject a custom `ObjectMapper`
|
||||
through the `ObjectMapper` property. By default, this
|
||||
converter supports `application/json`.
|
||||
|
||||
| `MappingJackson2XmlHttpMessageConverter`
|
||||
| An `HttpMessageConverter` implementation that can read and write XML by using
|
||||
https://github.com/FasterXML/jackson-dataformat-xml[Jackson XML] extension's
|
||||
`XmlMapper`. You can customize XML mapping as needed through the use of JAXB
|
||||
or Jackson's provided annotations. When you need further control (for cases where custom XML
|
||||
serializers/deserializers need to be provided for specific types), you can inject a custom `XmlMapper`
|
||||
through the `ObjectMapper` property. By default, this
|
||||
converter supports `application/xml`.
|
||||
|
||||
| `SourceHttpMessageConverter`
|
||||
| An `HttpMessageConverter` implementation that can read and write
|
||||
`javax.xml.transform.Source` from the HTTP request and response. Only `DOMSource`,
|
||||
`SAXSource`, and `StreamSource` are supported. By default, this converter supports
|
||||
`text/xml` and `application/xml`.
|
||||
|
||||
| `BufferedImageHttpMessageConverter`
|
||||
| An `HttpMessageConverter` implementation that can read and write
|
||||
`java.awt.image.BufferedImage` from the HTTP request and response. This converter reads
|
||||
and writes the media type supported by the Java I/O API.
|
||||
|
||||
|===
|
||||
|
||||
[[rest-template-jsonview]]
|
||||
=== Jackson JSON Views
|
||||
|
||||
You can specify a https://www.baeldung.com/jackson-json-view-annotation[Jackson JSON View]
|
||||
to serialize only a subset of the object properties, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
MappingJacksonValue value = new MappingJacksonValue(new User("eric", "7!jd#h23"));
|
||||
value.setSerializationView(User.WithoutPasswordView.class);
|
||||
|
||||
RequestEntity<MappingJacksonValue> requestEntity =
|
||||
RequestEntity.post(new URI("https://example.com/user")).body(value);
|
||||
|
||||
ResponseEntity<String> response = template.exchange(requestEntity, String.class);
|
||||
----
|
||||
|
||||
[[rest-template-multipart]]
|
||||
=== Multipart
|
||||
|
||||
To send multipart data, you need to provide a `MultiValueMap<String, Object>` whose values
|
||||
may be an `Object` for part content, a `Resource` for a file part, or an `HttpEntity` for
|
||||
part content with headers. For example:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
|
||||
|
||||
parts.add("fieldPart", "fieldValue");
|
||||
parts.add("filePart", new FileSystemResource("...logo.png"));
|
||||
parts.add("jsonPart", new Person("Jason"));
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_XML);
|
||||
parts.add("xmlPart", new HttpEntity<>(myBean, headers));
|
||||
----
|
||||
|
||||
In most cases, you do not have to specify the `Content-Type` for each part. The content
|
||||
type is determined automatically based on the `HttpMessageConverter` chosen to serialize
|
||||
it or, in the case of a `Resource` based on the file extension. If necessary, you can
|
||||
explicitly provide the `MediaType` with an `HttpEntity` wrapper.
|
||||
|
||||
Once the `MultiValueMap` is ready, you can pass it to the `RestTemplate`, as show below:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
MultiValueMap<String, Object> parts = ...;
|
||||
template.postForObject("https://example.com/upload", parts, Void.class);
|
||||
----
|
||||
|
||||
If the `MultiValueMap` contains at least one non-`String` value, the `Content-Type` is set
|
||||
to `multipart/form-data` by the `FormHttpMessageConverter`. If the `MultiValueMap` has
|
||||
`String` values the `Content-Type` is defaulted to `application/x-www-form-urlencoded`.
|
||||
If necessary the `Content-Type` may also be set explicitly.
|
||||
|
||||
|
||||
[[rest-http-interface]]
|
||||
== HTTP Interface
|
||||
|
||||
The Spring Framework lets you define an HTTP service as a Java interface with annotated
|
||||
methods for HTTP exchanges. You can then generate a proxy that implements this interface
|
||||
and performs the exchanges. This helps to simplify HTTP remote access which often
|
||||
involves a facade that wraps the details of using the underlying HTTP client.
|
||||
|
||||
One, declare an interface with `@HttpExchange` methods:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
interface RepositoryService {
|
||||
|
||||
@GetExchange("/repos/{owner}/{repo}")
|
||||
Repository getRepository(@PathVariable String owner, @PathVariable String repo);
|
||||
|
||||
// more HTTP exchange methods...
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Two, create a proxy that will perform the declared HTTP exchanges:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
WebClient client = WebClient.builder().baseUrl("https://api.github.com/").build();
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).build();
|
||||
|
||||
RepositoryService service = factory.createClient(RepositoryService.class);
|
||||
----
|
||||
|
||||
`@HttpExchange` is supported at the type level where it applies to all methods:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@HttpExchange(url = "/repos/{owner}/{repo}", accept = "application/vnd.github.v3+json")
|
||||
interface RepositoryService {
|
||||
|
||||
@GetExchange
|
||||
Repository getRepository(@PathVariable String owner, @PathVariable String repo);
|
||||
|
||||
@PatchExchange(contentType = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
||||
void updateRepository(@PathVariable String owner, @PathVariable String repo,
|
||||
@RequestParam String name, @RequestParam String description, @RequestParam String homepage);
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
[[rest-http-interface-method-parameters]]
|
||||
=== Method Parameters
|
||||
|
||||
Annotated, HTTP exchange methods support flexible method signatures with the following
|
||||
method parameters:
|
||||
|
||||
[cols="1,2", options="header"]
|
||||
|===
|
||||
| Method argument | Description
|
||||
|
||||
| `URI`
|
||||
| Dynamically set the URL for the request, overriding the annotation's `url` attribute.
|
||||
|
||||
| `HttpMethod`
|
||||
| Dynamically set the HTTP method for the request, overriding the annotation's `method` attribute
|
||||
|
||||
| `@RequestHeader`
|
||||
| Add a request header or multiple headers. The argument may be a `Map<String, ?>` or
|
||||
`MultiValueMap<String, ?>` with multiple headers, a `Collection<?>` of values, or an
|
||||
individual value. Type conversion is supported for non-String values.
|
||||
|
||||
| `@PathVariable`
|
||||
| Add a variable for expand a placeholder in the request URL. The argument may be a
|
||||
`Map<String, ?>` with multiple variables, or an individual value. Type conversion
|
||||
is supported for non-String values.
|
||||
|
||||
| `@RequestBody`
|
||||
| Provide the body of the request either as an Object to be serialized, or a
|
||||
Reactive Streams `Publisher` such as `Mono`, `Flux`, or any other async type supported
|
||||
through the configured `ReactiveAdapterRegistry`.
|
||||
|
||||
| `@RequestParam`
|
||||
| Add a request parameter or multiple parameters. The argument may be a `Map<String, ?>`
|
||||
or `MultiValueMap<String, ?>` with multiple parameters, a `Collection<?>` of values, or
|
||||
an individual value. Type conversion is supported for non-String values.
|
||||
|
||||
When `"content-type"` is set to `"application/x-www-form-urlencoded"`, request
|
||||
parameters are encoded in the request body. Otherwise, they are added as URL query
|
||||
parameters.
|
||||
|
||||
| `@RequestPart`
|
||||
| Add a request part, which may be a String (form field), `Resource` (file part),
|
||||
Object (entity to be encoded, e.g. as JSON), `HttpEntity` (part content and headers),
|
||||
a Spring `Part`, or Reactive Streams `Publisher` of any of the above.
|
||||
|
||||
| `@CookieValue`
|
||||
| Add a cookie or multiple cookies. The argument may be a `Map<String, ?>` or
|
||||
`MultiValueMap<String, ?>` with multiple cookies, a `Collection<?>` of values, or an
|
||||
individual value. Type conversion is supported for non-String values.
|
||||
|
||||
|===
|
||||
|
||||
|
||||
[[rest-http-interface-return-values]]
|
||||
=== Return Values
|
||||
|
||||
Annotated, HTTP exchange methods support the following return values:
|
||||
|
||||
[cols="1,2", options="header"]
|
||||
|===
|
||||
| Method return value | Description
|
||||
|
||||
| `void`, `Mono<Void>`
|
||||
| Perform the given request, and release the response content, if any.
|
||||
|
||||
| `HttpHeaders`, `Mono<HttpHeaders>`
|
||||
| Perform the given request, release the response content, if any, and return the
|
||||
response headers.
|
||||
|
||||
| `<T>`, `Mono<T>`
|
||||
| Perform the given request and decode the response content to the declared return type.
|
||||
|
||||
| `<T>`, `Flux<T>`
|
||||
| Perform the given request and decode the response content to a stream of the declared
|
||||
element type.
|
||||
|
||||
| `ResponseEntity<Void>`, `Mono<ResponseEntity<Void>>`
|
||||
| Perform the given request, and release the response content, if any, and return a
|
||||
`ResponseEntity` with the status and headers.
|
||||
|
||||
| `ResponseEntity<T>`, `Mono<ResponseEntity<T>>`
|
||||
| Perform the given request, decode the response content to the declared return type, and
|
||||
return a `ResponseEntity` with the status, headers, and the decoded body.
|
||||
|
||||
| `Mono<ResponseEntity<Flux<T>>`
|
||||
| Perform the given request, decode the response content to a stream of the declared
|
||||
element type, and return a `ResponseEntity` with the status, headers, and the decoded
|
||||
response body stream.
|
||||
|
||||
|===
|
||||
|
||||
TIP: You can also use any other async or reactive types registered in the
|
||||
`ReactiveAdapterRegistry`.
|
||||
|
||||
|
||||
[[rest-http-interface-exceptions]]
|
||||
=== Exception Handling
|
||||
|
||||
By default, `WebClient` raises `WebClientResponseException` for 4xx and 5xx HTTP status
|
||||
codes. To customize this, you can register a response status handler that applies to all
|
||||
responses performed through the client:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
WebClient webClient = WebClient.builder()
|
||||
.defaultStatusHandler(HttpStatusCode::isError, resp -> ...)
|
||||
.build();
|
||||
|
||||
WebClientAdapter clientAdapter = WebClientAdapter.forClient(webClient);
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory
|
||||
.builder(clientAdapter).build();
|
||||
----
|
||||
|
||||
For more details and options, such as suppressing error status codes, see the Javadoc of
|
||||
`defaultStatusHandler` in `WebClient.Builder`.
|
||||
@@ -0,0 +1,970 @@
|
||||
[[scheduling]]
|
||||
= Task Execution and Scheduling
|
||||
|
||||
The Spring Framework provides abstractions for the asynchronous execution and scheduling of
|
||||
tasks with the `TaskExecutor` and `TaskScheduler` interfaces, respectively. Spring also
|
||||
features implementations of those interfaces that support thread pools or delegation to
|
||||
CommonJ within an application server environment. Ultimately, the use of these
|
||||
implementations behind the common interfaces abstracts away the differences between Java
|
||||
SE 5, Java SE 6, and Jakarta EE environments.
|
||||
|
||||
Spring also features integration classes to support scheduling with the `Timer`
|
||||
(part of the JDK since 1.3) and the https://www.quartz-scheduler.org/[Quartz Scheduler].
|
||||
You can set up both of those schedulers by using a `FactoryBean` with optional references to
|
||||
`Timer` or `Trigger` instances, respectively. Furthermore, a convenience class for both
|
||||
the Quartz Scheduler and the `Timer` is available that lets you invoke a method of
|
||||
an existing target object (analogous to the normal `MethodInvokingFactoryBean`
|
||||
operation).
|
||||
|
||||
|
||||
|
||||
[[scheduling-task-executor]]
|
||||
== The Spring `TaskExecutor` Abstraction
|
||||
|
||||
Executors are the JDK name for the concept of thread pools. The "`executor`" naming is
|
||||
due to the fact that there is no guarantee that the underlying implementation is
|
||||
actually a pool. An executor may be single-threaded or even synchronous. Spring's
|
||||
abstraction hides implementation details between the Java SE and Jakarta EE environments.
|
||||
|
||||
Spring's `TaskExecutor` interface is identical to the `java.util.concurrent.Executor`
|
||||
interface. In fact, originally, its primary reason for existence was to abstract away
|
||||
the need for Java 5 when using thread pools. The interface has a single method
|
||||
(`execute(Runnable task)`) that accepts a task for execution based on the semantics
|
||||
and configuration of the thread pool.
|
||||
|
||||
The `TaskExecutor` was originally created to give other Spring components an abstraction
|
||||
for thread pooling where needed. Components such as the `ApplicationEventMulticaster`,
|
||||
JMS's `AbstractMessageListenerContainer`, and Quartz integration all use the
|
||||
`TaskExecutor` abstraction to pool threads. However, if your beans need thread pooling
|
||||
behavior, you can also use this abstraction for your own needs.
|
||||
|
||||
|
||||
[[scheduling-task-executor-types]]
|
||||
=== `TaskExecutor` Types
|
||||
|
||||
Spring includes a number of pre-built implementations of `TaskExecutor`.
|
||||
In all likelihood, you should never need to implement your own.
|
||||
The variants that Spring provides are as follows:
|
||||
|
||||
* `SyncTaskExecutor`:
|
||||
This implementation does not run invocations asynchronously. Instead, each
|
||||
invocation takes place in the calling thread. It is primarily used in situations
|
||||
where multi-threading is not necessary, such as in simple test cases.
|
||||
* `SimpleAsyncTaskExecutor`:
|
||||
This implementation does not reuse any threads. Rather, it starts up a new thread
|
||||
for each invocation. However, it does support a concurrency limit that blocks
|
||||
any invocations that are over the limit until a slot has been freed up. If you
|
||||
are looking for true pooling, see `ThreadPoolTaskExecutor`, later in this list.
|
||||
* `ConcurrentTaskExecutor`:
|
||||
This implementation is an adapter for a `java.util.concurrent.Executor` instance.
|
||||
There is an alternative (`ThreadPoolTaskExecutor`) that exposes the `Executor`
|
||||
configuration parameters as bean properties. There is rarely a need to use
|
||||
`ConcurrentTaskExecutor` directly. However, if the `ThreadPoolTaskExecutor` is not
|
||||
flexible enough for your needs, `ConcurrentTaskExecutor` is an alternative.
|
||||
* `ThreadPoolTaskExecutor`:
|
||||
This implementation is most commonly used. It exposes bean properties for
|
||||
configuring a `java.util.concurrent.ThreadPoolExecutor` and wraps it in a `TaskExecutor`.
|
||||
If you need to adapt to a different kind of `java.util.concurrent.Executor`, we
|
||||
recommend that you use a `ConcurrentTaskExecutor` instead.
|
||||
* `DefaultManagedTaskExecutor`:
|
||||
This implementation uses a JNDI-obtained `ManagedExecutorService` in a JSR-236
|
||||
compatible runtime environment (such as a Jakarta EE application server),
|
||||
replacing a CommonJ WorkManager for that purpose.
|
||||
|
||||
|
||||
[[scheduling-task-executor-usage]]
|
||||
=== Using a `TaskExecutor`
|
||||
|
||||
Spring's `TaskExecutor` implementations are used as simple JavaBeans. In the following example,
|
||||
we define a bean that uses the `ThreadPoolTaskExecutor` to asynchronously print
|
||||
out a set of messages:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
|
||||
public class TaskExecutorExample {
|
||||
|
||||
private class MessagePrinterTask implements Runnable {
|
||||
|
||||
private String message;
|
||||
|
||||
public MessagePrinterTask(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
System.out.println(message);
|
||||
}
|
||||
}
|
||||
|
||||
private TaskExecutor taskExecutor;
|
||||
|
||||
public TaskExecutorExample(TaskExecutor taskExecutor) {
|
||||
this.taskExecutor = taskExecutor;
|
||||
}
|
||||
|
||||
public void printMessages() {
|
||||
for(int i = 0; i < 25; i++) {
|
||||
taskExecutor.execute(new MessagePrinterTask("Message" + i));
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
As you can see, rather than retrieving a thread from the pool and executing it yourself,
|
||||
you add your `Runnable` to the queue. Then the `TaskExecutor` uses its internal rules to
|
||||
decide when the task gets run.
|
||||
|
||||
To configure the rules that the `TaskExecutor` uses, we expose simple bean properties:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
|
||||
<property name="corePoolSize" value="5"/>
|
||||
<property name="maxPoolSize" value="10"/>
|
||||
<property name="queueCapacity" value="25"/>
|
||||
</bean>
|
||||
|
||||
<bean id="taskExecutorExample" class="TaskExecutorExample">
|
||||
<constructor-arg ref="taskExecutor"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[scheduling-task-scheduler]]
|
||||
== The Spring `TaskScheduler` Abstraction
|
||||
|
||||
In addition to the `TaskExecutor` abstraction, Spring has a `TaskScheduler` SPI with a
|
||||
variety of methods for scheduling tasks to run at some point in the future. The following
|
||||
listing shows the `TaskScheduler` interface definition:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface TaskScheduler {
|
||||
|
||||
Clock getClock();
|
||||
|
||||
ScheduledFuture schedule(Runnable task, Trigger trigger);
|
||||
|
||||
ScheduledFuture schedule(Runnable task, Instant startTime);
|
||||
|
||||
ScheduledFuture scheduleAtFixedRate(Runnable task, Instant startTime, Duration period);
|
||||
|
||||
ScheduledFuture scheduleAtFixedRate(Runnable task, Duration period);
|
||||
|
||||
ScheduledFuture scheduleWithFixedDelay(Runnable task, Instant startTime, Duration delay);
|
||||
|
||||
ScheduledFuture scheduleWithFixedDelay(Runnable task, Duration delay);
|
||||
|
||||
----
|
||||
|
||||
The simplest method is the one named `schedule` that takes only a `Runnable` and an `Instant`.
|
||||
That causes the task to run once after the specified time. All of the other methods
|
||||
are capable of scheduling tasks to run repeatedly. The fixed-rate and fixed-delay
|
||||
methods are for simple, periodic execution, but the method that accepts a `Trigger` is
|
||||
much more flexible.
|
||||
|
||||
|
||||
[[scheduling-trigger-interface]]
|
||||
=== `Trigger` Interface
|
||||
|
||||
The `Trigger` interface is essentially inspired by JSR-236. The basic idea of the
|
||||
`Trigger` is that execution times may be determined based on past execution outcomes or
|
||||
even arbitrary conditions. If these determinations take into account the outcome of the
|
||||
preceding execution, that information is available within a `TriggerContext`. The
|
||||
`Trigger` interface itself is quite simple, as the following listing shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface Trigger {
|
||||
|
||||
Instant nextExecution(TriggerContext triggerContext);
|
||||
}
|
||||
----
|
||||
|
||||
The `TriggerContext` is the most important part. It encapsulates all of
|
||||
the relevant data and is open for extension in the future, if necessary. The
|
||||
`TriggerContext` is an interface (a `SimpleTriggerContext` implementation is used by
|
||||
default). The following listing shows the available methods for `Trigger` implementations.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface TriggerContext {
|
||||
|
||||
Clock getClock();
|
||||
|
||||
Instant lastScheduledExecution();
|
||||
|
||||
Instant lastActualExecution();
|
||||
|
||||
Instant lastCompletion();
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
[[scheduling-trigger-implementations]]
|
||||
=== `Trigger` Implementations
|
||||
|
||||
Spring provides two implementations of the `Trigger` interface. The most interesting one
|
||||
is the `CronTrigger`. It enables the scheduling of tasks based on
|
||||
<<scheduling-cron-expression,cron expressions>>.
|
||||
For example, the following task is scheduled to run 15 minutes past each hour but only
|
||||
during the 9-to-5 "business hours" on weekdays:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim"]
|
||||
----
|
||||
scheduler.schedule(task, new CronTrigger("0 15 9-17 * * MON-FRI"));
|
||||
----
|
||||
|
||||
The other implementation is a `PeriodicTrigger` that accepts a fixed
|
||||
period, an optional initial delay value, and a boolean to indicate whether the period
|
||||
should be interpreted as a fixed-rate or a fixed-delay. Since the `TaskScheduler`
|
||||
interface already defines methods for scheduling tasks at a fixed rate or with a
|
||||
fixed delay, those methods should be used directly whenever possible. The value of the
|
||||
`PeriodicTrigger` implementation is that you can use it within components that rely on
|
||||
the `Trigger` abstraction. For example, it may be convenient to allow periodic triggers,
|
||||
cron-based triggers, and even custom trigger implementations to be used interchangeably.
|
||||
Such a component could take advantage of dependency injection so that you can configure such `Triggers`
|
||||
externally and, therefore, easily modify or extend them.
|
||||
|
||||
|
||||
[[scheduling-task-scheduler-implementations]]
|
||||
=== `TaskScheduler` implementations
|
||||
|
||||
As with Spring's `TaskExecutor` abstraction, the primary benefit of the `TaskScheduler`
|
||||
arrangement is that an application's scheduling needs are decoupled from the deployment
|
||||
environment. This abstraction level is particularly relevant when deploying to an
|
||||
application server environment where threads should not be created directly by the
|
||||
application itself. For such scenarios, Spring provides a `TimerManagerTaskScheduler`
|
||||
that delegates to a CommonJ `TimerManager` on WebLogic or WebSphere as well as a more recent
|
||||
`DefaultManagedTaskScheduler` that delegates to a JSR-236 `ManagedScheduledExecutorService`
|
||||
in a Jakarta EE environment. Both are typically configured with a JNDI lookup.
|
||||
|
||||
Whenever external thread management is not a requirement, a simpler alternative is
|
||||
a local `ScheduledExecutorService` setup within the application, which can be adapted
|
||||
through Spring's `ConcurrentTaskScheduler`. As a convenience, Spring also provides a
|
||||
`ThreadPoolTaskScheduler`, which internally delegates to a `ScheduledExecutorService`
|
||||
to provide common bean-style configuration along the lines of `ThreadPoolTaskExecutor`.
|
||||
These variants work perfectly fine for locally embedded thread pool setups in lenient
|
||||
application server environments, as well -- in particular on Tomcat and Jetty.
|
||||
|
||||
|
||||
|
||||
[[scheduling-annotation-support]]
|
||||
== Annotation Support for Scheduling and Asynchronous Execution
|
||||
|
||||
Spring provides annotation support for both task scheduling and asynchronous method
|
||||
execution.
|
||||
|
||||
|
||||
[[scheduling-enable-annotation-support]]
|
||||
=== Enable Scheduling Annotations
|
||||
|
||||
To enable support for `@Scheduled` and `@Async` annotations, you can add `@EnableScheduling` and
|
||||
`@EnableAsync` to one of your `@Configuration` classes, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableAsync
|
||||
@EnableScheduling
|
||||
public class AppConfig {
|
||||
}
|
||||
----
|
||||
|
||||
You can pick and choose the relevant annotations for your application. For example,
|
||||
if you need only support for `@Scheduled`, you can omit `@EnableAsync`. For more
|
||||
fine-grained control, you can additionally implement the `SchedulingConfigurer`
|
||||
interface, the `AsyncConfigurer` interface, or both. See the
|
||||
{api-spring-framework}/scheduling/annotation/SchedulingConfigurer.html[`SchedulingConfigurer`]
|
||||
and {api-spring-framework}/scheduling/annotation/AsyncConfigurer.html[`AsyncConfigurer`]
|
||||
javadoc for full details.
|
||||
|
||||
If you prefer XML configuration, you can use the `<task:annotation-driven>` element,
|
||||
as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<task:annotation-driven executor="myExecutor" scheduler="myScheduler"/>
|
||||
<task:executor id="myExecutor" pool-size="5"/>
|
||||
<task:scheduler id="myScheduler" pool-size="10"/>
|
||||
----
|
||||
|
||||
Note that, with the preceding XML, an executor reference is provided for handling those
|
||||
tasks that correspond to methods with the `@Async` annotation, and the scheduler
|
||||
reference is provided for managing those methods annotated with `@Scheduled`.
|
||||
|
||||
NOTE: The default advice mode for processing `@Async` annotations is `proxy` which allows
|
||||
for interception of calls through the proxy only. Local calls within the same class
|
||||
cannot get intercepted that way. For a more advanced mode of interception, consider
|
||||
switching to `aspectj` mode in combination with compile-time or load-time weaving.
|
||||
|
||||
|
||||
[[scheduling-annotation-support-scheduled]]
|
||||
=== The `@Scheduled` annotation
|
||||
|
||||
You can add the `@Scheduled` annotation to a method, along with trigger metadata. For
|
||||
example, the following method is invoked every five seconds (5000 milliseconds) with a
|
||||
fixed delay, meaning that the period is measured from the completion time of each
|
||||
preceding invocation.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Scheduled(fixedDelay = 5000)
|
||||
public void doSomething() {
|
||||
// something that should run periodically
|
||||
}
|
||||
----
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
By default, milliseconds will be used as the time unit for fixed delay, fixed rate, and
|
||||
initial delay values. If you would like to use a different time unit such as seconds or
|
||||
minutes, you can configure this via the `timeUnit` attribute in `@Scheduled`.
|
||||
|
||||
For example, the previous example can also be written as follows.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Scheduled(fixedDelay = 5, timeUnit = TimeUnit.SECONDS)
|
||||
public void doSomething() {
|
||||
// something that should run periodically
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
If you need a fixed-rate execution, you can use the `fixedRate` attribute within the
|
||||
annotation. The following method is invoked every five seconds (measured between the
|
||||
successive start times of each invocation).
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Scheduled(fixedRate = 5, timeUnit = TimeUnit.SECONDS)
|
||||
public void doSomething() {
|
||||
// something that should run periodically
|
||||
}
|
||||
----
|
||||
|
||||
For fixed-delay and fixed-rate tasks, you can specify an initial delay by indicating the
|
||||
amount of time to wait before the first execution of the method, as the following
|
||||
`fixedRate` example shows.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Scheduled(initialDelay = 1000, fixedRate = 5000)
|
||||
public void doSomething() {
|
||||
// something that should run periodically
|
||||
}
|
||||
----
|
||||
|
||||
If simple periodic scheduling is not expressive enough, you can provide a
|
||||
<<scheduling-cron-expression,cron expression>>.
|
||||
The following example runs only on weekdays:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim"]
|
||||
----
|
||||
@Scheduled(cron="*/5 * * * * MON-FRI")
|
||||
public void doSomething() {
|
||||
// something that should run on weekdays only
|
||||
}
|
||||
----
|
||||
|
||||
TIP: You can also use the `zone` attribute to specify the time zone in which the cron
|
||||
expression is resolved.
|
||||
|
||||
Notice that the methods to be scheduled must have void returns and must not accept any
|
||||
arguments. If the method needs to interact with other objects from the application
|
||||
context, those would typically have been provided through dependency injection.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
As of Spring Framework 4.3, `@Scheduled` methods are supported on beans of any scope.
|
||||
|
||||
Make sure that you are not initializing multiple instances of the same `@Scheduled`
|
||||
annotation class at runtime, unless you do want to schedule callbacks to each such
|
||||
instance. Related to this, make sure that you do not use `@Configurable` on bean
|
||||
classes that are annotated with `@Scheduled` and registered as regular Spring beans
|
||||
with the container. Otherwise, you would get double initialization (once through the
|
||||
container and once through the `@Configurable` aspect), with the consequence of each
|
||||
`@Scheduled` method being invoked twice.
|
||||
====
|
||||
|
||||
|
||||
[[scheduling-annotation-support-async]]
|
||||
=== The `@Async` annotation
|
||||
|
||||
You can provide the `@Async` annotation on a method so that invocation of that method
|
||||
occurs asynchronously. In other words, the caller returns immediately upon
|
||||
invocation, while the actual execution of the method occurs in a task that has been
|
||||
submitted to a Spring `TaskExecutor`. In the simplest case, you can apply the annotation
|
||||
to a method that returns `void`, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Async
|
||||
void doSomething() {
|
||||
// this will be run asynchronously
|
||||
}
|
||||
----
|
||||
|
||||
Unlike the methods annotated with the `@Scheduled` annotation, these methods can expect
|
||||
arguments, because they are invoked in the "`normal`" way by callers at runtime rather
|
||||
than from a scheduled task being managed by the container. For example, the following code is
|
||||
a legitimate application of the `@Async` annotation:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Async
|
||||
void doSomething(String s) {
|
||||
// this will be run asynchronously
|
||||
}
|
||||
----
|
||||
|
||||
Even methods that return a value can be invoked asynchronously. However, such methods
|
||||
are required to have a `Future`-typed return value. This still provides the benefit of
|
||||
asynchronous execution so that the caller can perform other tasks prior to calling
|
||||
`get()` on that `Future`. The following example shows how to use `@Async` on a method
|
||||
that returns a value:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Async
|
||||
Future<String> returnSomething(int i) {
|
||||
// this will be run asynchronously
|
||||
}
|
||||
----
|
||||
|
||||
TIP: `@Async` methods may not only declare a regular `java.util.concurrent.Future` return type
|
||||
but also Spring's `org.springframework.util.concurrent.ListenableFuture` or, as of Spring
|
||||
4.2, JDK 8's `java.util.concurrent.CompletableFuture`, for richer interaction with the
|
||||
asynchronous task and for immediate composition with further processing steps.
|
||||
|
||||
You can not use `@Async` in conjunction with lifecycle callbacks such as
|
||||
`@PostConstruct`. To asynchronously initialize Spring beans, you currently have to use
|
||||
a separate initializing Spring bean that then invokes the `@Async` annotated method on the
|
||||
target, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public class SampleBeanImpl implements SampleBean {
|
||||
|
||||
@Async
|
||||
void doSomething() {
|
||||
// ...
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class SampleBeanInitializer {
|
||||
|
||||
private final SampleBean bean;
|
||||
|
||||
public SampleBeanInitializer(SampleBean bean) {
|
||||
this.bean = bean;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void initialize() {
|
||||
bean.doSomething();
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
NOTE: There is no direct XML equivalent for `@Async`, since such methods should be designed
|
||||
for asynchronous execution in the first place, not externally re-declared to be asynchronous.
|
||||
However, you can manually set up Spring's `AsyncExecutionInterceptor` with Spring AOP,
|
||||
in combination with a custom pointcut.
|
||||
|
||||
|
||||
[[scheduling-annotation-support-qualification]]
|
||||
=== Executor Qualification with `@Async`
|
||||
|
||||
By default, when specifying `@Async` on a method, the executor that is used is the
|
||||
one <<scheduling-enable-annotation-support, configured when enabling async support>>,
|
||||
i.e. the "`annotation-driven`" element if you are using XML or your `AsyncConfigurer`
|
||||
implementation, if any. However, you can use the `value` attribute of the `@Async`
|
||||
annotation when you need to indicate that an executor other than the default should be
|
||||
used when executing a given method. The following example shows how to do so:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Async("otherExecutor")
|
||||
void doSomething(String s) {
|
||||
// this will be run asynchronously by "otherExecutor"
|
||||
}
|
||||
----
|
||||
|
||||
In this case, `"otherExecutor"` can be the name of any `Executor` bean in the Spring
|
||||
container, or it may be the name of a qualifier associated with any `Executor` (for example, as
|
||||
specified with the `<qualifier>` element or Spring's `@Qualifier` annotation).
|
||||
|
||||
|
||||
[[scheduling-annotation-support-exception]]
|
||||
=== Exception Management with `@Async`
|
||||
|
||||
When an `@Async` method has a `Future`-typed return value, it is easy to manage
|
||||
an exception that was thrown during the method execution, as this exception is
|
||||
thrown when calling `get` on the `Future` result. With a `void` return type,
|
||||
however, the exception is uncaught and cannot be transmitted. You can provide an
|
||||
`AsyncUncaughtExceptionHandler` to handle such exceptions. The following example shows
|
||||
how to do so:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {
|
||||
|
||||
@Override
|
||||
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
|
||||
// handle exception
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
By default, the exception is merely logged. You can define a custom `AsyncUncaughtExceptionHandler`
|
||||
by using `AsyncConfigurer` or the `<task:annotation-driven/>` XML element.
|
||||
|
||||
|
||||
|
||||
[[scheduling-task-namespace]]
|
||||
== The `task` Namespace
|
||||
|
||||
As of version 3.0, Spring includes an XML namespace for configuring `TaskExecutor` and
|
||||
`TaskScheduler` instances. It also provides a convenient way to configure tasks to be
|
||||
scheduled with a trigger.
|
||||
|
||||
|
||||
[[scheduling-task-namespace-scheduler]]
|
||||
=== The 'scheduler' Element
|
||||
|
||||
The following element creates a `ThreadPoolTaskScheduler` instance with the
|
||||
specified thread pool size:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<task:scheduler id="scheduler" pool-size="10"/>
|
||||
----
|
||||
|
||||
The value provided for the `id` attribute is used as the prefix for thread names
|
||||
within the pool. The `scheduler` element is relatively straightforward. If you do not
|
||||
provide a `pool-size` attribute, the default thread pool has only a single thread.
|
||||
There are no other configuration options for the scheduler.
|
||||
|
||||
|
||||
[[scheduling-task-namespace-executor]]
|
||||
=== The `executor` Element
|
||||
|
||||
The following creates a `ThreadPoolTaskExecutor` instance:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<task:executor id="executor" pool-size="10"/>
|
||||
----
|
||||
|
||||
As with the scheduler shown in the <<scheduling-task-namespace-scheduler, previous section>>,
|
||||
the value provided for the `id` attribute is used as the prefix for thread names within
|
||||
the pool. As far as the pool size is concerned, the `executor` element supports more
|
||||
configuration options than the `scheduler` element. For one thing, the thread pool for
|
||||
a `ThreadPoolTaskExecutor` is itself more configurable. Rather than only a single size,
|
||||
an executor's thread pool can have different values for the core and the max size.
|
||||
If you provide a single value, the executor has a fixed-size thread pool (the core and
|
||||
max sizes are the same). However, the `executor` element's `pool-size` attribute also
|
||||
accepts a range in the form of `min-max`. The following example sets a minimum value of
|
||||
`5` and a maximum value of `25`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<task:executor
|
||||
id="executorWithPoolSizeRange"
|
||||
pool-size="5-25"
|
||||
queue-capacity="100"/>
|
||||
----
|
||||
|
||||
In the preceding configuration, a `queue-capacity` value has also been provided.
|
||||
The configuration of the thread pool should also be considered in light of the
|
||||
executor's queue capacity. For the full description of the relationship between pool
|
||||
size and queue capacity, see the documentation for
|
||||
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ThreadPoolExecutor.html[`ThreadPoolExecutor`].
|
||||
The main idea is that, when a task is submitted, the executor first tries to use a
|
||||
free thread if the number of active threads is currently less than the core size.
|
||||
If the core size has been reached, the task is added to the queue, as long as its
|
||||
capacity has not yet been reached. Only then, if the queue's capacity has been
|
||||
reached, does the executor create a new thread beyond the core size. If the max size
|
||||
has also been reached, then the executor rejects the task.
|
||||
|
||||
By default, the queue is unbounded, but this is rarely the desired configuration,
|
||||
because it can lead to `OutOfMemoryErrors` if enough tasks are added to that queue while
|
||||
all pool threads are busy. Furthermore, if the queue is unbounded, the max size has
|
||||
no effect at all. Since the executor always tries the queue before creating a new
|
||||
thread beyond the core size, a queue must have a finite capacity for the thread pool to
|
||||
grow beyond the core size (this is why a fixed-size pool is the only sensible case
|
||||
when using an unbounded queue).
|
||||
|
||||
Consider the case, as mentioned above, when a task is rejected. By default, when a
|
||||
task is rejected, a thread pool executor throws a `TaskRejectedException`. However,
|
||||
the rejection policy is actually configurable. The exception is thrown when using
|
||||
the default rejection policy, which is the `AbortPolicy` implementation.
|
||||
For applications where some tasks can be skipped under heavy load, you can instead
|
||||
configure either `DiscardPolicy` or `DiscardOldestPolicy`. Another option that works
|
||||
well for applications that need to throttle the submitted tasks under heavy load is
|
||||
the `CallerRunsPolicy`. Instead of throwing an exception or discarding tasks,
|
||||
that policy forces the thread that is calling the submit method to run the task itself.
|
||||
The idea is that such a caller is busy while running that task and not able to submit
|
||||
other tasks immediately. Therefore, it provides a simple way to throttle the incoming
|
||||
load while maintaining the limits of the thread pool and queue. Typically, this allows
|
||||
the executor to "`catch up`" on the tasks it is handling and thereby frees up some
|
||||
capacity on the queue, in the pool, or both. You can choose any of these options from an
|
||||
enumeration of values available for the `rejection-policy` attribute on the `executor`
|
||||
element.
|
||||
|
||||
The following example shows an `executor` element with a number of attributes to specify
|
||||
various behaviors:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<task:executor
|
||||
id="executorWithCallerRunsPolicy"
|
||||
pool-size="5-25"
|
||||
queue-capacity="100"
|
||||
rejection-policy="CALLER_RUNS"/>
|
||||
----
|
||||
|
||||
Finally, the `keep-alive` setting determines the time limit (in seconds) for which threads
|
||||
may remain idle before being stopped. If there are more than the core number of threads
|
||||
currently in the pool, after waiting this amount of time without processing a task, excess
|
||||
threads get stopped. A time value of zero causes excess threads to stop
|
||||
immediately after executing a task without remaining follow-up work in the task queue.
|
||||
The following example sets the `keep-alive` value to two minutes:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<task:executor
|
||||
id="executorWithKeepAlive"
|
||||
pool-size="5-25"
|
||||
keep-alive="120"/>
|
||||
----
|
||||
|
||||
|
||||
[[scheduling-task-namespace-scheduled-tasks]]
|
||||
=== The 'scheduled-tasks' Element
|
||||
|
||||
The most powerful feature of Spring's task namespace is the support for configuring
|
||||
tasks to be scheduled within a Spring Application Context. This follows an approach
|
||||
similar to other "`method-invokers`" in Spring, such as that provided by the JMS namespace
|
||||
for configuring message-driven POJOs. Basically, a `ref` attribute can point to any
|
||||
Spring-managed object, and the `method` attribute provides the name of a method to be
|
||||
invoked on that object. The following listing shows a simple example:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<task:scheduled-tasks scheduler="myScheduler">
|
||||
<task:scheduled ref="beanA" method="methodA" fixed-delay="5000"/>
|
||||
</task:scheduled-tasks>
|
||||
|
||||
<task:scheduler id="myScheduler" pool-size="10"/>
|
||||
----
|
||||
|
||||
The scheduler is referenced by the outer element, and each individual
|
||||
task includes the configuration of its trigger metadata. In the preceding example, that
|
||||
metadata defines a periodic trigger with a fixed delay indicating the number of
|
||||
milliseconds to wait after each task execution has completed. Another option is
|
||||
`fixed-rate`, indicating how often the method should be run regardless of how long
|
||||
any previous execution takes. Additionally, for both `fixed-delay` and `fixed-rate` tasks, you can specify an
|
||||
'initial-delay' parameter, indicating the number of milliseconds to wait
|
||||
before the first execution of the method. For more control, you can instead provide a `cron` attribute
|
||||
to provide a <<scheduling-cron-expression,cron expression>>.
|
||||
The following example shows these other options:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim"]
|
||||
----
|
||||
<task:scheduled-tasks scheduler="myScheduler">
|
||||
<task:scheduled ref="beanA" method="methodA" fixed-delay="5000" initial-delay="1000"/>
|
||||
<task:scheduled ref="beanB" method="methodB" fixed-rate="5000"/>
|
||||
<task:scheduled ref="beanC" method="methodC" cron="*/5 * * * * MON-FRI"/>
|
||||
</task:scheduled-tasks>
|
||||
|
||||
<task:scheduler id="myScheduler" pool-size="10"/>
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[scheduling-cron-expression]]
|
||||
== Cron Expressions
|
||||
|
||||
All Spring cron expressions have to conform to the same format, whether you are using them in
|
||||
<<scheduling-annotation-support-scheduled,`@Scheduled` annotations>>,
|
||||
<<scheduling-task-namespace-scheduled-tasks,`task:scheduled-tasks` elements>>,
|
||||
or someplace else.
|
||||
A well-formed cron expression, such as `* * * * * *`, consists of six space-separated time and date
|
||||
fields, each with its own range of valid values:
|
||||
|
||||
|
||||
....
|
||||
┌───────────── second (0-59)
|
||||
│ ┌───────────── minute (0 - 59)
|
||||
│ │ ┌───────────── hour (0 - 23)
|
||||
│ │ │ ┌───────────── day of the month (1 - 31)
|
||||
│ │ │ │ ┌───────────── month (1 - 12) (or JAN-DEC)
|
||||
│ │ │ │ │ ┌───────────── day of the week (0 - 7)
|
||||
│ │ │ │ │ │ (0 or 7 is Sunday, or MON-SUN)
|
||||
│ │ │ │ │ │
|
||||
* * * * * *
|
||||
....
|
||||
|
||||
There are some rules that apply:
|
||||
|
||||
* A field may be an asterisk (`*`), which always stands for "`first-last`".
|
||||
For the day-of-the-month or day-of-the-week fields, a question mark (`?`) may be used instead of an
|
||||
asterisk.
|
||||
* Commas (`,`) are used to separate items of a list.
|
||||
* Two numbers separated with a hyphen (`-`) express a range of numbers.
|
||||
The specified range is inclusive.
|
||||
* Following a range (or `*`) with `/` specifies the interval of the number's value through the range.
|
||||
* English names can also be used for the month and day-of-week fields.
|
||||
Use the first three letters of the particular day or month (case does not matter).
|
||||
* The day-of-month and day-of-week fields can contain an `L` character, which has a different meaning.
|
||||
** In the day-of-month field, `L` stands for _the last day of the month_.
|
||||
If followed by a negative offset (that is, `L-n`), it means _``n``th-to-last day of the month_.
|
||||
** In the day-of-week field, `L` stands for _the last day of the week_.
|
||||
If prefixed by a number or three-letter name (`dL` or `DDDL`), it means _the last day of week (`d`
|
||||
or `DDD`) in the month_.
|
||||
* The day-of-month field can be `nW`, which stands for _the nearest weekday to day of the month ``n``_.
|
||||
If `n` falls on Saturday, this yields the Friday before it.
|
||||
If `n` falls on Sunday, this yields the Monday after, which also happens if `n` is `1` and falls on
|
||||
a Saturday (that is: `1W` stands for _the first weekday of the month_).
|
||||
* If the day-of-month field is `LW`, it means _the last weekday of the month_.
|
||||
* The day-of-week field can be `d#n` (or `DDD#n`), which stands for _the ``n``th day of week `d`
|
||||
(or ``DDD``) in the month_.
|
||||
|
||||
Here are some examples:
|
||||
|
||||
|===
|
||||
| Cron Expression | Meaning
|
||||
|
||||
|`0 0 * * * *` | top of every hour of every day
|
||||
|`*/10 * * * * *` | every ten seconds
|
||||
| `0 0 8-10 * * *` | 8, 9 and 10 o'clock of every day
|
||||
| `0 0 6,19 * * *` | 6:00 AM and 7:00 PM every day
|
||||
| `0 0/30 8-10 * * *` | 8:00, 8:30, 9:00, 9:30, 10:00 and 10:30 every day
|
||||
| `0 0 9-17 * * MON-FRI`| on the hour nine-to-five weekdays
|
||||
| `0 0 0 25 DEC ?` | every Christmas Day at midnight
|
||||
| `0 0 0 L * *` | last day of the month at midnight
|
||||
| `0 0 0 L-3 * *` | third-to-last day of the month at midnight
|
||||
| `0 0 0 * * 5L` | last Friday of the month at midnight
|
||||
| `0 0 0 * * THUL` | last Thursday of the month at midnight
|
||||
| `0 0 0 1W * *` | first weekday of the month at midnight
|
||||
| `0 0 0 LW * *` | last weekday of the month at midnight
|
||||
| `0 0 0 ? * 5#2` | the second Friday in the month at midnight
|
||||
| `0 0 0 ? * MON#1` | the first Monday in the month at midnight
|
||||
|===
|
||||
|
||||
=== Macros
|
||||
|
||||
Expressions such as `0 0 * * * *` are hard for humans to parse and are, therefore, hard to fix in case of bugs.
|
||||
To improve readability, Spring supports the following macros, which represent commonly used sequences.
|
||||
You can use these macros instead of the six-digit value, thus: `@Scheduled(cron = "@hourly")`.
|
||||
|
||||
|===
|
||||
|Macro | Meaning
|
||||
|
||||
| `@yearly` (or `@annually`) | once a year (`0 0 0 1 1 *`)
|
||||
| `@monthly` | once a month (`0 0 0 1 * *`)
|
||||
| `@weekly` | once a week (`0 0 0 * * 0`)
|
||||
| `@daily` (or `@midnight`) | once a day (`0 0 0 * * *`), or
|
||||
| `@hourly` | once an hour, (`0 0 * * * *`)
|
||||
|===
|
||||
|
||||
|
||||
|
||||
[[scheduling-quartz]]
|
||||
== Using the Quartz Scheduler
|
||||
|
||||
Quartz uses `Trigger`, `Job`, and `JobDetail` objects to realize scheduling of all kinds
|
||||
of jobs. For the basic concepts behind Quartz, see the
|
||||
https://www.quartz-scheduler.org/[Quartz Web site]. For convenience purposes, Spring
|
||||
offers a couple of classes that simplify using Quartz within Spring-based applications.
|
||||
|
||||
|
||||
[[scheduling-quartz-jobdetail]]
|
||||
=== Using the `JobDetailFactoryBean`
|
||||
|
||||
Quartz `JobDetail` objects contain all the information needed to run a job. Spring provides a
|
||||
`JobDetailFactoryBean`, which provides bean-style properties for XML configuration purposes.
|
||||
Consider the following example:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean name="exampleJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
|
||||
<property name="jobClass" value="example.ExampleJob"/>
|
||||
<property name="jobDataAsMap">
|
||||
<map>
|
||||
<entry key="timeout" value="5"/>
|
||||
</map>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
The job detail configuration has all the information it needs to run the job (`ExampleJob`).
|
||||
The timeout is specified in the job data map. The job data map is available through the
|
||||
`JobExecutionContext` (passed to you at execution time), but the `JobDetail` also gets
|
||||
its properties from the job data mapped to properties of the job instance. So, in the following example,
|
||||
the `ExampleJob` contains a bean property named `timeout`, and the `JobDetail`
|
||||
has it applied automatically:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package example;
|
||||
|
||||
public class ExampleJob extends QuartzJobBean {
|
||||
|
||||
private int timeout;
|
||||
|
||||
/**
|
||||
* Setter called after the ExampleJob is instantiated
|
||||
* with the value from the JobDetailFactoryBean.
|
||||
*/
|
||||
public void setTimeout(int timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException {
|
||||
// do the actual work
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
All additional properties from the job data map are available to you as well.
|
||||
|
||||
NOTE: By using the `name` and `group` properties, you can modify the name and the group
|
||||
of the job, respectively. By default, the name of the job matches the bean name
|
||||
of the `JobDetailFactoryBean` (`exampleJob` in the preceding example above).
|
||||
|
||||
|
||||
[[scheduling-quartz-method-invoking-job]]
|
||||
=== Using the `MethodInvokingJobDetailFactoryBean`
|
||||
|
||||
Often you merely need to invoke a method on a specific object. By using the
|
||||
`MethodInvokingJobDetailFactoryBean`, you can do exactly this, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
|
||||
<property name="targetObject" ref="exampleBusinessObject"/>
|
||||
<property name="targetMethod" value="doIt"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
The preceding example results in the `doIt` method being called on the
|
||||
`exampleBusinessObject` method, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public class ExampleBusinessObject {
|
||||
|
||||
// properties and collaborators
|
||||
|
||||
public void doIt() {
|
||||
// do the actual work
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="exampleBusinessObject" class="examples.ExampleBusinessObject"/>
|
||||
----
|
||||
|
||||
By using the `MethodInvokingJobDetailFactoryBean`, you need not create one-line jobs
|
||||
that merely invoke a method. You need only create the actual business object and
|
||||
wire up the detail object.
|
||||
|
||||
By default, Quartz Jobs are stateless, resulting in the possibility of jobs interfering
|
||||
with each other. If you specify two triggers for the same `JobDetail`, it is possible
|
||||
that the second one starts before the first job has finished. If `JobDetail` classes
|
||||
implement the `Stateful` interface, this does not happen: the second job does not start
|
||||
before the first one has finished.
|
||||
|
||||
To make jobs resulting from the `MethodInvokingJobDetailFactoryBean` be non-concurrent,
|
||||
set the `concurrent` flag to `false`, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
|
||||
<property name="targetObject" ref="exampleBusinessObject"/>
|
||||
<property name="targetMethod" value="doIt"/>
|
||||
<property name="concurrent" value="false"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
NOTE: By default, jobs will run in a concurrent fashion.
|
||||
|
||||
|
||||
[[scheduling-quartz-cron]]
|
||||
=== Wiring up Jobs by Using Triggers and `SchedulerFactoryBean`
|
||||
|
||||
We have created job details and jobs. We have also reviewed the convenience bean that lets
|
||||
you invoke a method on a specific object. Of course, we still need to schedule the
|
||||
jobs themselves. This is done by using triggers and a `SchedulerFactoryBean`. Several
|
||||
triggers are available within Quartz, and Spring offers two Quartz `FactoryBean`
|
||||
implementations with convenient defaults: `CronTriggerFactoryBean` and
|
||||
`SimpleTriggerFactoryBean`.
|
||||
|
||||
Triggers need to be scheduled. Spring offers a `SchedulerFactoryBean` that exposes
|
||||
triggers to be set as properties. `SchedulerFactoryBean` schedules the actual jobs with
|
||||
those triggers.
|
||||
|
||||
The following listing uses both a `SimpleTriggerFactoryBean` and a `CronTriggerFactoryBean`:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim"]
|
||||
----
|
||||
<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
|
||||
<!-- see the example of method invoking job above -->
|
||||
<property name="jobDetail" ref="jobDetail"/>
|
||||
<!-- 10 seconds -->
|
||||
<property name="startDelay" value="10000"/>
|
||||
<!-- repeat every 50 seconds -->
|
||||
<property name="repeatInterval" value="50000"/>
|
||||
</bean>
|
||||
|
||||
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
|
||||
<property name="jobDetail" ref="exampleJob"/>
|
||||
<!-- run every morning at 6 AM -->
|
||||
<property name="cronExpression" value="0 0 6 * * ?"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
The preceding example sets up two triggers, one running every 50 seconds with a starting delay of 10
|
||||
seconds and one running every morning at 6 AM. To finalize everything, we need to set up the
|
||||
`SchedulerFactoryBean`, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
|
||||
<property name="triggers">
|
||||
<list>
|
||||
<ref bean="cronTrigger"/>
|
||||
<ref bean="simpleTrigger"/>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
More properties are available for the `SchedulerFactoryBean`, such as the calendars used by the
|
||||
job details, properties to customize Quartz with, and a Spring-provided JDBC DataSource. See
|
||||
the {api-spring-framework}/scheduling/quartz/SchedulerFactoryBean.html[`SchedulerFactoryBean`]
|
||||
javadoc for more information.
|
||||
|
||||
NOTE: `SchedulerFactoryBean` also recognizes a `quartz.properties` file in the classpath,
|
||||
based on Quartz property keys, as with regular Quartz configuration. Please note that many
|
||||
`SchedulerFactoryBean` settings interact with common Quartz settings in the properties file;
|
||||
it is therefore not recommended to specify values at both levels. For example, do not set
|
||||
an "org.quartz.jobStore.class" property if you mean to rely on a Spring-provided DataSource,
|
||||
or specify an `org.springframework.scheduling.quartz.LocalDataSourceJobStore` variant which
|
||||
is a full-fledged replacement for the standard `org.quartz.impl.jdbcjobstore.JobStoreTX`.
|
||||
|
||||
@@ -33,7 +33,7 @@ implement. Note that this interface is defined in plain Java. Dependent objects
|
||||
are injected with a reference to the `Messenger` do not know that the underlying
|
||||
implementation is a Groovy script. The following listing shows the `Messenger` interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.scripting;
|
||||
|
||||
@@ -45,7 +45,7 @@ implementation is a Groovy script. The following listing shows the `Messenger` i
|
||||
|
||||
The following example defines a class that has a dependency on the `Messenger` interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.scripting;
|
||||
|
||||
@@ -65,15 +65,14 @@ The following example defines a class that has a dependency on the `Messenger` i
|
||||
|
||||
The following example implements the `Messenger` interface in Groovy:
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,quotes"]
|
||||
[source,groovy,indent=0,subs="verbatim,quotes",chomp="-packages",fold="none"]
|
||||
----
|
||||
// from the file 'Messenger.groovy'
|
||||
package org.springframework.scripting.groovy;
|
||||
package org.springframework.scripting.groovy
|
||||
|
||||
// import the Messenger interface (written in Java) that is to be implemented
|
||||
// Import the Messenger interface (written in Java) that is to be implemented
|
||||
import org.springframework.scripting.Messenger
|
||||
|
||||
// define the implementation in Groovy
|
||||
// Define the implementation in Groovy in file 'Messenger.groovy'
|
||||
class GroovyMessenger implements Messenger {
|
||||
|
||||
String message
|
||||
@@ -276,7 +275,7 @@ surrounded by quotation marks. The following listing shows the changes that you
|
||||
(the developer) should make to the `Messenger.groovy` source file when the
|
||||
execution of the program is paused:
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,quotes"]
|
||||
[source,groovy,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.scripting
|
||||
|
||||
@@ -331,13 +330,13 @@ feature works:
|
||||
<lang:groovy id="messenger">
|
||||
<lang:inline-script>
|
||||
|
||||
package org.springframework.scripting.groovy;
|
||||
package org.springframework.scripting.groovy
|
||||
|
||||
import org.springframework.scripting.Messenger
|
||||
import org.springframework.scripting.Messenger
|
||||
|
||||
class GroovyMessenger implements Messenger {
|
||||
String message
|
||||
}
|
||||
class GroovyMessenger implements Messenger {
|
||||
String message
|
||||
}
|
||||
|
||||
</lang:inline-script>
|
||||
<lang:property name="message" value="I Can Do The Frug" />
|
||||
@@ -363,13 +362,13 @@ constructors and properties 100% clear, the following mixture of code and config
|
||||
does not work:
|
||||
|
||||
.An approach that cannot work
|
||||
[source,groovy,indent=0,subs="verbatim,quotes"]
|
||||
[source,groovy,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
// from the file 'Messenger.groovy'
|
||||
package org.springframework.scripting.groovy;
|
||||
package org.springframework.scripting.groovy
|
||||
|
||||
import org.springframework.scripting.Messenger
|
||||
|
||||
// from the file 'Messenger.groovy'
|
||||
class GroovyMessenger implements Messenger {
|
||||
|
||||
GroovyMessenger() {}
|
||||
@@ -420,7 +419,7 @@ If you have read this chapter straight from the top, you have already
|
||||
<<dynamic-language-a-first-example, seen an example>> of a Groovy-dynamic-language-backed
|
||||
bean. Now consider another example (again using an example from the Spring test suite):
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.scripting;
|
||||
|
||||
@@ -432,11 +431,11 @@ bean. Now consider another example (again using an example from the Spring test
|
||||
|
||||
The following example implements the `Calculator` interface in Groovy:
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,quotes"]
|
||||
[source,groovy,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
// from the file 'calculator.groovy'
|
||||
package org.springframework.scripting.groovy
|
||||
|
||||
// from the file 'calculator.groovy'
|
||||
class GroovyCalculator implements Calculator {
|
||||
|
||||
int add(int x, int y) {
|
||||
@@ -457,7 +456,7 @@ The following bean definition uses the calculator defined in Groovy:
|
||||
|
||||
Finally, the following small application exercises the preceding configuration:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.scripting;
|
||||
|
||||
@@ -596,7 +595,7 @@ Now we can show a fully working example of using a BeanShell-based bean that imp
|
||||
the `Messenger` interface that was defined earlier in this chapter. We again show the
|
||||
definition of the `Messenger` interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.scripting;
|
||||
|
||||
@@ -676,9 +675,8 @@ beans, you have to enable the "`refreshable beans`" functionality. See
|
||||
The following example shows an `org.springframework.web.servlet.mvc.Controller` implemented
|
||||
by using the Groovy dynamic language:
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,quotes"]
|
||||
[source,groovy,indent=0,subs="verbatim,quotes",chomp="-packages"]
|
||||
----
|
||||
// from the file '/WEB-INF/groovy/FortuneController.groovy'
|
||||
package org.springframework.showcase.fortune.web
|
||||
|
||||
import org.springframework.showcase.fortune.service.FortuneService
|
||||
@@ -689,6 +687,7 @@ by using the Groovy dynamic language:
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
|
||||
// from the file '/WEB-INF/groovy/FortuneController.groovy'
|
||||
class FortuneController implements Controller {
|
||||
|
||||
@Property FortuneService fortuneService
|
||||
|
||||
@@ -20,6 +20,9 @@ Feel free to join the #spring channel of https://slack.kotlinlang.org/[Kotlin Sl
|
||||
or ask a question with `spring` and `kotlin` as tags on
|
||||
https://stackoverflow.com/questions/tagged/spring+kotlin[Stackoverflow] if you need support.
|
||||
|
||||
|
||||
|
||||
|
||||
[[kotlin-requirements]]
|
||||
== Requirements
|
||||
|
||||
@@ -37,6 +40,9 @@ for serializing or deserializing JSON data for Kotlin classes with Jackson, so m
|
||||
`com.fasterxml.jackson.module:jackson-module-kotlin` dependency to your project if you have such need.
|
||||
It is automatically registered when found in the classpath.
|
||||
|
||||
|
||||
|
||||
|
||||
[[kotlin-extensions]]
|
||||
== Extensions
|
||||
|
||||
@@ -80,6 +86,9 @@ With Kotlin and the Spring Framework extensions, you can instead write the follo
|
||||
As in Java, `users` in Kotlin is strongly typed, but Kotlin's clever type inference allows
|
||||
for shorter syntax.
|
||||
|
||||
|
||||
|
||||
|
||||
[[kotlin-null-safety]]
|
||||
== Null-safety
|
||||
|
||||
@@ -115,6 +124,9 @@ NOTE: Generic type arguments, varargs, and array elements nullability are not su
|
||||
but should be in an upcoming release. See https://github.com/Kotlin/KEEP/issues/79[this discussion]
|
||||
for up-to-date information.
|
||||
|
||||
|
||||
|
||||
|
||||
[[kotlin-classes-interfaces]]
|
||||
== Classes and Interfaces
|
||||
|
||||
@@ -124,12 +136,16 @@ with default values.
|
||||
|
||||
Kotlin parameter names are recognized through a dedicated `KotlinReflectionParameterNameDiscoverer`,
|
||||
which allows finding interface method parameter names without requiring the Java 8 `-parameters`
|
||||
compiler flag to be enabled during compilation.
|
||||
compiler flag to be enabled during compilation. (For completeness, we nevertheless recommend
|
||||
running the Kotlin compiler with its `-java-parameters` flag for standard Java parameter exposure.)
|
||||
|
||||
You can declare configuration classes as
|
||||
https://kotlinlang.org/docs/reference/nested-classes.html[top level or nested but not inner],
|
||||
since the later requires a reference to the outer class.
|
||||
|
||||
|
||||
|
||||
|
||||
[[kotlin-annotations]]
|
||||
== Annotations
|
||||
|
||||
@@ -156,6 +172,9 @@ https://kotlinlang.org/docs/reference/annotations.html#annotation-use-site-targe
|
||||
such as `@field:NotNull` or `@get:Size(min=5, max=15)`, as described in
|
||||
https://stackoverflow.com/a/35853200/1092077[this Stack Overflow response].
|
||||
|
||||
|
||||
|
||||
|
||||
[[kotlin-bean-definition-dsl]]
|
||||
== Bean Definition DSL
|
||||
|
||||
@@ -263,16 +282,20 @@ as the following example shows:
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
NOTE: Spring Boot is based on JavaConfig and
|
||||
https://github.com/spring-projects/spring-boot/issues/8115[does not yet provide specific support for functional bean definition],
|
||||
but you can experimentally use functional bean definitions through Spring Boot's `ApplicationContextInitializer` support.
|
||||
See https://stackoverflow.com/questions/45935931/how-to-use-functional-bean-definition-kotlin-dsl-with-spring-boot-and-spring-w/46033685#46033685[this Stack Overflow answer]
|
||||
for more details and up-to-date information. See also the experimental Kofu DSL developed in https://github.com/spring-projects/spring-fu[Spring Fu incubator].
|
||||
|
||||
|
||||
|
||||
|
||||
[[kotlin-web]]
|
||||
== Web
|
||||
|
||||
|
||||
|
||||
=== Router DSL
|
||||
|
||||
Spring Framework comes with a Kotlin router DSL available in 3 flavors:
|
||||
@@ -314,6 +337,8 @@ when you need to register routes depending on dynamic data (for example, from a
|
||||
|
||||
See https://github.com/mixitconf/mixit/[MiXiT project] for a concrete example.
|
||||
|
||||
|
||||
|
||||
=== MockMvc DSL
|
||||
|
||||
A Kotlin DSL is provided via `MockMvc` Kotlin extensions in order to provide a more
|
||||
@@ -339,6 +364,8 @@ mockMvc.get("/person/{name}", "Lee") {
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
|
||||
=== Kotlin Script Templates
|
||||
|
||||
Spring Framework provides a
|
||||
@@ -357,9 +384,7 @@ dependencies {
|
||||
}
|
||||
----
|
||||
|
||||
Configuration is usually done with `ScriptTemplateConfigurer` and `ScriptTemplateViewResolver`
|
||||
beans.
|
||||
|
||||
Configuration is usually done with `ScriptTemplateConfigurer` and `ScriptTemplateViewResolver` beans.
|
||||
|
||||
`KotlinScriptConfiguration.kt`
|
||||
[source,kotlin,indent=0]
|
||||
@@ -386,6 +411,8 @@ class KotlinScriptConfiguration {
|
||||
See the https://github.com/sdeleuze/kotlin-script-templating[kotlin-script-templating] example
|
||||
project for more details.
|
||||
|
||||
|
||||
|
||||
=== Kotlin multiplatform serialization
|
||||
|
||||
As of Spring Framework 5.3, https://github.com/Kotlin/kotlinx.serialization[Kotlin multiplatform serialization] is
|
||||
@@ -397,6 +424,9 @@ Kotlin serialization is designed to serialize only Kotlin classes annotated with
|
||||
With Spring Messaging (RSocket), make sure that neither Jackson, GSON or JSONB are in the classpath if you want automatic configuration,
|
||||
if Jackson is needed configure `KotlinSerializationJsonMessageConverter` manually.
|
||||
|
||||
|
||||
|
||||
|
||||
== Coroutines
|
||||
|
||||
Kotlin https://kotlinlang.org/docs/reference/coroutines-overview.html[Coroutines] are Kotlin
|
||||
@@ -415,6 +445,8 @@ Spring Framework provides support for Coroutines on the following scope:
|
||||
* Suspending function and `Flow` support in RSocket `@MessageMapping` annotated methods
|
||||
* Extensions for {docs-spring-framework}/kdoc-api/spring-messaging/org.springframework.messaging.rsocket/index.html[`RSocketRequester`]
|
||||
|
||||
|
||||
|
||||
=== Dependencies
|
||||
|
||||
Coroutines support is enabled when `kotlinx-coroutines-core` and `kotlinx-coroutines-reactor`
|
||||
@@ -432,6 +464,8 @@ dependencies {
|
||||
|
||||
Version `1.4.0` and above are supported.
|
||||
|
||||
|
||||
|
||||
=== How Reactive translates to Coroutines?
|
||||
|
||||
For return values, the translation from Reactive to Coroutines APIs is the following:
|
||||
@@ -458,6 +492,8 @@ https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coro
|
||||
Read this blog post about https://spring.io/blog/2019/04/12/going-reactive-with-spring-coroutines-and-kotlin-flow[Going Reactive with Spring, Coroutines and Kotlin Flow]
|
||||
for more details, including how to run code concurrently with Coroutines.
|
||||
|
||||
|
||||
|
||||
=== Controllers
|
||||
|
||||
Here is an example of a Coroutines `@RestController`.
|
||||
@@ -554,6 +590,8 @@ class CoroutinesViewController(banner: Banner) {
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
|
||||
=== WebFlux.fn
|
||||
|
||||
Here is an example of Coroutines router defined via the {docs-spring-framework}/kdoc-api/spring-webflux/org.springframework.web.reactive.function.server/co-router.html[coRouter { }] DSL and related handlers.
|
||||
@@ -587,6 +625,8 @@ class UserHandler(builder: WebClient.Builder) {
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
|
||||
=== Transactions
|
||||
|
||||
Transactions on Coroutines are supported via the programmatic variant of the Reactive
|
||||
@@ -636,6 +676,8 @@ For Kotlin `Flow`, a `Flow<T>.transactional` extension is provided.
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
||||
[[kotlin-spring-projects-in-kotlin]]
|
||||
== Spring Projects in Kotlin
|
||||
|
||||
@@ -683,6 +725,8 @@ NOTE: The Kotlin code samples in Spring Framework documentation do not explicitl
|
||||
`open` on the classes and their member functions. The samples are written for projects
|
||||
using the `kotlin-allopen` plugin, since this is the most commonly used setup.
|
||||
|
||||
|
||||
|
||||
=== Using Immutable Class Instances for Persistence
|
||||
|
||||
In Kotlin, it is convenient and considered to be a best practice to declare read-only properties
|
||||
@@ -726,6 +770,8 @@ NOTE: As of the Kay release train, Spring Data supports Kotlin immutable class i
|
||||
does not require the `kotlin-noarg` plugin if the module uses Spring Data object mappings
|
||||
(such as MongoDB, Redis, Cassandra, and others).
|
||||
|
||||
|
||||
|
||||
=== Injecting Dependencies
|
||||
|
||||
Our recommendation is to try to favor constructor injection with `val` read-only (and
|
||||
@@ -761,6 +807,8 @@ as the following example shows:
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
|
||||
=== Injecting Configuration Properties
|
||||
|
||||
In Java, you can inject configuration properties by using annotations (such as pass:q[`@Value("${property}")`)].
|
||||
@@ -801,6 +849,7 @@ that uses the `${...}` syntax, with configuration beans, as the following exampl
|
||||
----
|
||||
|
||||
|
||||
|
||||
=== Checked Exceptions
|
||||
|
||||
Java and https://kotlinlang.org/docs/reference/exceptions.html[Kotlin exception handling]
|
||||
@@ -813,6 +862,8 @@ To get the original exception thrown like in Java, methods should be annotated w
|
||||
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.jvm/-throws/index.html[`@Throws`]
|
||||
to specify explicitly the checked exceptions thrown (for example `@Throws(IOException::class)`).
|
||||
|
||||
|
||||
|
||||
=== Annotation Array Attributes
|
||||
|
||||
Kotlin annotations are mostly similar to Java annotations, but array attributes (which are
|
||||
@@ -857,6 +908,8 @@ use a shortcut annotation, such as `@GetMapping`, `@PostMapping`, and others.
|
||||
NOTE: If the `@RequestMapping` `method` attribute is not specified, all HTTP methods will
|
||||
be matched, not only the `GET` method.
|
||||
|
||||
|
||||
|
||||
=== Testing
|
||||
|
||||
This section addresses testing with the combination of Kotlin and Spring Framework.
|
||||
@@ -866,6 +919,7 @@ https://mockk.io/[Mockk] for mocking.
|
||||
NOTE: If you are using Spring Boot, see
|
||||
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-kotlin-testing[this related documentation].
|
||||
|
||||
|
||||
==== Constructor injection
|
||||
|
||||
As described in the <<testing#testcontext-junit-jupiter-di, dedicated section>>,
|
||||
@@ -887,6 +941,7 @@ class OrderServiceIntegrationTests(val orderService: OrderService,
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
==== `PER_CLASS` Lifecycle
|
||||
|
||||
Kotlin lets you specify meaningful test function names between backticks (```).
|
||||
@@ -930,6 +985,7 @@ class IntegrationTests {
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
==== Specification-like Tests
|
||||
|
||||
You can create specification-like tests with JUnit 5 and Kotlin.
|
||||
@@ -959,6 +1015,7 @@ class SpecificationLikeTests {
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
[[kotlin-webtestclient-issue]]
|
||||
==== `WebTestClient` Type Inference Issue in Kotlin
|
||||
|
||||
@@ -968,17 +1025,24 @@ since it provides a workaround for the Kotlin issue with the Java API.
|
||||
|
||||
See also the related https://jira.spring.io/browse/SPR-16057[SPR-16057] issue.
|
||||
|
||||
|
||||
|
||||
|
||||
[[kotlin-getting-started]]
|
||||
== Getting Started
|
||||
|
||||
The easiest way to learn how to build a Spring application with Kotlin is to follow
|
||||
https://spring.io/guides/tutorials/spring-boot-kotlin/[the dedicated tutorial].
|
||||
|
||||
|
||||
|
||||
=== `start.spring.io`
|
||||
|
||||
The easiest way to start a new Spring Framework project in Kotlin is to create a new Spring
|
||||
Boot 2 project on https://start.spring.io/#!language=kotlin&type=gradle-project[start.spring.io].
|
||||
|
||||
|
||||
|
||||
=== Choosing the Web Flavor
|
||||
|
||||
Spring Framework now comes with two different web stacks: <<web#mvc, Spring MVC>> and
|
||||
@@ -991,6 +1055,9 @@ Kotlin DSL.
|
||||
For other use cases, especially if you are using blocking technologies such as JPA, Spring
|
||||
MVC and its annotation-based programming model is the recommended choice.
|
||||
|
||||
|
||||
|
||||
|
||||
[[kotlin-resources]]
|
||||
== Resources
|
||||
|
||||
@@ -1004,6 +1071,8 @@ Kotlin and the Spring Framework:
|
||||
* https://blog.jetbrains.com/kotlin/[Kotlin blog]
|
||||
* https://kotlin.link/[Awesome Kotlin]
|
||||
|
||||
|
||||
|
||||
=== Examples
|
||||
|
||||
The following Github projects offer examples that you can learn from and possibly even extend:
|
||||
@@ -1016,6 +1085,8 @@ The following Github projects offer examples that you can learn from and possibl
|
||||
* https://github.com/sdeleuze/spring-kotlin-deepdive[spring-kotlin-deepdive]: A step-by-step migration guide for Boot 1.0 and Java to Boot 2.0 and Kotlin
|
||||
* https://github.com/spring-cloud/spring-cloud-gcp/tree/master/spring-cloud-gcp-kotlin-samples/spring-cloud-gcp-kotlin-app-sample[spring-cloud-gcp-kotlin-app-sample]: Spring Boot with Google Cloud Platform Integrations
|
||||
|
||||
|
||||
|
||||
=== Issues
|
||||
|
||||
The following list categorizes the pending issues related to Spring and Kotlin support:
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
:noheader:
|
||||
= Spring Framework Documentation
|
||||
:toc:
|
||||
:toclevels: 4
|
||||
:tabsize: 4
|
||||
include::attributes.adoc[]
|
||||
= Spring Framework Documentation
|
||||
Rod Johnson; Juergen Hoeller; Keith Donald; Colin Sampaleanu; Rob Harrop; Thomas Risberg; Alef Arendsen; Darren Davison; Dmitriy Kopylenko; Mark Pollack; Thierry Templier; Erwin Vervaet; Portia Tung; Ben Hale; Adrian Colyer; John Lewis; Costin Leau; Mark Fisher; Sam Brannen; Ramnivas Laddad; Arjen Poutsma; Chris Beams; Tareq Abedrabbo; Andy Clement; Dave Syer; Oliver Gierke; Rossen Stoyanchev; Phillip Webb; Rob Winch; Brian Clozel; Stephane Nicoll; Sebastien Deleuze; Jay Bryant; Mark Paluch
|
||||
|
||||
NOTE: This documentation is also available in {docs-spring-framework}/reference/html/index.html[HTML] format.
|
||||
|
||||
[[legal]]
|
||||
== Legal
|
||||
|
||||
Copyright © 2002 - 2023 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.
|
||||
|
||||
include::overview.adoc[leveloffset=+1]
|
||||
include::core.adoc[leveloffset=+1]
|
||||
@@ -11,16 +24,3 @@ include::web-reactive.adoc[leveloffset=+1]
|
||||
include::integration.adoc[leveloffset=+1]
|
||||
include::languages.adoc[leveloffset=+1]
|
||||
include::appendix.adoc[leveloffset=+1]
|
||||
|
||||
Rod Johnson, Juergen Hoeller, Keith Donald, Colin Sampaleanu, Rob Harrop, Thomas Risberg,
|
||||
Alef Arendsen, Darren Davison, Dmitriy Kopylenko, Mark Pollack, Thierry Templier, Erwin
|
||||
Vervaet, Portia Tung, Ben Hale, Adrian Colyer, John Lewis, Costin Leau, Mark Fisher, Sam
|
||||
Brannen, Ramnivas Laddad, Arjen Poutsma, Chris Beams, Tareq Abedrabbo, Andy Clement, Dave
|
||||
Syer, Oliver Gierke, Rossen Stoyanchev, Phillip Webb, Rob Winch, Brian Clozel, Stephane
|
||||
Nicoll, Sebastien Deleuze, Jay Bryant, Mark Paluch
|
||||
|
||||
Copyright © 2002 - 2022 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Copies of this document may be made for your own use and for distribution to others,
|
||||
provided that you do not charge any fee for such copies and further provided that each
|
||||
copy contains this Copyright Notice, whether distributed in print or electronically.
|
||||
|
||||
@@ -117,6 +117,55 @@ logic but without running a server. The following example shows how to do so:
|
||||
// Test code that uses the above RestTemplate ...
|
||||
----
|
||||
|
||||
In some cases it may be necessary to perform an actual call to a remote service instead
|
||||
of mocking the response. The following example shows how to do that through
|
||||
`ExecutingResponseCreator`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
// Create ExecutingResponseCreator with the original request factory
|
||||
ExecutingResponseCreator withActualResponse = new ExecutingResponseCreator(restTemplate.getRequestFactory());
|
||||
|
||||
MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build();
|
||||
mockServer.expect(requestTo("/profile")).andRespond(withSuccess());
|
||||
mockServer.expect(requestTo("/quoteOfTheDay")).andRespond(withActualResponse);
|
||||
|
||||
// Test code that uses the above RestTemplate ...
|
||||
|
||||
mockServer.verify();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val restTemplate = RestTemplate()
|
||||
|
||||
// Create ExecutingResponseCreator with the original request factory
|
||||
val withActualResponse = new ExecutingResponseCreator(restTemplate.getRequestFactory())
|
||||
|
||||
val mockServer = MockRestServiceServer.bindTo(restTemplate).build()
|
||||
mockServer.expect(requestTo("/profile")).andRespond(withSuccess())
|
||||
mockServer.expect(requestTo("/quoteOfTheDay")).andRespond(withActualResponse)
|
||||
|
||||
// Test code that uses the above RestTemplate ...
|
||||
|
||||
mockServer.verify()
|
||||
----
|
||||
|
||||
In the preceding example, we create the `ExecutingResponseCreator` using the
|
||||
`ClientHttpRequestFactory` from the `RestTemplate` _before_ `MockRestServiceServer` replaces
|
||||
it with a different one that mocks responses.
|
||||
Then we define expectations with two kinds of responses:
|
||||
|
||||
* a stub `200` response for the `/profile` endpoint (no actual request will be executed)
|
||||
* a response obtained through a call to the `/quoteOfTheDay` endpoint
|
||||
|
||||
In the second case, the request is executed through the `ClientHttpRequestFactory` that was
|
||||
captured earlier. This generates a response that could e.g. come from an actual remote server,
|
||||
depending on how the `RestTemplate` was originally configured.
|
||||
|
||||
[[spring-mvc-test-client-static-imports]]
|
||||
== Static Imports
|
||||
|
||||
|
||||
@@ -445,6 +445,17 @@ all failures will be tracked and reported.
|
||||
content().contentType("application/json;charset=UTF-8"));
|
||||
----
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.test.web.servlet.get
|
||||
|
||||
mockMvc.get("/accounts/1").andExpectAll {
|
||||
status { isOk() }
|
||||
content { contentType(APPLICATION_JSON) }
|
||||
}
|
||||
----
|
||||
|
||||
`MockMvcResultMatchers.*` provides a number of expectations, some of which are further
|
||||
nested with more detailed expectations.
|
||||
|
||||
@@ -961,7 +972,7 @@ leads to a number of additional challenges:
|
||||
* Testing can become slow, since each test would need to ensure that the database is in
|
||||
the correct state.
|
||||
* Since our database needs to be in a specific state, we cannot run tests in parallel.
|
||||
* Performing assertions on such items as auto-generated ids, timestamps, and others can
|
||||
* Performing assertions on such items as auto-generated IDs, timestamps, and others can
|
||||
be difficult.
|
||||
|
||||
These challenges do not mean that we should abandon end-to-end integration testing
|
||||
@@ -1429,6 +1440,7 @@ Now we can use WebDriver as we normally would but without the need to deploy our
|
||||
application to a Servlet container. For example, we can request the view to create a
|
||||
message with the following:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -1440,9 +1452,11 @@ message with the following:
|
||||
----
|
||||
val page = CreateMessagePage.to(driver)
|
||||
----
|
||||
--
|
||||
|
||||
We can then fill out the form and submit it to create a message, as follows:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -1456,6 +1470,7 @@ We can then fill out the form and submit it to create a message, as follows:
|
||||
val viewMessagePage =
|
||||
page.createMessage(ViewMessagePage::class, expectedSummary, expectedText)
|
||||
----
|
||||
--
|
||||
|
||||
This improves on the design of our <<spring-mvc-test-server-htmlunit-mah-usage, HtmlUnit test>>
|
||||
by leveraging the Page Object Pattern. As we mentioned in
|
||||
@@ -1463,18 +1478,17 @@ by leveraging the Page Object Pattern. As we mentioned in
|
||||
with HtmlUnit, but it is much easier with WebDriver. Consider the following
|
||||
`CreateMessagePage` implementation:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class CreateMessagePage
|
||||
extends AbstractPage { // <1>
|
||||
public class CreateMessagePage extends AbstractPage { // <1>
|
||||
|
||||
// <2>
|
||||
private WebElement summary;
|
||||
private WebElement text;
|
||||
|
||||
// <3>
|
||||
@FindBy(css = "input[type=submit]")
|
||||
@FindBy(css = "input[type=submit]") // <3>
|
||||
private WebElement submit;
|
||||
|
||||
public CreateMessagePage(WebDriver driver) {
|
||||
@@ -1509,7 +1523,7 @@ by the `id` or `name` of the element within the HTML page.
|
||||
<3> We can use the
|
||||
https://github.com/SeleniumHQ/selenium/wiki/PageFactory#making-the-example-work-using-annotations[`@FindBy` annotation]
|
||||
to override the default lookup behavior. Our example shows how to use the `@FindBy`
|
||||
annotation to look up our submit button with a `css` selector (*input[type=submit]*).
|
||||
annotation to look up our submit button with a `css` selector (`input[type=submit]`).
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
@@ -1520,8 +1534,7 @@ annotation to look up our submit button with a `css` selector (*input[type=submi
|
||||
private lateinit var summary: WebElement
|
||||
private lateinit var text: WebElement
|
||||
|
||||
// <3>
|
||||
@FindBy(css = "input[type=submit]")
|
||||
@FindBy(css = "input[type=submit]") // <3>
|
||||
private lateinit var submit: WebElement
|
||||
|
||||
fun <T> createMessage(resultPage: Class<T>, summary: String, details: String): T {
|
||||
@@ -1554,11 +1567,12 @@ by the `id` or `name` of the element within the HTML page.
|
||||
https://github.com/SeleniumHQ/selenium/wiki/PageFactory#making-the-example-work-using-annotations[`@FindBy` annotation]
|
||||
to override the default lookup behavior. Our example shows how to use the `@FindBy`
|
||||
annotation to look up our submit button with a `css` selector (*input[type=submit]*).
|
||||
|
||||
--
|
||||
|
||||
Finally, we can verify that a new message was created successfully. The following
|
||||
assertions use the https://assertj.github.io/doc/[AssertJ] assertion library:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -1571,10 +1585,12 @@ assertions use the https://assertj.github.io/doc/[AssertJ] assertion library:
|
||||
assertThat(viewMessagePage.message).isEqualTo(expectedMessage)
|
||||
assertThat(viewMessagePage.success).isEqualTo("Successfully created a new message")
|
||||
----
|
||||
--
|
||||
|
||||
We can see that our `ViewMessagePage` lets us interact with our custom domain model. For
|
||||
example, it exposes a method that returns a `Message` object:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -1592,12 +1608,14 @@ example, it exposes a method that returns a `Message` object:
|
||||
----
|
||||
fun getMessage() = Message(getId(), getCreated(), getSummary(), getText())
|
||||
----
|
||||
--
|
||||
|
||||
We can then use the rich domain objects in our assertions.
|
||||
|
||||
Lastly, we must not forget to close the `WebDriver` instance when the test is complete,
|
||||
as follows:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -1619,6 +1637,7 @@ as follows:
|
||||
}
|
||||
}
|
||||
----
|
||||
--
|
||||
|
||||
For additional information on using WebDriver, see the Selenium
|
||||
https://github.com/SeleniumHQ/selenium/wiki/Getting-Started[WebDriver documentation].
|
||||
|
||||
@@ -631,7 +631,7 @@ path that represents a resource URL (i.e., a path prefixed with `classpath:`, `f
|
||||
@ExtendWith(SpringExtension.class)
|
||||
// ApplicationContext will be loaded from "/app-config.xml" and
|
||||
// "/test-config.xml" in the root of the classpath
|
||||
@ContextConfiguration(locations={"/app-config.xml", "/test-config.xml"}) // <1>
|
||||
@ContextConfiguration(locations = {"/app-config.xml", "/test-config.xml"}) // <1>
|
||||
class MyTest {
|
||||
// class body...
|
||||
}
|
||||
@@ -644,7 +644,7 @@ path that represents a resource URL (i.e., a path prefixed with `classpath:`, `f
|
||||
@ExtendWith(SpringExtension::class)
|
||||
// ApplicationContext will be loaded from "/app-config.xml" and
|
||||
// "/test-config.xml" in the root of the classpath
|
||||
@ContextConfiguration("/app-config.xml", "/test-config.xml") // <1>
|
||||
@ContextConfiguration(locations = ["/app-config.xml", "/test-config.xml"]) // <1>
|
||||
class MyTest {
|
||||
// class body...
|
||||
}
|
||||
@@ -667,7 +667,7 @@ demonstrated in the following example:
|
||||
// class body...
|
||||
}
|
||||
----
|
||||
<1> Specifying XML files without using the `location` attribute.
|
||||
<1> Specifying XML files without using the `locations` attribute.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
@@ -678,7 +678,7 @@ demonstrated in the following example:
|
||||
// class body...
|
||||
}
|
||||
----
|
||||
<1> Specifying XML files without using the `location` attribute.
|
||||
<1> Specifying XML files without using the `locations` attribute.
|
||||
|
||||
|
||||
If you omit both the `locations` and the `value` attributes from the
|
||||
@@ -743,6 +743,7 @@ The following example shows how to specify Groovy configuration files:
|
||||
// class body...
|
||||
}
|
||||
----
|
||||
<1> Specifying the location of Groovy configuration files.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
@@ -898,8 +899,7 @@ class:
|
||||
.Java
|
||||
----
|
||||
@SpringJUnitConfig <1>
|
||||
// ApplicationContext will be loaded from the
|
||||
// static nested Config class
|
||||
// ApplicationContext will be loaded from the static nested Config class
|
||||
class OrderServiceTest {
|
||||
|
||||
@Configuration
|
||||
@@ -2022,12 +2022,13 @@ properties.
|
||||
class ExampleIntegrationTests {
|
||||
|
||||
@Container
|
||||
static RedisContainer redis = new RedisContainer();
|
||||
static GenericContainer redis =
|
||||
new GenericContainer("redis:5.0.3-alpine").withExposedPorts(6379);
|
||||
|
||||
@DynamicPropertySource
|
||||
static void redisProperties(DynamicPropertyRegistry registry) {
|
||||
registry.add("redis.host", redis::getHost);
|
||||
registry.add("redis.port", redis::getMappedPort);
|
||||
registry.add("redis.port", redis::getFirstMappedPort);
|
||||
}
|
||||
|
||||
// tests ...
|
||||
@@ -2045,13 +2046,14 @@ properties.
|
||||
|
||||
@Container
|
||||
@JvmStatic
|
||||
val redis: RedisContainer = RedisContainer()
|
||||
val redis: GenericContainer =
|
||||
GenericContainer("redis:5.0.3-alpine").withExposedPorts(6379)
|
||||
|
||||
@DynamicPropertySource
|
||||
@JvmStatic
|
||||
fun redisProperties(registry: DynamicPropertyRegistry) {
|
||||
registry.add("redis.host", redis::getHost)
|
||||
registry.add("redis.port", redis::getMappedPort)
|
||||
registry.add("redis.port", redis::getFirstMappedPort)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -222,6 +222,7 @@ resource base path). The resource base path is used behind the scenes to create
|
||||
|
||||
The following example shows how to use the `@WebAppConfiguration` annotation:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -231,6 +232,7 @@ The following example shows how to use the `@WebAppConfiguration` annotation:
|
||||
// class body...
|
||||
}
|
||||
----
|
||||
<1> The `@WebAppConfiguration` annotation.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
@@ -242,6 +244,7 @@ The following example shows how to use the `@WebAppConfiguration` annotation:
|
||||
}
|
||||
----
|
||||
<1> The `@WebAppConfiguration` annotation.
|
||||
--
|
||||
|
||||
|
||||
To override the default, you can specify a different base resource path by using the
|
||||
@@ -249,6 +252,7 @@ implicit `value` attribute. Both `classpath:` and `file:` resource prefixes are
|
||||
supported. If no resource prefix is supplied, the path is assumed to be a file system
|
||||
resource. The following example shows how to specify a classpath resource:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -270,6 +274,7 @@ resource. The following example shows how to specify a classpath resource:
|
||||
}
|
||||
----
|
||||
<1> Specifying a classpath resource.
|
||||
--
|
||||
|
||||
|
||||
Note that `@WebAppConfiguration` must be used in conjunction with
|
||||
@@ -1104,7 +1109,7 @@ annotation. The following example shows how to declare an SQL group:
|
||||
@SqlGroup({ // <1>
|
||||
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),
|
||||
@Sql("/test-user-data.sql")
|
||||
)}
|
||||
})
|
||||
void userTest() {
|
||||
// run code that uses the test schema and test data
|
||||
}
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
.Java
|
||||
----
|
||||
UriComponents uriComponents = UriComponentsBuilder
|
||||
.fromUriString("https://example.com/hotels/{hotel}") // <1>
|
||||
.queryParam("q", "{q}") // <2>
|
||||
.fromUriString("https://example.com/hotels/{hotel}") // <1>
|
||||
.queryParam("q", "{q}") // <2>
|
||||
.encode() // <3>
|
||||
.build(); // <4>
|
||||
|
||||
URI uri = uriComponents.expand("Westin", "123").toUri(); // <5>
|
||||
URI uri = uriComponents.expand("Westin", "123").toUri(); // <5>
|
||||
----
|
||||
<1> Static factory method with a URI template.
|
||||
<2> Add or replace URI components.
|
||||
@@ -25,12 +25,12 @@
|
||||
.Kotlin
|
||||
----
|
||||
val uriComponents = UriComponentsBuilder
|
||||
.fromUriString("https://example.com/hotels/{hotel}") // <1>
|
||||
.queryParam("q", "{q}") // <2>
|
||||
.fromUriString("https://example.com/hotels/{hotel}") // <1>
|
||||
.queryParam("q", "{q}") // <2>
|
||||
.encode() // <3>
|
||||
.build() // <4>
|
||||
|
||||
val uri = uriComponents.expand("Westin", "123").toUri() // <5>
|
||||
val uri = uriComponents.expand("Westin", "123").toUri() // <5>
|
||||
----
|
||||
<1> Static factory method with a URI template.
|
||||
<2> Add or replace URI components.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[[webflux-cors]]
|
||||
= CORS
|
||||
[.small]#<<web.adoc#mvc-cors, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-cors, See equivalent in the Servlet stack>>#
|
||||
|
||||
Spring WebFlux lets you handle CORS (Cross-Origin Resource Sharing). This section
|
||||
describes how to do so.
|
||||
@@ -10,7 +10,7 @@ describes how to do so.
|
||||
|
||||
[[webflux-cors-intro]]
|
||||
== Introduction
|
||||
[.small]#<<web.adoc#mvc-cors-intro, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-cors-intro, See equivalent in the Servlet stack>>#
|
||||
|
||||
For security reasons, browsers prohibit AJAX calls to resources outside the current origin.
|
||||
For example, you could have your bank account in one tab and evil.com in another. Scripts
|
||||
@@ -27,7 +27,7 @@ powerful workarounds based on IFRAME or JSONP.
|
||||
|
||||
[[webflux-cors-processing]]
|
||||
== Processing
|
||||
[.small]#<<web.adoc#mvc-cors-processing, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-cors-processing, See equivalent in the Servlet stack>>#
|
||||
|
||||
The CORS specification distinguishes between preflight, simple, and actual requests.
|
||||
To learn how CORS works, you can read
|
||||
@@ -77,12 +77,13 @@ To learn more from the source or to make advanced customizations, see:
|
||||
|
||||
[[webflux-cors-controller]]
|
||||
== `@CrossOrigin`
|
||||
[.small]#<<web.adoc#mvc-cors-controller, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-cors-controller, See equivalent in the Servlet stack>>#
|
||||
|
||||
The {api-spring-framework}/web/bind/annotation/CrossOrigin.html[`@CrossOrigin`]
|
||||
annotation enables cross-origin requests on annotated controller methods, as the
|
||||
following example shows:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -121,6 +122,7 @@ following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
--
|
||||
|
||||
By default, `@CrossOrigin` allows:
|
||||
|
||||
@@ -139,6 +141,7 @@ the `allowOriginPatterns` property may be used to match to a dynamic set of orig
|
||||
`@CrossOrigin` is supported at the class level, too, and inherited by all methods.
|
||||
The following example specifies a certain domain and sets `maxAge` to an hour:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -177,10 +180,12 @@ The following example specifies a certain domain and sets `maxAge` to an hour:
|
||||
}
|
||||
}
|
||||
----
|
||||
--
|
||||
|
||||
You can use `@CrossOrigin` at both the class and the method level,
|
||||
as the following example shows:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -226,12 +231,13 @@ as the following example shows:
|
||||
----
|
||||
<1> Using `@CrossOrigin` at the class level.
|
||||
<2> Using `@CrossOrigin` at the method level.
|
||||
--
|
||||
|
||||
|
||||
|
||||
[[webflux-cors-global]]
|
||||
== Global Configuration
|
||||
[.small]#<<web.adoc#mvc-cors-global, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-cors-global, See equivalent in the Servlet stack>>#
|
||||
|
||||
In addition to fine-grained, controller method-level configuration, you probably want to
|
||||
define some global CORS configuration, too. You can set URL-based `CorsConfiguration`
|
||||
@@ -302,7 +308,7 @@ as the following example shows:
|
||||
|
||||
[[webflux-cors-webfilter]]
|
||||
== CORS `WebFilter`
|
||||
[.small]#<<web.adoc#mvc-cors-filter, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-cors-filter, See equivalent in the Servlet stack>>#
|
||||
|
||||
You can apply CORS support through the built-in
|
||||
{api-spring-framework}/web/cors/reactive/CorsWebFilter.html[`CorsWebFilter`], which is a
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[[webflux-fn]]
|
||||
= Functional Endpoints
|
||||
[.small]#<<web.adoc#webmvc-fn, Web MVC>>#
|
||||
[.small]#<<web.adoc#webmvc-fn, See equivalent in the Servlet stack>>#
|
||||
|
||||
Spring WebFlux includes WebFlux.fn, a lightweight functional programming model in which functions
|
||||
are used to route and handle requests and contracts are designed for immutability.
|
||||
@@ -12,7 +12,7 @@ the same <<web-reactive.adoc#webflux-reactive-spring-web>> foundation.
|
||||
|
||||
[[webflux-fn-overview]]
|
||||
== Overview
|
||||
[.small]#<<web.adoc#webmvc-fn-overview, Web MVC>>#
|
||||
[.small]#<<web.adoc#webmvc-fn-overview, See equivalent in the Servlet stack>>#
|
||||
|
||||
In WebFlux.fn, an HTTP request is handled with a `HandlerFunction`: a function that takes
|
||||
`ServerRequest` and returns a delayed `ServerResponse` (i.e. `Mono<ServerResponse>`).
|
||||
@@ -113,7 +113,7 @@ Most applications can run through the WebFlux Java configuration, see <<webflux-
|
||||
|
||||
[[webflux-fn-handler-functions]]
|
||||
== HandlerFunction
|
||||
[.small]#<<web.adoc#webmvc-fn-handler-functions, Web MVC>>#
|
||||
[.small]#<<web.adoc#webmvc-fn-handler-functions, See equivalent in the Servlet stack>>#
|
||||
|
||||
`ServerRequest` and `ServerResponse` are immutable interfaces that offer JDK 8-friendly
|
||||
access to the HTTP request and response.
|
||||
@@ -317,6 +317,7 @@ ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView::cla
|
||||
|
||||
We can write a handler function as a lambda, as the following example shows:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -328,6 +329,7 @@ HandlerFunction<ServerResponse> helloWorld =
|
||||
----
|
||||
val helloWorld = HandlerFunction<ServerResponse> { ServerResponse.ok().bodyValue("Hello World") }
|
||||
----
|
||||
--
|
||||
|
||||
That is convenient, but in an application we need multiple functions, and multiple inline
|
||||
lambda's can get messy.
|
||||
@@ -335,6 +337,7 @@ Therefore, it is useful to group related handler functions together into a handl
|
||||
has a similar role as `@Controller` in an annotation-based application.
|
||||
For example, the following class exposes a reactive `Person` repository:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -409,6 +412,7 @@ Note that `PersonRepository.savePerson(Person)` is a suspending function with no
|
||||
<3> `getPerson` is a handler function that returns a single person, identified by the `id` path
|
||||
variable. We retrieve that `Person` from the repository and create a JSON response, if it is
|
||||
found. If it is not found, we return a 404 Not Found response.
|
||||
--
|
||||
|
||||
|
||||
[[webflux-fn-handler-validation]]
|
||||
@@ -482,7 +486,7 @@ See <<core.adoc#validation-beanvalidation, Spring Validation>>.
|
||||
|
||||
[[webflux-fn-router-functions]]
|
||||
== `RouterFunction`
|
||||
[.small]#<<web.adoc#webmvc-fn-router-functions, Web MVC>>#
|
||||
[.small]#<<web.adoc#webmvc-fn-router-functions, See equivalent in the Servlet stack>>#
|
||||
|
||||
Router functions are used to route the requests to the corresponding `HandlerFunction`.
|
||||
Typically, you do not write router functions yourself, but rather use a method on the
|
||||
@@ -683,7 +687,7 @@ We can further improve by using the `nest` method together with `accept`:
|
||||
|
||||
[[webflux-fn-running]]
|
||||
== Running a Server
|
||||
[.small]#<<web.adoc#webmvc-fn-running, Web MVC>>#
|
||||
[.small]#<<web.adoc#webmvc-fn-running, See equivalent in the Servlet stack>>#
|
||||
|
||||
How do you run a router function in an HTTP server? A simple option is to convert a router
|
||||
function to an `HttpHandler` by using one of the following:
|
||||
@@ -789,7 +793,7 @@ The following example shows a WebFlux Java configuration (see
|
||||
|
||||
[[webflux-fn-handler-filter-function]]
|
||||
== Filtering Handler Functions
|
||||
[.small]#<<web.adoc#webmvc-fn-handler-filter-function, Web MVC>>#
|
||||
[.small]#<<web.adoc#webmvc-fn-handler-filter-function, See equivalent in the Servlet stack>>#
|
||||
|
||||
You can filter handler functions by using the `before`, `after`, or `filter` methods on the routing
|
||||
function builder.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[[webflux-view]]
|
||||
= View Technologies
|
||||
[.small]#<<web.adoc#mvc-view, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-view, See equivalent in the Servlet stack>>#
|
||||
|
||||
The use of view technologies in Spring WebFlux is pluggable. Whether you decide to
|
||||
use Thymeleaf, FreeMarker, or some other view technology is primarily a matter of a
|
||||
@@ -12,7 +12,7 @@ WebFlux. We assume you are already familiar with <<webflux-viewresolution>>.
|
||||
|
||||
[[webflux-view-thymeleaf]]
|
||||
== Thymeleaf
|
||||
[.small]#<<web.adoc#mvc-view-thymeleaf, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-view-thymeleaf, See equivalent in the Servlet stack>>#
|
||||
|
||||
Thymeleaf is a modern server-side Java template engine that emphasizes natural HTML
|
||||
templates that can be previewed in a browser by double-clicking, which is very
|
||||
@@ -33,7 +33,7 @@ https://web.archive.org/web/20210623051330/http%3A//forum.thymeleaf.org/Thymelea
|
||||
|
||||
[[webflux-view-freemarker]]
|
||||
== FreeMarker
|
||||
[.small]#<<web.adoc#mvc-view-freemarker, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-view-freemarker, See equivalent in the Servlet stack>>#
|
||||
|
||||
https://freemarker.apache.org/[Apache FreeMarker] is a template engine for generating any
|
||||
kind of text output from HTML to email and others. The Spring Framework has built-in
|
||||
@@ -43,7 +43,7 @@ integration for using Spring WebFlux with FreeMarker templates.
|
||||
|
||||
[[webflux-view-freemarker-contextconfig]]
|
||||
=== View Configuration
|
||||
[.small]#<<web.adoc#mvc-view-freemarker-contextconfig, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-view-freemarker-contextconfig, See equivalent in the Servlet stack>>#
|
||||
|
||||
The following example shows how to configure FreeMarker as a view technology:
|
||||
|
||||
@@ -98,7 +98,7 @@ returns the view name, `welcome`, the resolver looks for the
|
||||
|
||||
[[webflux-views-freemarker]]
|
||||
=== FreeMarker Configuration
|
||||
[.small]#<<web.adoc#mvc-views-freemarker, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-views-freemarker, See equivalent in the Servlet stack>>#
|
||||
|
||||
You can pass FreeMarker 'Settings' and 'SharedVariables' directly to the FreeMarker
|
||||
`Configuration` object (which is managed by Spring) by setting the appropriate bean
|
||||
@@ -151,7 +151,7 @@ the `Configuration` object.
|
||||
|
||||
[[webflux-view-freemarker-forms]]
|
||||
=== Form Handling
|
||||
[.small]#<<web.adoc#mvc-view-freemarker-forms, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-view-freemarker-forms, See equivalent in the Servlet stack>>#
|
||||
|
||||
Spring provides a tag library for use in JSPs that contains, among others, a
|
||||
`<spring:bind/>` element. This element primarily lets forms display values from
|
||||
@@ -162,7 +162,7 @@ with additional convenience macros for generating form input elements themselves
|
||||
|
||||
[[webflux-view-bind-macros]]
|
||||
==== The Bind Macros
|
||||
[.small]#<<web.adoc#mvc-view-bind-macros, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-view-bind-macros, See equivalent in the Servlet stack>>#
|
||||
|
||||
A standard set of macros are maintained within the `spring-webflux.jar` file for
|
||||
FreeMarker, so they are always available to a suitably configured application.
|
||||
@@ -193,7 +193,7 @@ sections of the Spring MVC documentation.
|
||||
|
||||
[[webflux-view-script]]
|
||||
== Script Views
|
||||
[.small]#<<web.adoc#mvc-view-script, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-view-script, See equivalent in the Servlet stack>>#
|
||||
|
||||
The Spring Framework has a built-in integration for using Spring WebFlux with any
|
||||
templating library that can run on top of the
|
||||
@@ -219,7 +219,7 @@ TIP: The basic rule for integrating any other script engine is that it must impl
|
||||
|
||||
[[webflux-view-script-dependencies]]
|
||||
=== Requirements
|
||||
[.small]#<<web.adoc#mvc-view-script-dependencies, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-view-script-dependencies, See equivalent in the Servlet stack>>#
|
||||
|
||||
You need to have the script engine on your classpath, the details of which vary by script engine:
|
||||
|
||||
@@ -239,7 +239,7 @@ through https://www.webjars.org/[WebJars].
|
||||
|
||||
[[webflux-view-script-integrate]]
|
||||
=== Script Templates
|
||||
[.small]#<<web.adoc#mvc-view-script-integrate, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-view-script-integrate, See equivalent in the Servlet stack>>#
|
||||
|
||||
You can declare a `ScriptTemplateConfigurer` bean to specify the script engine to use,
|
||||
the script files to load, what function to call to render templates, and so on.
|
||||
@@ -389,7 +389,7 @@ for more configuration examples.
|
||||
|
||||
[[webflux-view-httpmessagewriter]]
|
||||
== JSON and XML
|
||||
[.small]#<<web.adoc#mvc-view-jackson, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-view-jackson, See equivalent in the Servlet stack>>#
|
||||
|
||||
For <<webflux-multiple-representations>> purposes, it is useful to be able to alternate
|
||||
between rendering a model with an HTML template or as other formats (such as JSON or XML),
|
||||
|
||||
@@ -38,6 +38,8 @@ You can also use `WebClient.builder()` with further options:
|
||||
* `filter`: Client filter for every request.
|
||||
* `exchangeStrategies`: HTTP message reader/writer customizations.
|
||||
* `clientConnector`: HTTP client library settings.
|
||||
* `observationRegistry`: the registry to use for enabling <<integration.adoc#integration.observability.http-client.webclient, Observability support>>.
|
||||
* `observationConvention`: <<integration.adoc#integration.observability.config,an optional, custom convention to extract metadata>> for recorded observations.
|
||||
|
||||
For example:
|
||||
|
||||
@@ -156,6 +158,7 @@ application deployed as a WAR), you can declare a Spring-managed bean of type
|
||||
Netty global resources are shut down when the Spring `ApplicationContext` is closed,
|
||||
as the following example shows:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -170,11 +173,13 @@ as the following example shows:
|
||||
@Bean
|
||||
fun reactorResourceFactory() = ReactorResourceFactory()
|
||||
----
|
||||
--
|
||||
|
||||
You can also choose not to participate in the global Reactor Netty resources. However,
|
||||
in this mode, the burden is on you to ensure that all Reactor Netty client and server
|
||||
instances use shared resources, as the following example shows:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -225,6 +230,7 @@ instances use shared resources, as the following example shows:
|
||||
<1> Create resources independent of global ones.
|
||||
<2> Use the `ReactorClientHttpConnector` constructor with resource factory.
|
||||
<3> Plug the connector into the `WebClient.Builder`.
|
||||
--
|
||||
|
||||
|
||||
[[webflux-client-builder-reactor-timeout]]
|
||||
@@ -375,6 +381,7 @@ The following example shows how to customize the JDK `HttpClient`:
|
||||
|
||||
The following example shows how to customize Jetty `HttpClient` settings:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -395,6 +402,7 @@ The following example shows how to customize Jetty `HttpClient` settings:
|
||||
.clientConnector(JettyClientHttpConnector(httpClient))
|
||||
.build();
|
||||
----
|
||||
--
|
||||
|
||||
By default, `HttpClient` creates its own resources (`Executor`, `ByteBufferPool`, `Scheduler`),
|
||||
which remain active until the process exits or `stop()` is called.
|
||||
@@ -404,6 +412,7 @@ ensure that the resources are shut down when the Spring `ApplicationContext` is
|
||||
declaring a Spring-managed bean of type `JettyResourceFactory`, as the following example
|
||||
shows:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -446,6 +455,7 @@ shows:
|
||||
----
|
||||
<1> Use the `JettyClientHttpConnector` constructor with resource factory.
|
||||
<2> Plug the connector into the `WebClient.Builder`.
|
||||
--
|
||||
|
||||
|
||||
|
||||
@@ -1188,7 +1198,7 @@ response individually, and instead wait for the combined result:
|
||||
|
||||
The above is merely one example. There are lots of other patterns and operators for putting
|
||||
together a reactive pipeline that makes many remote calls, potentially some nested,
|
||||
inter-dependent, without ever blocking until the end.
|
||||
interdependent, without ever blocking until the end.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[[webflux-websocket]]
|
||||
= WebSockets
|
||||
[.small]#<<web.adoc#websocket, Same as in the Servlet stack>>#
|
||||
[.small]#<<web.adoc#websocket, See equivalent in the Servlet stack>>#
|
||||
|
||||
This part of the reference documentation covers support for reactive-stack WebSocket
|
||||
messaging.
|
||||
@@ -12,7 +12,7 @@ include::websocket-intro.adoc[leveloffset=+1]
|
||||
|
||||
[[webflux-websocket-server]]
|
||||
== WebSocket API
|
||||
[.small]#<<web.adoc#websocket-server, Same as in the Servlet stack>>#
|
||||
[.small]#<<web.adoc#websocket-server, See equivalent in the Servlet stack>>#
|
||||
|
||||
The Spring Framework provides a WebSocket API that you can use to write client- and
|
||||
server-side applications that handle WebSocket messages.
|
||||
@@ -21,7 +21,7 @@ server-side applications that handle WebSocket messages.
|
||||
|
||||
[[webflux-websocket-server-handler]]
|
||||
=== Server
|
||||
[.small]#<<web.adoc#websocket-server-handler, Same as in the Servlet stack>>#
|
||||
[.small]#<<web.adoc#websocket-server-handler, See equivalent in the Servlet stack>>#
|
||||
|
||||
To create a WebSocket server, you can first create a `WebSocketHandler`.
|
||||
The following example shows how to do so:
|
||||
@@ -339,7 +339,7 @@ subsequently use `DataBufferUtils.release(dataBuffer)` when the buffers are cons
|
||||
|
||||
[[webflux-websocket-server-handshake]]
|
||||
=== Handshake
|
||||
[.small]#<<web.adoc#websocket-server-handshake, Same as in the Servlet stack>>#
|
||||
[.small]#<<web.adoc#websocket-server-handshake, See equivalent in the Servlet stack>>#
|
||||
|
||||
`WebSocketHandlerAdapter` delegates to a `WebSocketService`. By default, that is an instance
|
||||
of `HandshakeWebSocketService`, which performs basic checks on the WebSocket request and
|
||||
@@ -354,7 +354,7 @@ into the attributes of the `WebSocketSession`.
|
||||
|
||||
[[webflux-websocket-server-config]]
|
||||
=== Server Configuration
|
||||
[.small]#<<web.adoc#websocket-server-runtime-configuration, Same as in the Servlet stack>>#
|
||||
[.small]#<<web.adoc#websocket-server-runtime-configuration, See equivalent in the Servlet stack>>#
|
||||
|
||||
The `RequestUpgradeStrategy` for each server exposes configuration specific to the
|
||||
underlying WebSocket server engine. When using the WebFlux Java config you can customize
|
||||
@@ -408,7 +408,7 @@ only Tomcat and Jetty expose such options.
|
||||
|
||||
[[webflux-websocket-server-cors]]
|
||||
=== CORS
|
||||
[.small]#<<web.adoc#websocket-server-allowed-origins, Same as in the Servlet stack>>#
|
||||
[.small]#<<web.adoc#websocket-server-allowed-origins, See equivalent in the Servlet stack>>#
|
||||
|
||||
The easiest way to configure CORS and restrict access to a WebSocket endpoint is to
|
||||
have your `WebSocketHandler` implement `CorsConfigurationSource` and return a
|
||||
|
||||
@@ -248,20 +248,22 @@ current thread (and rely on callbacks instead) means that you do not need extra
|
||||
there are no blocking calls to absorb.
|
||||
|
||||
|
||||
==== Invoking a Blocking API
|
||||
|
||||
.Invoking a Blocking API
|
||||
What if you do need to use a blocking library? Both Reactor and RxJava provide the
|
||||
`publishOn` operator to continue processing on a different thread. That means there is an
|
||||
easy escape hatch. Keep in mind, however, that blocking APIs are not a good fit for
|
||||
this concurrency model.
|
||||
|
||||
.Mutable State
|
||||
==== Mutable State
|
||||
|
||||
In Reactor and RxJava, you declare logic through operators. At runtime, a reactive
|
||||
pipeline is formed where data is processed sequentially, in distinct stages. A key benefit
|
||||
of this is that it frees applications from having to protect mutable state because
|
||||
application code within that pipeline is never invoked concurrently.
|
||||
|
||||
.Threading Model
|
||||
==== Threading Model
|
||||
|
||||
What threads should you expect to see on a server running with Spring WebFlux?
|
||||
|
||||
* On a "`vanilla`" Spring WebFlux server (for example, no data access nor other optional
|
||||
@@ -285,7 +287,8 @@ specific thread pool `Scheduler` strategy.
|
||||
* Data access libraries and other third party dependencies can also create and use threads
|
||||
of their own.
|
||||
|
||||
.Configuring
|
||||
==== Configuring
|
||||
|
||||
The Spring Framework does not provide support for starting and stopping
|
||||
<<webflux-server-choice, servers>>. To configure the threading model for a server,
|
||||
you need to use server-specific configuration APIs, or, if you use Spring Boot,
|
||||
@@ -387,14 +390,14 @@ The code snippets below show using the `HttpHandler` adapters with each server A
|
||||
----
|
||||
HttpHandler handler = ...
|
||||
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
|
||||
HttpServer.create().host(host).port(port).handle(adapter).bind().block();
|
||||
HttpServer.create().host(host).port(port).handle(adapter).bindNow();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val handler: HttpHandler = ...
|
||||
val adapter = ReactorHttpHandlerAdapter(handler)
|
||||
HttpServer.create().host(host).port(port).handle(adapter).bind().block()
|
||||
HttpServer.create().host(host).port(port).handle(adapter).bindNow()
|
||||
----
|
||||
|
||||
*Undertow*
|
||||
@@ -594,7 +597,7 @@ The `DefaultServerWebExchange` uses the configured `HttpMessageReader` to parse
|
||||
|
||||
[[webflux-multipart]]
|
||||
==== Multipart Data
|
||||
[.small]#<<web.adoc#mvc-multipart, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-multipart, See equivalent in the Servlet stack>>#
|
||||
|
||||
`ServerWebExchange` exposes the following method for accessing multipart data:
|
||||
|
||||
@@ -610,8 +613,8 @@ The `DefaultServerWebExchange` uses the configured `HttpMessageReader` to parse
|
||||
----
|
||||
|
||||
The `DefaultServerWebExchange` uses the configured
|
||||
`HttpMessageReader<MultiValueMap<String, Part>>` to parse `multipart/form-data` content
|
||||
into a `MultiValueMap`.
|
||||
`HttpMessageReader<MultiValueMap<String, Part>>` to parse `multipart/form-data`,
|
||||
`multipart/mixed`, and `multipart/related` content into a `MultiValueMap`.
|
||||
By default, this is the `DefaultPartHttpMessageReader`, which does not have any third-party
|
||||
dependencies.
|
||||
Alternatively, the `SynchronossPartHttpMessageReader` can be used, which is based on the
|
||||
@@ -628,7 +631,7 @@ collecting to a `MultiValueMap`.
|
||||
|
||||
[[webflux-forwarded-headers]]
|
||||
==== Forwarded Headers
|
||||
[.small]#<<web.adoc#filters-forwarded-headers, Web MVC>>#
|
||||
[.small]#<<web.adoc#filters-forwarded-headers, See equivalent in the Servlet stack>>#
|
||||
|
||||
As a request goes through proxies (such as load balancers), the host, port, and
|
||||
scheme may change. That makes it a challenge, from a client perspective, to create links that point to the correct
|
||||
@@ -659,7 +662,7 @@ filters, and `ForwardedHeaderTransformer` is used instead.
|
||||
|
||||
[[webflux-filters]]
|
||||
=== Filters
|
||||
[.small]#<<web.adoc#filters, Web MVC>>#
|
||||
[.small]#<<web.adoc#filters, See equivalent in the Servlet stack>>#
|
||||
|
||||
In the <<webflux-web-handler-api>>, you can use a `WebFilter` to apply interception-style
|
||||
logic before and after the rest of the processing chain of filters and the target
|
||||
@@ -670,7 +673,7 @@ the bean declaration or by implementing `Ordered`.
|
||||
|
||||
[[webflux-filters-cors]]
|
||||
==== CORS
|
||||
[.small]#<<web.adoc#filters-cors, Web MVC>>#
|
||||
[.small]#<<web.adoc#filters-cors, See equivalent in the Servlet stack>>#
|
||||
|
||||
Spring WebFlux provides fine-grained support for CORS configuration through annotations on
|
||||
controllers. However, when you use it with Spring Security, we advise relying on the built-in
|
||||
@@ -681,7 +684,7 @@ See the section on <<webflux-cors>> and the <<webflux-cors-webfilter>> for more
|
||||
|
||||
[[webflux-exception-handler]]
|
||||
=== Exceptions
|
||||
[.small]#<<web.adoc#mvc-ann-customer-servlet-container-error-page, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-customer-servlet-container-error-page, See equivalent in the Servlet stack>>#
|
||||
|
||||
In the <<webflux-web-handler-api>>, you can use a `WebExceptionHandler` to handle
|
||||
exceptions from the chain of `WebFilter` instances and the target `WebHandler`. When using the
|
||||
@@ -712,7 +715,7 @@ The following table describes the available `WebExceptionHandler` implementation
|
||||
|
||||
[[webflux-codecs]]
|
||||
=== Codecs
|
||||
[.small]#<<integration.adoc#rest-message-conversion, Web MVC>>#
|
||||
[.small]#<<integration.adoc#rest-message-conversion, See equivalent in the Servlet stack>>#
|
||||
|
||||
The `spring-web` and `spring-core` modules provide support for serializing and
|
||||
deserializing byte content to and from higher level objects through non-blocking I/O with
|
||||
@@ -802,12 +805,12 @@ consistently for access to the cached form data versus reading from the raw requ
|
||||
==== Multipart
|
||||
|
||||
`MultipartHttpMessageReader` and `MultipartHttpMessageWriter` support decoding and
|
||||
encoding "multipart/form-data" content. In turn `MultipartHttpMessageReader` delegates to
|
||||
another `HttpMessageReader` for the actual parsing to a `Flux<Part>` and then simply
|
||||
collects the parts into a `MultiValueMap`.
|
||||
encoding "multipart/form-data", "multipart/mixed", and "multipart/related" content.
|
||||
In turn `MultipartHttpMessageReader` delegates to another `HttpMessageReader`
|
||||
for the actual parsing to a `Flux<Part>` and then simply collects the parts into a `MultiValueMap`.
|
||||
By default, the `DefaultPartHttpMessageReader` is used, but this can be changed through the
|
||||
`ServerCodecConfigurer`.
|
||||
For more information about the `DefaultPartHttpMessageReader`, refer to to the
|
||||
For more information about the `DefaultPartHttpMessageReader`, refer to the
|
||||
{api-spring-framework}/http/codec/multipart/DefaultPartHttpMessageReader.html[javadoc of `DefaultPartHttpMessageReader`].
|
||||
|
||||
On the server side where multipart form content may need to be accessed from multiple
|
||||
@@ -852,7 +855,7 @@ To configure all three in WebFlux, you'll need to supply a pre-configured instan
|
||||
|
||||
[[webflux-codecs-streaming]]
|
||||
==== Streaming
|
||||
[.small]#<<web.adoc#mvc-ann-async-http-streaming, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-async-http-streaming, See equivalent in the Servlet stack>>#
|
||||
|
||||
When streaming to the HTTP response (for example, `text/event-stream`,
|
||||
`application/x-ndjson`), it is important to send data periodically, in order to
|
||||
@@ -880,7 +883,7 @@ especially the section on <<core#databuffers-using, Using DataBuffer>>.
|
||||
|
||||
[[webflux-logging]]
|
||||
=== Logging
|
||||
[.small]#<<web.adoc#mvc-logging, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-logging, See equivalent in the Servlet stack>>#
|
||||
|
||||
`DEBUG` level logging in Spring WebFlux is designed to be compact, minimal, and
|
||||
human-friendly. It focuses on high value bits of information that are useful over and
|
||||
@@ -912,7 +915,7 @@ while a fully formatted prefix based on that ID is available from
|
||||
|
||||
[[webflux-logging-sensitive-data]]
|
||||
==== Sensitive Data
|
||||
[.small]#<<web.adoc#mvc-logging-sensitive-data, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-logging-sensitive-data, See equivalent in the Servlet stack>>#
|
||||
|
||||
`DEBUG` and `TRACE` logging can log sensitive information. This is why form parameters and
|
||||
headers are masked by default and you must explicitly enable their logging in full.
|
||||
@@ -1014,7 +1017,7 @@ The following example shows how to do so for client-side requests:
|
||||
|
||||
[[webflux-dispatcher-handler]]
|
||||
== `DispatcherHandler`
|
||||
[.small]#<<web.adoc#mvc-servlet, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-servlet, See equivalent in the Servlet stack>>#
|
||||
|
||||
Spring WebFlux, similarly to Spring MVC, is designed around the front controller pattern,
|
||||
where a central `WebHandler`, the `DispatcherHandler`, provides a shared algorithm for
|
||||
@@ -1057,7 +1060,7 @@ The resulting `HttpHandler` is ready for use with a <<webflux-httphandler, serve
|
||||
|
||||
[[webflux-special-bean-types]]
|
||||
=== Special Bean Types
|
||||
[.small]#<<web.adoc#mvc-servlet-special-bean-types, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-servlet-special-bean-types, See equivalent in the Servlet stack>>#
|
||||
|
||||
The `DispatcherHandler` delegates to special beans to process requests and render the
|
||||
appropriate responses. By "`special beans,`" we mean Spring-managed `Object` instances that
|
||||
@@ -1099,7 +1102,7 @@ there are also some other beans detected at a lower level (see
|
||||
|
||||
[[webflux-framework-config]]
|
||||
=== WebFlux Config
|
||||
[.small]#<<web.adoc#mvc-servlet-config, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-servlet-config, See equivalent in the Servlet stack>>#
|
||||
|
||||
Applications can declare the infrastructure beans (listed under
|
||||
<<webflux-web-handler-api-special-beans, Web Handler API>> and
|
||||
@@ -1114,7 +1117,7 @@ many extra convenient options.
|
||||
|
||||
[[webflux-dispatcher-handler-sequence]]
|
||||
=== Processing
|
||||
[.small]#<<web.adoc#mvc-servlet-sequence, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-servlet-sequence, See equivalent in the Servlet stack>>#
|
||||
|
||||
`DispatcherHandler` processes requests as follows:
|
||||
|
||||
@@ -1165,7 +1168,7 @@ as a `HandlerResult`, along with some additional context, and passed to the firs
|
||||
|
||||
[[webflux-dispatcher-exceptions]]
|
||||
=== Exceptions
|
||||
[.small]#<<web.adoc#mvc-exceptionhandlers, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-exceptionhandlers, See equivalent in the Servlet stack>>#
|
||||
|
||||
`HandlerAdapter` implementations can handle internally exceptions from invoking a request
|
||||
handler, such as a controller method. However, an exception may be deferred if the request
|
||||
@@ -1175,7 +1178,7 @@ A `HandlerAdapter` may expose its exception handling mechanism as a
|
||||
`DispatchExceptionHandler` set on the `HandlerResult` it returns. When that's set,
|
||||
`DispatcherHandler` will also apply it to the handling of the result.
|
||||
|
||||
A `HandlerAdapter` may also choose to implement `DispatchExceptionHandler`. Inn that case
|
||||
A `HandlerAdapter` may also choose to implement `DispatchExceptionHandler`. In that case
|
||||
`DispatcherHandler` will apply it to exceptions that arise before a handler is mapped,
|
||||
e.g. during handler mapping, or earlier, e.g. in a `WebFilter`.
|
||||
|
||||
@@ -1186,7 +1189,7 @@ See also <<webflux-ann-controller-exceptions>> in the "`Annotated Controller`" s
|
||||
|
||||
[[webflux-viewresolution]]
|
||||
=== View Resolution
|
||||
[.small]#<<web.adoc#mvc-viewresolver, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-viewresolver, See equivalent in the Servlet stack>>#
|
||||
|
||||
View resolution enables rendering to a browser with an HTML template and a model without
|
||||
tying you to a specific view technology. In Spring WebFlux, view resolution is
|
||||
@@ -1197,7 +1200,7 @@ instance. The `View` is then used to render the response.
|
||||
|
||||
[[webflux-viewresolution-handling]]
|
||||
==== Handling
|
||||
[.small]#<<web.adoc#mvc-viewresolver-handling, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-viewresolver-handling, See equivalent in the Servlet stack>>#
|
||||
|
||||
The `HandlerResult` passed into `ViewResolutionResultHandler` contains the return value
|
||||
from the handler and the model that contains attributes added during request
|
||||
@@ -1233,7 +1236,7 @@ See <<webflux-view>> for more on the view technologies integrated with Spring We
|
||||
|
||||
[[webflux-redirecting-redirect-prefix]]
|
||||
==== Redirecting
|
||||
[.small]#<<web.adoc#mvc-redirecting-redirect-prefix, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-redirecting-redirect-prefix, See equivalent in the Servlet stack>>#
|
||||
|
||||
The special `redirect:` prefix in a view name lets you perform a redirect. The
|
||||
`UrlBasedViewResolver` (and sub-classes) recognize this as an instruction that a
|
||||
@@ -1248,7 +1251,7 @@ operate in terms of logical view names. A view name such as
|
||||
|
||||
[[webflux-multiple-representations]]
|
||||
==== Content Negotiation
|
||||
[.small]#<<web.adoc#mvc-multiple-representations, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-multiple-representations, See equivalent in the Servlet stack>>#
|
||||
|
||||
`ViewResolutionResultHandler` supports content negotiation. It compares the request
|
||||
media types with the media types supported by each selected `View`. The first `View`
|
||||
@@ -1265,7 +1268,7 @@ always selected and used if they match the requested media type.
|
||||
|
||||
[[webflux-controller]]
|
||||
== Annotated Controllers
|
||||
[.small]#<<web.adoc#mvc-controller, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-controller, See equivalent in the Servlet stack>>#
|
||||
|
||||
Spring WebFlux provides an annotation-based programming model, where `@Controller` and
|
||||
`@RestController` components use annotations to express request mappings, request input,
|
||||
@@ -1303,7 +1306,7 @@ In the preceding example, the method returns a `String` to be written to the res
|
||||
|
||||
[[webflux-ann-controller]]
|
||||
=== `@Controller`
|
||||
[.small]#<<web.adoc#mvc-ann-controller, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-controller, See equivalent in the Servlet stack>>#
|
||||
|
||||
You can define controller beans by using a standard Spring bean definition.
|
||||
The `@Controller` stereotype allows for auto-detection and is aligned with Spring general support
|
||||
@@ -1347,7 +1350,7 @@ directly to the response body versus view resolution and rendering with an HTML
|
||||
|
||||
[[webflux-ann-requestmapping-proxying]]
|
||||
==== AOP Proxies
|
||||
[.small]#<<web.adoc#mvc-ann-requestmapping-proxying, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-requestmapping-proxying, See equivalent in the Servlet stack>>#
|
||||
|
||||
In some cases, you may need to decorate a controller with an AOP proxy at runtime.
|
||||
One example is if you choose to have `@Transactional` annotations directly on the
|
||||
@@ -1370,7 +1373,7 @@ Please, enable class based proxying, or otherwise the interface must also have a
|
||||
|
||||
[[webflux-ann-requestmapping]]
|
||||
=== Request Mapping
|
||||
[.small]#<<web.adoc#mvc-ann-requestmapping, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-requestmapping, See equivalent in the Servlet stack>>#
|
||||
|
||||
The `@RequestMapping` annotation is used to map requests to controllers methods. It has
|
||||
various attributes to match by URL, HTTP method, request parameters, headers, and media
|
||||
@@ -1434,7 +1437,7 @@ The following example uses type and method level mappings:
|
||||
|
||||
[[webflux-ann-requestmapping-uri-templates]]
|
||||
==== URI Patterns
|
||||
[.small]#<<web.adoc#mvc-ann-requestmapping-uri-templates, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-requestmapping-uri-templates, See equivalent in the Servlet stack>>#
|
||||
|
||||
You can map requests by using glob patterns and wildcards:
|
||||
|
||||
@@ -1474,6 +1477,7 @@ You can map requests by using glob patterns and wildcards:
|
||||
|
||||
Captured URI variables can be accessed with `@PathVariable`, as the following example shows:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -1490,9 +1494,11 @@ Captured URI variables can be accessed with `@PathVariable`, as the following ex
|
||||
// ...
|
||||
}
|
||||
----
|
||||
--
|
||||
|
||||
You can declare URI variables at the class and method levels, as the following example shows:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -1524,6 +1530,7 @@ You can declare URI variables at the class and method levels, as the following e
|
||||
----
|
||||
<1> Class-level URI mapping.
|
||||
<2> Method-level URI mapping.
|
||||
--
|
||||
|
||||
|
||||
URI variables are automatically converted to the appropriate type or a `TypeMismatchException`
|
||||
@@ -1543,6 +1550,7 @@ The syntax `{varName:regex}` declares a URI variable with a regular expression t
|
||||
syntax: `{varName:regex}`. For example, given a URL of `/spring-web-3.0.5.jar`, the following method
|
||||
extracts the name, version, and file extension:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -1559,6 +1567,7 @@ extracts the name, version, and file extension:
|
||||
// ...
|
||||
}
|
||||
----
|
||||
--
|
||||
|
||||
URI path patterns can also have embedded `${...}` placeholders that are resolved on startup
|
||||
through `PropertySourcesPlaceholderConfigurer` against local, system, environment, and
|
||||
@@ -1577,7 +1586,7 @@ explicit, and less vulnerable to URL path based exploits.
|
||||
|
||||
[[webflux-ann-requestmapping-pattern-comparison]]
|
||||
==== Pattern Comparison
|
||||
[.small]#<<web.adoc#mvc-ann-requestmapping-pattern-comparison, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-requestmapping-pattern-comparison, See equivalent in the Servlet stack>>#
|
||||
|
||||
When multiple patterns match a URL, they must be compared to find the best match. This is done
|
||||
with `PathPattern.SPECIFICITY_COMPARATOR`, which looks for patterns that are more specific.
|
||||
@@ -1592,7 +1601,7 @@ sorted last instead. If two patterns are both catch-all, the longer is chosen.
|
||||
|
||||
[[webflux-ann-requestmapping-consumes]]
|
||||
==== Consumable Media Types
|
||||
[.small]#<<web.adoc#mvc-ann-requestmapping-consumes, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-requestmapping-consumes, See equivalent in the Servlet stack>>#
|
||||
|
||||
You can narrow the request mapping based on the `Content-Type` of the request,
|
||||
as the following example shows:
|
||||
@@ -1627,7 +1636,7 @@ TIP: `MediaType` provides constants for commonly used media types -- for example
|
||||
|
||||
[[webflux-ann-requestmapping-produces]]
|
||||
==== Producible Media Types
|
||||
[.small]#<<web.adoc#mvc-ann-requestmapping-produces, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-requestmapping-produces, See equivalent in the Servlet stack>>#
|
||||
|
||||
You can narrow the request mapping based on the `Accept` request header and the list of
|
||||
content types that a controller method produces, as the following example shows:
|
||||
@@ -1664,7 +1673,7 @@ TIP: `MediaType` provides constants for commonly used media types -- e.g.
|
||||
|
||||
[[webflux-ann-requestmapping-params-and-headers]]
|
||||
==== Parameters and Headers
|
||||
[.small]#<<web.adoc#mvc-ann-requestmapping-params-and-headers, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-requestmapping-params-and-headers, See equivalent in the Servlet stack>>#
|
||||
|
||||
You can narrow request mappings based on query parameter conditions. You can test for the
|
||||
presence of a query parameter (`myParam`), for its absence (`!myParam`), or for a
|
||||
@@ -1716,7 +1725,7 @@ You can also use the same with request header conditions, as the following examp
|
||||
|
||||
[[webflux-ann-requestmapping-head-options]]
|
||||
==== HTTP HEAD, OPTIONS
|
||||
[.small]#<<web.adoc#mvc-ann-requestmapping-head-options, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-requestmapping-head-options, See equivalent in the Servlet stack>>#
|
||||
|
||||
`@GetMapping` and `@RequestMapping(method=HttpMethod.GET)` support HTTP HEAD
|
||||
transparently for request mapping purposes. Controller methods need not change.
|
||||
@@ -1737,7 +1746,7 @@ is not necessary in the common case.
|
||||
|
||||
[[webflux-ann-requestmapping-composed]]
|
||||
==== Custom Annotations
|
||||
[.small]#<<web.adoc#mvc-ann-requestmapping-composed, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-requestmapping-composed, See equivalent in the Servlet stack>>#
|
||||
|
||||
Spring WebFlux supports the use of <<core.adoc#beans-meta-annotations, composed annotations>>
|
||||
for request mapping. Those are annotations that are themselves meta-annotated with
|
||||
@@ -1758,7 +1767,7 @@ you can check the custom attribute and return your own `RequestCondition`.
|
||||
|
||||
[[webflux-ann-requestmapping-registration]]
|
||||
==== Explicit Registrations
|
||||
[.small]#<<web.adoc#mvc-ann-requestmapping-registration, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-requestmapping-registration, See equivalent in the Servlet stack>>#
|
||||
|
||||
You can programmatically register Handler methods, which can be used for dynamic
|
||||
registrations or for advanced cases, such as different instances of the same handler
|
||||
@@ -1815,7 +1824,7 @@ under different URLs. The following example shows how to do so:
|
||||
|
||||
[[webflux-ann-methods]]
|
||||
=== Handler Methods
|
||||
[.small]#<<web.adoc#mvc-ann-methods, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-methods, See equivalent in the Servlet stack>>#
|
||||
|
||||
`@RequestMapping` handler methods have a flexible signature and can choose from a range of
|
||||
supported controller method arguments and return values.
|
||||
@@ -1823,7 +1832,7 @@ supported controller method arguments and return values.
|
||||
|
||||
[[webflux-ann-arguments]]
|
||||
==== Method Arguments
|
||||
[.small]#<<web.adoc#mvc-ann-arguments, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-arguments, See equivalent in the Servlet stack>>#
|
||||
|
||||
The following table shows the supported controller method arguments.
|
||||
|
||||
@@ -1943,7 +1952,7 @@ and others) and is equivalent to `required=false`.
|
||||
|
||||
[[webflux-ann-return-types]]
|
||||
==== Return Values
|
||||
[.small]#<<web.adoc#mvc-ann-return-types, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-return-types, See equivalent in the Servlet stack>>#
|
||||
|
||||
The following table shows the supported controller method return values. Note that reactive
|
||||
types from libraries such as Reactor, RxJava, <<webflux-reactive-libraries, or other>> are
|
||||
@@ -2024,7 +2033,7 @@ generally supported for all return values.
|
||||
|
||||
[[webflux-ann-typeconversion]]
|
||||
==== Type Conversion
|
||||
[.small]#<<web.adoc#mvc-ann-typeconversion, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-typeconversion, See equivalent in the Servlet stack>>#
|
||||
|
||||
Some annotated controller method arguments that represent String-based request input (for example,
|
||||
`@RequestParam`, `@RequestHeader`, `@PathVariable`, `@MatrixVariable`, and `@CookieValue`)
|
||||
@@ -2044,7 +2053,7 @@ argument as `@Nullable`.
|
||||
|
||||
[[webflux-ann-matrix-variables]]
|
||||
==== Matrix Variables
|
||||
[.small]#<<web.adoc#mvc-ann-matrix-variables, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-matrix-variables, See equivalent in the Servlet stack>>#
|
||||
|
||||
https://tools.ietf.org/html/rfc3986#section-3.3[RFC 3986] discusses name-value pairs in
|
||||
path segments. In Spring WebFlux, we refer to those as "`matrix variables`" based on an
|
||||
@@ -2179,7 +2188,7 @@ To get all matrix variables, use a `MultiValueMap`, as the following example sho
|
||||
|
||||
[[webflux-ann-requestparam]]
|
||||
==== `@RequestParam`
|
||||
[.small]#<<web.adoc#mvc-ann-requestparam, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-requestparam, See equivalent in the Servlet stack>>#
|
||||
|
||||
You can use the `@RequestParam` annotation to bind query parameters to a method argument in a
|
||||
controller. The following code snippet shows the usage:
|
||||
@@ -2254,7 +2263,7 @@ with `@RequestParam`.
|
||||
|
||||
[[webflux-ann-requestheader]]
|
||||
==== `@RequestHeader`
|
||||
[.small]#<<web.adoc#mvc-ann-requestheader, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-requestheader, See equivalent in the Servlet stack>>#
|
||||
|
||||
You can use the `@RequestHeader` annotation to bind a request header to a method argument in a
|
||||
controller.
|
||||
@@ -2315,7 +2324,7 @@ example, a method parameter annotated with `@RequestHeader("Accept")` may be of
|
||||
|
||||
[[webflux-ann-cookievalue]]
|
||||
==== `@CookieValue`
|
||||
[.small]#<<web.adoc#mvc-ann-cookievalue, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-cookievalue, See equivalent in the Servlet stack>>#
|
||||
|
||||
You can use the `@CookieValue` annotation to bind the value of an HTTP cookie to a method argument
|
||||
in a controller.
|
||||
@@ -2356,7 +2365,7 @@ Type conversion is applied automatically if the target method parameter type is
|
||||
|
||||
[[webflux-ann-modelattrib-method-args]]
|
||||
==== `@ModelAttribute`
|
||||
[.small]#<<web.adoc#mvc-ann-modelattrib-method-args, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-modelattrib-method-args, See equivalent in the Servlet stack>>#
|
||||
|
||||
You can use the `@ModelAttribute` annotation on a method argument to access an attribute from the
|
||||
model or have it instantiated if not present. The model attribute is also overlaid with
|
||||
@@ -2503,7 +2512,7 @@ with `@ModelAttribute`.
|
||||
|
||||
[[webflux-ann-sessionattributes]]
|
||||
==== `@SessionAttributes`
|
||||
[.small]#<<web.adoc#mvc-ann-sessionattributes, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-sessionattributes, See equivalent in the Servlet stack>>#
|
||||
|
||||
`@SessionAttributes` is used to store model attributes in the `WebSession` between
|
||||
requests. It is a type-level annotation that declares session attributes used by a
|
||||
@@ -2588,7 +2597,7 @@ as the following example shows:
|
||||
|
||||
[[webflux-ann-sessionattribute]]
|
||||
==== `@SessionAttribute`
|
||||
[.small]#<<web.adoc#mvc-ann-sessionattribute, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-sessionattribute, See equivalent in the Servlet stack>>#
|
||||
|
||||
If you need access to pre-existing session attributes that are managed globally
|
||||
(that is, outside the controller -- for example, by a filter) and may or may not be present,
|
||||
@@ -2624,7 +2633,7 @@ workflow, consider using `SessionAttributes`, as described in
|
||||
|
||||
[[webflux-ann-requestattrib]]
|
||||
==== `@RequestAttribute`
|
||||
[.small]#<<web.adoc#mvc-ann-requestattrib, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-requestattrib, See equivalent in the Servlet stack>>#
|
||||
|
||||
Similarly to `@SessionAttribute`, you can use the `@RequestAttribute` annotation to
|
||||
access pre-existing request attributes created earlier (for example, by a `WebFilter`),
|
||||
@@ -2653,13 +2662,14 @@ as the following example shows:
|
||||
|
||||
[[webflux-multipart-forms]]
|
||||
==== Multipart Content
|
||||
[.small]#<<web.adoc#mvc-multipart-forms, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-multipart-forms, See equivalent in the Servlet stack>>#
|
||||
|
||||
As explained in <<webflux-multipart>>, `ServerWebExchange` provides access to multipart
|
||||
content. The best way to handle a file upload form (for example, from a browser) in a controller
|
||||
is through data binding to a <<webflux-ann-modelattrib-method-args, command object>>,
|
||||
as the following example shows:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -2700,6 +2710,7 @@ as the following example shows:
|
||||
|
||||
}
|
||||
----
|
||||
--
|
||||
|
||||
You can also submit multipart requests from non-browser clients in a RESTful service
|
||||
scenario. The following example uses a file along with JSON:
|
||||
@@ -2726,6 +2737,7 @@ Content-Transfer-Encoding: 8bit
|
||||
|
||||
You can access individual parts with `@RequestPart`, as the following example shows:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -2749,11 +2761,13 @@ You can access individual parts with `@RequestPart`, as the following example sh
|
||||
----
|
||||
<1> Using `@RequestPart` to get the metadata.
|
||||
<2> Using `@RequestPart` to get the file.
|
||||
--
|
||||
|
||||
|
||||
To deserialize the raw part content (for example, to JSON -- similar to `@RequestBody`),
|
||||
you can declare a concrete target `Object`, instead of `Part`, as the following example shows:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -2773,6 +2787,7 @@ you can declare a concrete target `Object`, instead of `Part`, as the following
|
||||
}
|
||||
----
|
||||
<1> Using `@RequestPart` to get the metadata.
|
||||
--
|
||||
|
||||
You can use `@RequestPart` in combination with `jakarta.validation.Valid` or Spring's
|
||||
`@Validated` annotation, which causes Standard Bean Validation to be applied. Validation
|
||||
@@ -2781,6 +2796,7 @@ The exception contains a `BindingResult` with the error details and can also be
|
||||
in the controller method by declaring the argument with an async wrapper and then using
|
||||
error related operators:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -2798,10 +2814,12 @@ error related operators:
|
||||
// ...
|
||||
}
|
||||
----
|
||||
--
|
||||
|
||||
To access all multipart data as a `MultiValueMap`, you can use `@RequestBody`,
|
||||
as the following example shows:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -2821,6 +2839,7 @@ as the following example shows:
|
||||
}
|
||||
----
|
||||
<1> Using `@RequestBody`.
|
||||
--
|
||||
|
||||
===== `PartEvent`
|
||||
|
||||
@@ -2920,7 +2939,7 @@ See <<webflux-client-body-multipart>>.
|
||||
|
||||
[[webflux-ann-requestbody]]
|
||||
==== `@RequestBody`
|
||||
[.small]#<<web.adoc#mvc-ann-requestbody, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-requestbody, See equivalent in the Servlet stack>>#
|
||||
|
||||
You can use the `@RequestBody` annotation to have the request body read and deserialized into an
|
||||
`Object` through an <<webflux-codecs,HttpMessageReader>>.
|
||||
@@ -2994,7 +3013,7 @@ related operators:
|
||||
|
||||
[[webflux-ann-httpentity]]
|
||||
==== `HttpEntity`
|
||||
[.small]#<<web.adoc#mvc-ann-httpentity, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-httpentity, See equivalent in the Servlet stack>>#
|
||||
|
||||
`HttpEntity` is more or less identical to using <<webflux-ann-requestbody>> but is based on a
|
||||
container object that exposes request headers and the body. The following example uses an
|
||||
@@ -3020,7 +3039,7 @@ container object that exposes request headers and the body. The following exampl
|
||||
|
||||
[[webflux-ann-responsebody]]
|
||||
==== `@ResponseBody`
|
||||
[.small]#<<web.adoc#mvc-ann-responsebody, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-responsebody, See equivalent in the Servlet stack>>#
|
||||
|
||||
You can use the `@ResponseBody` annotation on a method to have the return serialized
|
||||
to the response body through an <<webflux-codecs, HttpMessageWriter>>. The following
|
||||
@@ -3063,7 +3082,7 @@ configure or customize message writing.
|
||||
|
||||
[[webflux-ann-responseentity]]
|
||||
==== `ResponseEntity`
|
||||
[.small]#<<web.adoc#mvc-ann-responseentity, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-responseentity, See equivalent in the Servlet stack>>#
|
||||
|
||||
`ResponseEntity` is like <<webflux-ann-responsebody>> but with status and headers. For example:
|
||||
|
||||
@@ -3110,7 +3129,7 @@ Spring offers support for the Jackson JSON library.
|
||||
|
||||
[[webflux-ann-jsonview]]
|
||||
===== JSON Views
|
||||
[.small]#<<web.adoc#mvc-ann-jackson, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-jackson, See equivalent in the Servlet stack>>#
|
||||
|
||||
Spring WebFlux provides built-in support for
|
||||
https://www.baeldung.com/jackson-json-view-annotation[Jackson's Serialization Views],
|
||||
@@ -3188,7 +3207,7 @@ controller method. Use a composite interface if you need to activate multiple vi
|
||||
|
||||
[[webflux-ann-modelattrib-methods]]
|
||||
=== `Model`
|
||||
[.small]#<<web.adoc#mvc-ann-modelattrib-methods, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-modelattrib-methods, See equivalent in the Servlet stack>>#
|
||||
|
||||
You can use the `@ModelAttribute` annotation:
|
||||
|
||||
@@ -3327,7 +3346,7 @@ as the following example shows:
|
||||
|
||||
[[webflux-ann-initbinder]]
|
||||
=== `DataBinder`
|
||||
[.small]#<<web.adoc#mvc-ann-initbinder, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-initbinder, See equivalent in the Servlet stack>>#
|
||||
|
||||
`@Controller` or `@ControllerAdvice` classes can have `@InitBinder` methods, to
|
||||
initialize instances of `WebDataBinder`. Those, in turn, are used to:
|
||||
@@ -3347,6 +3366,7 @@ do, except for `@ModelAttribute` (command object) arguments. Typically, they are
|
||||
with a `WebDataBinder` argument, for registrations, and a `void` return value.
|
||||
The following example uses the `@InitBinder` annotation:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -3381,11 +3401,14 @@ The following example uses the `@InitBinder` annotation:
|
||||
// ...
|
||||
}
|
||||
----
|
||||
<1> Using the `@InitBinder` annotation.
|
||||
--
|
||||
|
||||
Alternatively, when using a `Formatter`-based setup through a shared
|
||||
`FormattingConversionService`, you could re-use the same approach and register
|
||||
controller-specific `Formatter` instances, as the following example shows:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -3417,17 +3440,19 @@ controller-specific `Formatter` instances, as the following example shows:
|
||||
}
|
||||
----
|
||||
<1> Adding a custom formatter (a `DateFormatter`, in this case).
|
||||
--
|
||||
|
||||
|
||||
[[webflux-ann-initbinder-model-design]]
|
||||
==== Model Design
|
||||
[.small]#<<web.adoc#mvc-ann-initbinder-model-design, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-initbinder-model-design, See equivalent in the Servlet stack>>#
|
||||
|
||||
include::web-data-binding-model-design.adoc[]
|
||||
|
||||
|
||||
[[webflux-ann-controller-exceptions]]
|
||||
=== Exceptions
|
||||
[.small]#<<web.adoc#mvc-ann-exceptionhandler, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-exceptionhandler, See equivalent in the Servlet stack>>#
|
||||
|
||||
`@Controller` and <<webflux-ann-controller-advice, @ControllerAdvice>> classes can have
|
||||
`@ExceptionHandler` methods to handle exceptions from controller methods. The following
|
||||
@@ -3489,7 +3514,7 @@ for more detail.
|
||||
|
||||
[[webflux-ann-exceptionhandler-args]]
|
||||
==== Method Arguments
|
||||
[.small]#<<web.adoc#mvc-ann-exceptionhandler-args, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-exceptionhandler-args, See equivalent in the Servlet stack>>#
|
||||
|
||||
`@ExceptionHandler` methods support the same <<webflux-ann-arguments,method arguments>>
|
||||
as `@RequestMapping` methods, except the request body might have been consumed already.
|
||||
@@ -3498,7 +3523,7 @@ as `@RequestMapping` methods, except the request body might have been consumed a
|
||||
|
||||
[[webflux-ann-exceptionhandler-return-values]]
|
||||
==== Return Values
|
||||
[.small]#<<web.adoc#mvc-ann-exceptionhandler-return-values, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-exceptionhandler-return-values, See equivalent in the Servlet stack>>#
|
||||
|
||||
`@ExceptionHandler` methods support the same <<webflux-ann-return-types,return values>>
|
||||
as `@RequestMapping` methods.
|
||||
@@ -3507,7 +3532,7 @@ as `@RequestMapping` methods.
|
||||
|
||||
[[webflux-ann-controller-advice]]
|
||||
=== Controller Advice
|
||||
[.small]#<<web.adoc#mvc-ann-controller-advice, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-ann-controller-advice, See equivalent in the Servlet stack>>#
|
||||
|
||||
Typically, the `@ExceptionHandler`, `@InitBinder`, and `@ModelAttribute` methods apply
|
||||
within the `@Controller` class (or class hierarchy) in which they are declared. If you
|
||||
@@ -3575,7 +3600,7 @@ include::webflux-functional.adoc[leveloffset=+1]
|
||||
|
||||
[[webflux-uri-building]]
|
||||
== URI Links
|
||||
[.small]#<<web.adoc#mvc-uri-building, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-uri-building, See equivalent in the Servlet stack>>#
|
||||
|
||||
This section describes various options available in the Spring Framework to prepare URIs.
|
||||
|
||||
@@ -3586,7 +3611,7 @@ include::webflux-cors.adoc[leveloffset=+1]
|
||||
|
||||
[[webflux-ann-rest-exceptions]]
|
||||
== Error Responses
|
||||
[.small]#<<webmvc.adoc#mvc-ann-rest-exceptions, Web MVC>>#
|
||||
[.small]#<<webmvc.adoc#mvc-ann-rest-exceptions, See equivalent in the Servlet stack>>#
|
||||
|
||||
A common requirement for REST services is to include details in the body of error
|
||||
responses. The Spring Framework supports the "Problem Details for HTTP APIs"
|
||||
@@ -3610,7 +3635,7 @@ and any `ErrorResponseException`, and renders an error response with a body.
|
||||
|
||||
[[webflux-ann-rest-exceptions-render]]
|
||||
=== Render
|
||||
[.small]#<<webmvc.adoc#mvc-ann-rest-exceptions-render, Web MVC>>#
|
||||
[.small]#<<webmvc.adoc#mvc-ann-rest-exceptions-render, See equivalent in the Servlet stack>>#
|
||||
|
||||
You can return `ProblemDetail` or `ErrorResponse` from any `@ExceptionHandler` or from
|
||||
any `@RequestMapping` method to render an RFC 7807 response. This is processed as follows:
|
||||
@@ -3633,7 +3658,7 @@ use a protected method to map any exception to a `ProblemDetail`.
|
||||
|
||||
[[webflux-ann-rest-exceptions-non-standard]]
|
||||
=== Non-Standard Fields
|
||||
[.small]#<<webmvc.adoc#mvc-ann-rest-exceptions-non-standard, Web MVC>>#
|
||||
[.small]#<<webmvc.adoc#mvc-ann-rest-exceptions-non-standard, See equivalent in the Servlet stack>>#
|
||||
|
||||
You can extend an RFC 7807 response with non-standard fields in one of two ways.
|
||||
|
||||
@@ -3653,7 +3678,7 @@ from an existing `ProblemDetail`. This could be done centrally, e.g. from an
|
||||
|
||||
[[webflux-ann-rest-exceptions-i18n]]
|
||||
=== Internationalization
|
||||
[.small]#<<webmvc.adoc#mvc-ann-rest-exceptions-i18n, Web MVC>>#
|
||||
[.small]#<<webmvc.adoc#mvc-ann-rest-exceptions-i18n, See equivalent in the Servlet stack>>#
|
||||
|
||||
It is a common requirement to internationalize error response details, and good practice
|
||||
to customize the problem details for Spring WebFlux exceptions. This is supported as follows:
|
||||
@@ -3724,7 +3749,7 @@ qualified exception class name.
|
||||
|
||||
[[webflux-ann-rest-exceptions-client]]
|
||||
=== Client Handling
|
||||
[.small]#<<webmvc.adoc#mvc-ann-rest-exceptions-client, Web MVC>>#
|
||||
[.small]#<<webmvc.adoc#mvc-ann-rest-exceptions-client, See equivalent in the Servlet stack>>#
|
||||
|
||||
A client application can catch `WebClientResponseException`, when using the `WebClient`,
|
||||
or `RestClientResponseException` when using the `RestTemplate`, and use their
|
||||
@@ -3736,7 +3761,7 @@ or `RestClientResponseException` when using the `RestTemplate`, and use their
|
||||
|
||||
[[webflux-web-security]]
|
||||
== Web Security
|
||||
[.small]#<<web.adoc#mvc-web-security, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-web-security, See equivalent in the Servlet stack>>#
|
||||
|
||||
The https://spring.io/projects/spring-security[Spring Security] project provides support
|
||||
for protecting web applications from malicious exploits. See the Spring Security
|
||||
@@ -3752,7 +3777,7 @@ reference documentation, including:
|
||||
|
||||
[[webflux-caching]]
|
||||
== HTTP Caching
|
||||
[.small]#<<web.adoc#mvc-caching, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-caching, See equivalent in the Servlet stack>>#
|
||||
|
||||
HTTP caching can significantly improve the performance of a web application. HTTP caching
|
||||
revolves around the `Cache-Control` response header and subsequent conditional request
|
||||
@@ -3768,7 +3793,7 @@ This section describes the HTTP caching related options available in Spring WebF
|
||||
|
||||
[[webflux-caching-cachecontrol]]
|
||||
=== `CacheControl`
|
||||
[.small]#<<web.adoc#mvc-caching-cachecontrol, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-caching-cachecontrol, See equivalent in the Servlet stack>>#
|
||||
|
||||
{api-spring-framework}/http/CacheControl.html[`CacheControl`] provides support for
|
||||
configuring settings related to the `Cache-Control` header and is accepted as an argument
|
||||
@@ -3816,13 +3841,14 @@ use case-oriented approach that focuses on the common scenarios, as the followin
|
||||
|
||||
[[webflux-caching-etag-lastmodified]]
|
||||
=== Controllers
|
||||
[.small]#<<web.adoc#mvc-caching-etag-lastmodified, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-caching-etag-lastmodified, See equivalent in the Servlet stack>>#
|
||||
|
||||
Controllers can add explicit support for HTTP caching. We recommend doing so, since the
|
||||
`lastModified` or `ETag` value for a resource needs to be calculated before it can be compared
|
||||
against conditional request headers. A controller can add an `ETag` and `Cache-Control`
|
||||
settings to a `ResponseEntity`, as the following example shows:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -3856,6 +3882,7 @@ settings to a `ResponseEntity`, as the following example shows:
|
||||
.body(book)
|
||||
}
|
||||
----
|
||||
--
|
||||
|
||||
The preceding example sends a 304 (NOT_MODIFIED) response with an empty body if the comparison
|
||||
to the conditional request headers indicates the content has not changed. Otherwise, the
|
||||
@@ -3864,6 +3891,7 @@ to the conditional request headers indicates the content has not changed. Otherw
|
||||
You can also make the check against conditional request headers in the controller,
|
||||
as the following example shows:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -3903,6 +3931,7 @@ as the following example shows:
|
||||
<1> Application-specific calculation.
|
||||
<2> Response has been set to 304 (NOT_MODIFIED). No further processing.
|
||||
<3> Continue with request processing.
|
||||
--
|
||||
|
||||
There are three variants for checking conditional requests against `eTag` values, `lastModified`
|
||||
values, or both. For conditional `GET` and `HEAD` requests, you can set the response to
|
||||
@@ -3913,7 +3942,7 @@ to 412 (PRECONDITION_FAILED) to prevent concurrent modification.
|
||||
|
||||
[[webflux-caching-static-resources]]
|
||||
=== Static Resources
|
||||
[.small]#<<web.adoc#mvc-caching-static-resources, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-caching-static-resources, See equivalent in the Servlet stack>>#
|
||||
|
||||
You should serve static resources with a `Cache-Control` and conditional response headers
|
||||
for optimal performance. See the section on configuring <<webflux-config-static-resources>>.
|
||||
@@ -3924,7 +3953,7 @@ include::webflux-view.adoc[leveloffset=+1]
|
||||
|
||||
[[webflux-config]]
|
||||
== WebFlux Config
|
||||
[.small]#<<web.adoc#mvc-config, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-config, See equivalent in the Servlet stack>>#
|
||||
|
||||
The WebFlux Java configuration declares the components that are required to process
|
||||
requests with annotated controllers or functional endpoints, and it offers an API to
|
||||
@@ -3941,7 +3970,7 @@ gain full control over the configuration through the
|
||||
|
||||
[[webflux-config-enable]]
|
||||
=== Enabling WebFlux Config
|
||||
[.small]#<<web.adoc#mvc-config-enable, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-config-enable, See equivalent in the Servlet stack>>#
|
||||
|
||||
You can use the `@EnableWebFlux` annotation in your Java config, as the following example shows:
|
||||
|
||||
@@ -3970,7 +3999,7 @@ available on the classpath -- for JSON, XML, and others.
|
||||
|
||||
[[webflux-config-customize]]
|
||||
=== WebFlux config API
|
||||
[.small]#<<web.adoc#mvc-config-customize, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-config-customize, See equivalent in the Servlet stack>>#
|
||||
|
||||
In your Java configuration, you can implement the `WebFluxConfigurer` interface,
|
||||
as the following example shows:
|
||||
@@ -4001,7 +4030,7 @@ class WebConfig : WebFluxConfigurer {
|
||||
|
||||
[[webflux-config-conversion]]
|
||||
=== Conversion, formatting
|
||||
[.small]#<<web.adoc#mvc-config-conversion, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-config-conversion, See equivalent in the Servlet stack>>#
|
||||
|
||||
By default, formatters for various number and date types are installed, along with support
|
||||
for customization via `@NumberFormat` and `@DateTimeFormat` on fields.
|
||||
@@ -4078,7 +4107,7 @@ use `FormatterRegistrar` implementations.
|
||||
|
||||
[[webflux-config-validation]]
|
||||
=== Validation
|
||||
[.small]#<<web.adoc#mvc-config-validation, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-config-validation, See equivalent in the Servlet stack>>#
|
||||
|
||||
By default, if <<core.adoc#validation-beanvalidation-overview, Bean Validation>> is present
|
||||
on the classpath (for example, the Hibernate Validator), the `LocalValidatorFactoryBean`
|
||||
@@ -4153,7 +4182,7 @@ mark it with `@Primary` in order to avoid conflict with the one declared in the
|
||||
|
||||
[[webflux-config-content-negotiation]]
|
||||
=== Content Type Resolvers
|
||||
[.small]#<<web.adoc#mvc-config-content-negotiation, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-config-content-negotiation, See equivalent in the Servlet stack>>#
|
||||
|
||||
You can configure how Spring WebFlux determines the requested media types for
|
||||
`@Controller` instances from the request. By default, only the `Accept` header is checked,
|
||||
@@ -4191,7 +4220,7 @@ The following example shows how to customize the requested content type resoluti
|
||||
|
||||
[[webflux-config-message-codecs]]
|
||||
=== HTTP message codecs
|
||||
[.small]#<<web.adoc#mvc-config-message-converters, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-config-message-converters, See equivalent in the Servlet stack>>#
|
||||
|
||||
The following example shows how to customize how the request and response body are read and written:
|
||||
|
||||
@@ -4242,7 +4271,7 @@ It also automatically registers the following well-known modules if they are det
|
||||
|
||||
[[webflux-config-view-resolvers]]
|
||||
=== View Resolvers
|
||||
[.small]#<<web.adoc#mvc-config-view-resolvers, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-config-view-resolvers, See equivalent in the Servlet stack>>#
|
||||
|
||||
The following example shows how to configure view resolution:
|
||||
|
||||
@@ -4399,7 +4428,7 @@ See <<webflux-view>> for more on the view technologies that are integrated with
|
||||
|
||||
[[webflux-config-static-resources]]
|
||||
=== Static Resources
|
||||
[.small]#<<web.adoc#mvc-config-static-resources, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-config-static-resources, See equivalent in the Servlet stack>>#
|
||||
|
||||
This option provides a convenient way to serve static resources from a list of
|
||||
{api-spring-framework}/core/io/Resource.html[`Resource`]-based locations.
|
||||
@@ -4523,7 +4552,7 @@ for fine-grained control, e.g. last-modified behavior and optimized resource res
|
||||
|
||||
[[webflux-config-path-matching]]
|
||||
=== Path Matching
|
||||
[.small]#<<web.adoc#mvc-config-path-matching, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-config-path-matching, See equivalent in the Servlet stack>>#
|
||||
|
||||
You can customize options related to path matching. For details on the individual options, see the
|
||||
{api-spring-framework}/web/reactive/config/PathMatchConfigurer.html[`PathMatchConfigurer`] javadoc.
|
||||
@@ -4624,7 +4653,7 @@ For example:
|
||||
|
||||
[[webflux-config-advanced-java]]
|
||||
=== Advanced Configuration Mode
|
||||
[.small]#<<web.adoc#mvc-config-advanced-java, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-config-advanced-java, See equivalent in the Servlet stack>>#
|
||||
|
||||
`@EnableWebFlux` imports `DelegatingWebFluxConfiguration` that:
|
||||
|
||||
@@ -4664,7 +4693,7 @@ the classpath.
|
||||
|
||||
[[webflux-http2]]
|
||||
== HTTP/2
|
||||
[.small]#<<web.adoc#mvc-http2, Web MVC>>#
|
||||
[.small]#<<web.adoc#mvc-http2, See equivalent in the Servlet stack>>#
|
||||
|
||||
HTTP/2 is supported with Reactor Netty, Tomcat, Jetty, and Undertow. However, there are
|
||||
considerations related to server configuration. For more details, see the
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[[mvc-cors]]
|
||||
= CORS
|
||||
[.small]#<<web-reactive.adoc#webflux-cors, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-cors, See equivalent in the Reactive stack>>#
|
||||
|
||||
Spring MVC lets you handle CORS (Cross-Origin Resource Sharing). This section
|
||||
describes how to do so.
|
||||
@@ -10,7 +10,7 @@ describes how to do so.
|
||||
|
||||
[[mvc-cors-intro]]
|
||||
== Introduction
|
||||
[.small]#<<web-reactive.adoc#webflux-cors-intro, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-cors-intro, See equivalent in the Reactive stack>>#
|
||||
|
||||
For security reasons, browsers prohibit AJAX calls to resources outside the current origin.
|
||||
For example, you could have your bank account in one tab and evil.com in another. Scripts
|
||||
@@ -27,7 +27,7 @@ powerful workarounds based on IFRAME or JSONP.
|
||||
|
||||
[[mvc-cors-processing]]
|
||||
== Processing
|
||||
[.small]#<<web-reactive.adoc#webflux-cors-processing, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-cors-processing, See equivalent in the Reactive stack>>#
|
||||
|
||||
The CORS specification distinguishes between preflight, simple, and actual requests.
|
||||
To learn how CORS works, you can read
|
||||
@@ -77,7 +77,7 @@ To learn more from the source or make advanced customizations, check the code be
|
||||
|
||||
[[mvc-cors-controller]]
|
||||
== `@CrossOrigin`
|
||||
[.small]#<<web-reactive.adoc#webflux-cors-controller, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-cors-controller, See equivalent in the Reactive stack>>#
|
||||
|
||||
The {api-spring-framework}/web/bind/annotation/CrossOrigin.html[`@CrossOrigin`]
|
||||
annotation enables cross-origin requests on annotated controller methods,
|
||||
@@ -226,7 +226,7 @@ as the following example shows:
|
||||
|
||||
[[mvc-cors-global]]
|
||||
== Global Configuration
|
||||
[.small]#<<web-reactive.adoc#webflux-cors-global, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-cors-global, See equivalent in the Reactive stack>>#
|
||||
|
||||
In addition to fine-grained, controller method level configuration, you probably want to
|
||||
define some global CORS configuration, too. You can set URL-based `CorsConfiguration`
|
||||
@@ -252,7 +252,7 @@ the `allowOriginPatterns` property may be used to match to a dynamic set of orig
|
||||
|
||||
[[mvc-cors-global-java]]
|
||||
=== Java Configuration
|
||||
[.small]#<<web-reactive.adoc#webflux-cors-global, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-cors-global, See equivalent in the Reactive stack>>#
|
||||
|
||||
To enable CORS in the MVC Java config, you can use the `CorsRegistry` callback,
|
||||
as the following example shows:
|
||||
@@ -329,7 +329,7 @@ as the following example shows:
|
||||
|
||||
[[mvc-cors-filter]]
|
||||
== CORS Filter
|
||||
[.small]#<<webflux-cors.adoc#webflux-cors-webfilter, WebFlux>>#
|
||||
[.small]#<<webflux-cors.adoc#webflux-cors-webfilter, See equivalent in the Reactive stack>>#
|
||||
|
||||
You can apply CORS support through the built-in
|
||||
{api-spring-framework}/web/filter/CorsFilter.html[`CorsFilter`].
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[[webmvc-fn]]
|
||||
= Functional Endpoints
|
||||
[.small]#<<web-reactive.adoc#webflux-fn, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-fn, See equivalent in the Reactive stack>>#
|
||||
|
||||
Spring Web MVC includes WebMvc.fn, a lightweight functional programming model in which functions
|
||||
are used to route and handle requests and contracts are designed for immutability.
|
||||
@@ -12,7 +12,7 @@ the same <<web#mvc-servlet>>.
|
||||
|
||||
[[webmvc-fn-overview]]
|
||||
== Overview
|
||||
[.small]#<<web-reactive.adoc#webflux-fn-overview, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-fn-overview, See equivalent in the Reactive stack>>#
|
||||
|
||||
In WebMvc.fn, an HTTP request is handled with a `HandlerFunction`: a function that takes
|
||||
`ServerRequest` and returns a `ServerResponse`.
|
||||
@@ -40,7 +40,7 @@ as the following example shows:
|
||||
PersonRepository repository = ...
|
||||
PersonHandler handler = new PersonHandler(repository);
|
||||
|
||||
RouterFunction<ServerResponse> route = route()
|
||||
RouterFunction<ServerResponse> route = route() // <1>
|
||||
.GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
|
||||
.GET("/person", accept(APPLICATION_JSON), handler::listPeople)
|
||||
.POST("/person", handler::createPerson)
|
||||
@@ -64,6 +64,7 @@ as the following example shows:
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Create router using `route()`.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
@@ -110,7 +111,7 @@ If you register the `RouterFunction` as a bean, for instance by exposing it in a
|
||||
|
||||
[[webmvc-fn-handler-functions]]
|
||||
== HandlerFunction
|
||||
[.small]#<<web-reactive.adoc#webflux-fn-handler-functions, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-fn-handler-functions, See equivalent in the Reactive stack>>#
|
||||
|
||||
`ServerRequest` and `ServerResponse` are immutable interfaces that offer JDK 8-friendly
|
||||
access to the HTTP request and response, including headers, body, method, and status code.
|
||||
@@ -237,22 +238,22 @@ allows you to send Strings, or other objects as JSON. For example:
|
||||
.Java
|
||||
----
|
||||
public RouterFunction<ServerResponse> sse() {
|
||||
return route(GET("/sse"), request -> ServerResponse.sse(sseBuilder -> {
|
||||
// Save the sseBuilder object somewhere..
|
||||
}));
|
||||
return route(GET("/sse"), request -> ServerResponse.sse(sseBuilder -> {
|
||||
// Save the sseBuilder object somewhere..
|
||||
}));
|
||||
}
|
||||
|
||||
// In some other thread, sending a String
|
||||
sseBuilder.send("Hello world");
|
||||
|
||||
// Or an object, which will be transformed into JSON
|
||||
Person person = ...
|
||||
Person person = ...
|
||||
sseBuilder.send(person);
|
||||
|
||||
// Customize the event by using the other methods
|
||||
sseBuilder.id("42")
|
||||
.event("sse event")
|
||||
.data(person);
|
||||
// Customize the event by using the other methods
|
||||
sseBuilder.id("42")
|
||||
.event("sse event")
|
||||
.data(person);
|
||||
|
||||
// and done at some point
|
||||
sseBuilder.complete();
|
||||
@@ -260,23 +261,23 @@ allows you to send Strings, or other objects as JSON. For example:
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
fun sse(): RouterFunction<ServerResponse> = router {
|
||||
GET("/sse") { request -> ServerResponse.sse { sseBuilder ->
|
||||
// Save the sseBuilder object somewhere..
|
||||
}
|
||||
}
|
||||
fun sse(): RouterFunction<ServerResponse> = router {
|
||||
GET("/sse") { request -> ServerResponse.sse { sseBuilder ->
|
||||
// Save the sseBuilder object somewhere..
|
||||
}
|
||||
}
|
||||
|
||||
// In some other thread, sending a String
|
||||
sseBuilder.send("Hello world")
|
||||
|
||||
// Or an object, which will be transformed into JSON
|
||||
val person = ...
|
||||
val person = ...
|
||||
sseBuilder.send(person)
|
||||
|
||||
// Customize the event by using the other methods
|
||||
sseBuilder.id("42")
|
||||
.event("sse event")
|
||||
.data(person)
|
||||
// Customize the event by using the other methods
|
||||
sseBuilder.id("42")
|
||||
.event("sse event")
|
||||
.data(person)
|
||||
|
||||
// and done at some point
|
||||
sseBuilder.complete()
|
||||
@@ -289,6 +290,7 @@ allows you to send Strings, or other objects as JSON. For example:
|
||||
|
||||
We can write a handler function as a lambda, as the following example shows:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -301,6 +303,7 @@ HandlerFunction<ServerResponse> helloWorld =
|
||||
val helloWorld: (ServerRequest) -> ServerResponse =
|
||||
{ ServerResponse.ok().body("Hello World") }
|
||||
----
|
||||
--
|
||||
|
||||
That is convenient, but in an application we need multiple functions, and multiple inline
|
||||
lambda's can get messy.
|
||||
@@ -308,6 +311,7 @@ Therefore, it is useful to group related handler functions together into a handl
|
||||
has a similar role as `@Controller` in an annotation-based application.
|
||||
For example, the following class exposes a reactive `Person` repository:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -383,6 +387,7 @@ JSON.
|
||||
<3> `getPerson` is a handler function that returns a single person, identified by the `id` path
|
||||
variable. We retrieve that `Person` from the repository and create a JSON response, if it is
|
||||
found. If it is not found, we return a 404 Not Found response.
|
||||
--
|
||||
|
||||
|
||||
[[webmvc-fn-handler-validation]]
|
||||
@@ -458,7 +463,7 @@ See <<core.adoc#validation-beanvalidation, Spring Validation>>.
|
||||
|
||||
[[webmvc-fn-router-functions]]
|
||||
== `RouterFunction`
|
||||
[.small]#<<web-reactive.adoc#webflux-fn-router-functions, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-fn-router-functions, See equivalent in the Reactive stack>>#
|
||||
|
||||
Router functions are used to route the requests to the corresponding `HandlerFunction`.
|
||||
Typically, you do not write router functions yourself, but rather use a method on the
|
||||
@@ -667,7 +672,7 @@ We can further improve by using the `nest` method together with `accept`:
|
||||
|
||||
[[webmvc-fn-running]]
|
||||
== Running a Server
|
||||
[.small]#<<web-reactive.adoc#webflux-fn-running, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-fn-running, See equivalent in the Reactive stack>>#
|
||||
|
||||
You typically run router functions in a <<web.adoc#mvc-servlet, `DispatcherHandler`>>-based setup through the
|
||||
<<web.adoc#mvc-config>>, which uses Spring configuration to declare the
|
||||
@@ -760,7 +765,7 @@ The following example shows a WebFlux Java configuration:
|
||||
|
||||
[[webmvc-fn-handler-filter-function]]
|
||||
== Filtering Handler Functions
|
||||
[.small]#<<web-reactive.adoc#webflux-fn-handler-filter-function, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-fn-handler-filter-function, See equivalent in the Reactive stack>>#
|
||||
|
||||
You can filter handler functions by using the `before`, `after`, or `filter` methods on the routing
|
||||
function builder.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[[webmvc.test]]
|
||||
= Testing
|
||||
[.small]#<<web-reactive.adoc#webflux-test, Same in Spring WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-test, See equivalent in the Reactive stack>>#
|
||||
|
||||
This section summarizes the options available in `spring-test` for Spring MVC applications.
|
||||
|
||||
@@ -25,4 +25,4 @@ See <<testing.adoc#spring-mvc-test-client, Client REST Tests>> for more details.
|
||||
* `WebTestClient`: Built for testing WebFlux applications, but it can also be used for
|
||||
end-to-end integration testing, to any server, over an HTTP connection. It is a
|
||||
non-blocking, reactive client and is well suited for testing asynchronous and streaming
|
||||
scenarios.
|
||||
scenarios. See <<testing.adoc#webtestclient, `WebTestClient`>> for more details.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[[mvc-view]]
|
||||
= View Technologies
|
||||
[.small]#<<web-reactive.adoc#webflux-view, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-view, See equivalent in the Reactive stack>>#
|
||||
|
||||
The use of view technologies in Spring MVC is pluggable. Whether you decide to use
|
||||
Thymeleaf, Groovy Markup Templates, JSPs, or other technologies is primarily a matter of
|
||||
@@ -14,7 +14,7 @@ the templates are editable by external sources, since this can have security imp
|
||||
|
||||
[[mvc-view-thymeleaf]]
|
||||
== Thymeleaf
|
||||
[.small]#<<web-reactive.adoc#webflux-view-thymeleaf, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-view-thymeleaf, See equivalent in the Reactive stack>>#
|
||||
|
||||
Thymeleaf is a modern server-side Java template engine that emphasizes natural HTML
|
||||
templates that can be previewed in a browser by double-clicking, which is very helpful
|
||||
@@ -34,7 +34,7 @@ See https://www.thymeleaf.org/documentation.html[Thymeleaf+Spring] for more deta
|
||||
|
||||
[[mvc-view-freemarker]]
|
||||
== FreeMarker
|
||||
[.small]#<<web-reactive.adoc#webflux-view-freemarker, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-view-freemarker, See equivalent in the Reactive stack>>#
|
||||
|
||||
https://freemarker.apache.org/[Apache FreeMarker] is a template engine for generating any
|
||||
kind of text output from HTML to email and others. The Spring Framework has built-in
|
||||
@@ -44,7 +44,7 @@ integration for using Spring MVC with FreeMarker templates.
|
||||
|
||||
[[mvc-view-freemarker-contextconfig]]
|
||||
=== View Configuration
|
||||
[.small]#<<web-reactive.adoc#webflux-view-freemarker-contextconfig, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-view-freemarker-contextconfig, See equivalent in the Reactive stack>>#
|
||||
|
||||
The following example shows how to configure FreeMarker as a view technology:
|
||||
|
||||
@@ -125,7 +125,7 @@ returns a view name of `welcome`, the resolver looks for the
|
||||
|
||||
[[mvc-views-freemarker]]
|
||||
=== FreeMarker Configuration
|
||||
[.small]#<<web-reactive.adoc#webflux-views-freemarker, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-views-freemarker, See equivalent in the Reactive stack>>#
|
||||
|
||||
You can pass FreeMarker 'Settings' and 'SharedVariables' directly to the FreeMarker
|
||||
`Configuration` object (which is managed by Spring) by setting the appropriate bean
|
||||
@@ -164,7 +164,7 @@ with additional convenience macros for generating form input elements themselves
|
||||
|
||||
[[mvc-view-bind-macros]]
|
||||
==== The Bind Macros
|
||||
[.small]#<<web-reactive.adoc#webflux-view-bind-macros, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-view-bind-macros, See equivalent in the Reactive stack>>#
|
||||
|
||||
A standard set of macros are maintained within the `spring-webmvc.jar` file for
|
||||
FreeMarker, so they are always available to a suitably configured application.
|
||||
@@ -288,7 +288,7 @@ as the value for the `fieldType` parameter.
|
||||
|
||||
The parameters to any of the above macros have consistent meanings:
|
||||
|
||||
* `path`: The name of the field to bind to (ie "command.name")
|
||||
* `path`: The name of the field to bind to (for example, "command.name")
|
||||
* `options`: A `Map` of all the available values that can be selected from in the input
|
||||
field. The keys to the map represent the values that are POSTed back from the form
|
||||
and bound to the command object. Map objects stored against the keys are the labels
|
||||
@@ -576,7 +576,7 @@ syntax. The following example shows a sample template for an HTML page:
|
||||
|
||||
[[mvc-view-script]]
|
||||
== Script Views
|
||||
[.small]#<<web-reactive.adoc#webflux-view-script, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-view-script, See equivalent in the Reactive stack>>#
|
||||
|
||||
The Spring Framework has a built-in integration for using Spring MVC with any
|
||||
templating library that can run on top of the
|
||||
@@ -602,7 +602,7 @@ TIP: The basic rule for integrating any other script engine is that it must impl
|
||||
|
||||
[[mvc-view-script-dependencies]]
|
||||
=== Requirements
|
||||
[.small]#<<web-reactive.adoc#webflux-view-script-dependencies, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-view-script-dependencies, See equivalent in the Reactive stack>>#
|
||||
|
||||
You need to have the script engine on your classpath, the details of which vary by script engine:
|
||||
|
||||
@@ -622,7 +622,7 @@ through https://www.webjars.org/[WebJars].
|
||||
|
||||
[[mvc-view-script-integrate]]
|
||||
=== Script Templates
|
||||
[.small]#<<web-reactive.adoc#webflux-view-script, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-view-script, See equivalent in the Reactive stack>>#
|
||||
|
||||
You can declare a `ScriptTemplateConfigurer` bean to specify the script engine to use,
|
||||
the script files to load, what function to call to render templates, and so on.
|
||||
@@ -1649,138 +1649,6 @@ is the default type.
|
||||
|
||||
|
||||
|
||||
[[mvc-view-tiles]]
|
||||
== Tiles
|
||||
|
||||
You can integrate Tiles - just as any other view technology - in web
|
||||
applications that use Spring. This section describes, in a broad way, how to do so.
|
||||
|
||||
NOTE: This section focuses on Spring's support for Tiles version 3 in the
|
||||
`org.springframework.web.servlet.view.tiles3` package.
|
||||
|
||||
|
||||
|
||||
[[mvc-view-tiles-dependencies]]
|
||||
=== Dependencies
|
||||
|
||||
To be able to use Tiles, you have to add a dependency on Tiles version 3.0.1 or higher
|
||||
and https://tiles.apache.org/framework/dependency-management.html[its transitive dependencies]
|
||||
to your project.
|
||||
|
||||
|
||||
|
||||
[[mvc-view-tiles-integrate]]
|
||||
=== Configuration
|
||||
|
||||
To be able to use Tiles, you have to configure it by using files that contain definitions
|
||||
(for basic information on definitions and other Tiles concepts, see
|
||||
https://tiles.apache.org[]). In Spring, this is done by using the `TilesConfigurer`.
|
||||
The following example `ApplicationContext` configuration shows how to do so:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
|
||||
<property name="definitions">
|
||||
<list>
|
||||
<value>/WEB-INF/defs/general.xml</value>
|
||||
<value>/WEB-INF/defs/widgets.xml</value>
|
||||
<value>/WEB-INF/defs/administrator.xml</value>
|
||||
<value>/WEB-INF/defs/customer.xml</value>
|
||||
<value>/WEB-INF/defs/templates.xml</value>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
The preceding example defines five files that contain definitions. The files are all
|
||||
located in the `WEB-INF/defs` directory. At initialization of the `WebApplicationContext`,
|
||||
the files are loaded, and the definitions factory are initialized. After that has
|
||||
been done, the Tiles included in the definition files can be used as views within your
|
||||
Spring web application. To be able to use the views, you have to have a `ViewResolver`
|
||||
as with any other view technology in Spring: typically a convenient `TilesViewResolver`.
|
||||
|
||||
You can specify locale-specific Tiles definitions by adding an underscore and then
|
||||
the locale, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
|
||||
<property name="definitions">
|
||||
<list>
|
||||
<value>/WEB-INF/defs/tiles.xml</value>
|
||||
<value>/WEB-INF/defs/tiles_fr_FR.xml</value>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
With the preceding configuration, `tiles_fr_FR.xml` is used for requests with the `fr_FR` locale,
|
||||
and `tiles.xml` is used by default.
|
||||
|
||||
NOTE: Since underscores are used to indicate locales, we recommended not using
|
||||
them otherwise in the file names for Tiles definitions.
|
||||
|
||||
|
||||
|
||||
[[mvc-view-tiles-url]]
|
||||
==== `UrlBasedViewResolver`
|
||||
|
||||
The `UrlBasedViewResolver` instantiates the given `viewClass` for each view it has to
|
||||
resolve. The following bean defines a `UrlBasedViewResolver`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
|
||||
<property name="viewClass" value="org.springframework.web.servlet.view.tiles3.TilesView"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
|
||||
[[mvc-view-tiles-preparer]]
|
||||
==== `SimpleSpringPreparerFactory` and `SpringBeanPreparerFactory`
|
||||
|
||||
As an advanced feature, Spring also supports two special Tiles `PreparerFactory`
|
||||
implementations. See the Tiles documentation for details on how to use
|
||||
`ViewPreparer` references in your Tiles definition files.
|
||||
|
||||
You can specify `SimpleSpringPreparerFactory` to autowire `ViewPreparer` instances based on
|
||||
specified preparer classes, applying Spring's container callbacks as well as applying
|
||||
configured Spring BeanPostProcessors. If Spring's context-wide annotation configuration has
|
||||
been activated, annotations in `ViewPreparer` classes are automatically detected and
|
||||
applied. Note that this expects preparer classes in the Tiles definition files, as
|
||||
the default `PreparerFactory` does.
|
||||
|
||||
You can specify `SpringBeanPreparerFactory` to operate on specified preparer names (instead
|
||||
of classes), obtaining the corresponding Spring bean from the DispatcherServlet's
|
||||
application context. The full bean creation process is in the control of the Spring
|
||||
application context in this case, allowing for the use of explicit dependency injection
|
||||
configuration, scoped beans, and so on. Note that you need to define one Spring bean definition
|
||||
for each preparer name (as used in your Tiles definitions). The following example shows
|
||||
how to define a `SpringBeanPreparerFactory` property on a `TilesConfigurer` bean:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
|
||||
<property name="definitions">
|
||||
<list>
|
||||
<value>/WEB-INF/defs/general.xml</value>
|
||||
<value>/WEB-INF/defs/widgets.xml</value>
|
||||
<value>/WEB-INF/defs/administrator.xml</value>
|
||||
<value>/WEB-INF/defs/customer.xml</value>
|
||||
<value>/WEB-INF/defs/templates.xml</value>
|
||||
</list>
|
||||
</property>
|
||||
|
||||
<!-- resolving preparer names as Spring bean definition names -->
|
||||
<property name="preparerFactoryClass"
|
||||
value="org.springframework.web.servlet.view.tiles3.SpringBeanPreparerFactory"/>
|
||||
|
||||
</bean>
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
||||
[[mvc-view-feeds]]
|
||||
== RSS and Atom
|
||||
|
||||
@@ -1965,7 +1833,7 @@ an external definition (by name) or as a `View` instance from the handler method
|
||||
|
||||
[[mvc-view-jackson]]
|
||||
== Jackson
|
||||
[.small]#<<web-reactive.adoc#webflux-view-httpmessagewriter, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-view-httpmessagewriter, See equivalent in the Reactive stack>>#
|
||||
|
||||
Spring offers support for the Jackson JSON library.
|
||||
|
||||
@@ -1973,7 +1841,7 @@ Spring offers support for the Jackson JSON library.
|
||||
|
||||
[[mvc-view-json-mapping]]
|
||||
=== Jackson-based JSON MVC Views
|
||||
[.small]#<<web-reactive.adoc#webflux-view-httpmessagewriter, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-view-httpmessagewriter, See equivalent in the Reactive stack>>#
|
||||
|
||||
The `MappingJackson2JsonView` uses the Jackson library's `ObjectMapper` to render the response
|
||||
content as JSON. By default, the entire contents of the model map (with the exception of
|
||||
@@ -1992,7 +1860,7 @@ serializers and deserializers for specific types.
|
||||
|
||||
[[mvc-view-xml-mapping]]
|
||||
=== Jackson-based XML Views
|
||||
[.small]#<<web-reactive.adoc#webflux-view-httpmessagewriter, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-view-httpmessagewriter, See equivalent in the Reactive stack>>#
|
||||
|
||||
`MappingJackson2XmlView` uses the
|
||||
https://github.com/FasterXML/jackson-dataformat-xml[Jackson XML extension's] `XmlMapper`
|
||||
|
||||
@@ -23,7 +23,7 @@ https://github.com/spring-projects/spring-framework/wiki/Spring-Framework-Versio
|
||||
|
||||
[[mvc-servlet]]
|
||||
== DispatcherServlet
|
||||
[.small]#<<web-reactive.adoc#webflux-dispatcher-handler, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-dispatcher-handler, See equivalent in the Reactive stack>>#
|
||||
|
||||
Spring MVC, as many other web frameworks, is designed around the front controller
|
||||
pattern where a central `Servlet`, the `DispatcherServlet`, provides a shared algorithm
|
||||
@@ -240,7 +240,7 @@ TIP: If an application context hierarchy is not required, applications may confi
|
||||
|
||||
[[mvc-servlet-special-bean-types]]
|
||||
=== Special Bean Types
|
||||
[.small]#<<web-reactive.adoc#webflux-special-bean-types, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-special-bean-types, See equivalent in the Reactive stack>>#
|
||||
|
||||
The `DispatcherServlet` delegates to special beans to process requests and render the
|
||||
appropriate responses. By "`special beans`" we mean Spring-managed `Object` instances that
|
||||
@@ -300,7 +300,7 @@ The following table lists the special beans detected by the `DispatcherServlet`:
|
||||
|
||||
[[mvc-servlet-config]]
|
||||
=== Web MVC Config
|
||||
[.small]#<<web-reactive.adoc#webflux-framework-config, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-framework-config, See equivalent in the Reactive stack>>#
|
||||
|
||||
Applications can declare the infrastructure beans listed in <<mvc-servlet-special-bean-types>>
|
||||
that are required to process requests. The `DispatcherServlet` checks the
|
||||
@@ -503,7 +503,7 @@ override the `createDispatcherServlet` method.
|
||||
|
||||
[[mvc-servlet-sequence]]
|
||||
=== Processing
|
||||
[.small]#<<web-reactive.adoc#webflux-dispatcher-handler-sequence, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-dispatcher-handler-sequence, See equivalent in the Reactive stack>>#
|
||||
|
||||
The `DispatcherServlet` processes requests as follows:
|
||||
|
||||
@@ -662,7 +662,7 @@ declare it as an <<mvc-ann-controller-advice>> bean or configure it directly on
|
||||
|
||||
[[mvc-exceptionhandlers]]
|
||||
=== Exceptions
|
||||
[.small]#<<web-reactive.adoc#webflux-dispatcher-exceptions, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-dispatcher-exceptions, See equivalent in the Reactive stack>>#
|
||||
|
||||
If an exception occurs during request mapping or is thrown from a request handler (such as
|
||||
a `@Controller`), the `DispatcherServlet` delegates to a chain of `HandlerExceptionResolver`
|
||||
@@ -773,7 +773,7 @@ however, use both a `WebApplicationInitializer` and a minimal `web.xml`.
|
||||
|
||||
[[mvc-viewresolver]]
|
||||
=== View Resolution
|
||||
[.small]#<<web-reactive.adoc#webflux-viewresolution, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-viewresolution, See equivalent in the Reactive stack>>#
|
||||
|
||||
Spring MVC defines the `ViewResolver` and `View` interfaces that let you render
|
||||
models in a browser without tying you to a specific view technology. `ViewResolver`
|
||||
@@ -802,8 +802,8 @@ The following table provides more details on the `ViewResolver` hierarchy:
|
||||
|
||||
| `InternalResourceViewResolver`
|
||||
| Convenient subclass of `UrlBasedViewResolver` that supports `InternalResourceView` (in
|
||||
effect, Servlets and JSPs) and subclasses such as `JstlView` and `TilesView`. You can
|
||||
specify the view class for all views generated by this resolver by using `setViewClass(..)`.
|
||||
effect, Servlets and JSPs) and subclasses such as `JstlView`. You can specify the view
|
||||
class for all views generated by this resolver by using `setViewClass(..)`.
|
||||
See the {api-spring-framework}/web/reactive/result/view/UrlBasedViewResolver.html[`UrlBasedViewResolver`]
|
||||
javadoc for details.
|
||||
|
||||
@@ -825,7 +825,7 @@ The following table provides more details on the `ViewResolver` hierarchy:
|
||||
|
||||
[[mvc-viewresolver-handling]]
|
||||
==== Handling
|
||||
[.small]#<<web-reactive.adoc#webflux-viewresolution-handling, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-viewresolution-handling, See equivalent in the Reactive stack>>#
|
||||
|
||||
You can chain view resolvers by declaring more than one resolver bean and, if necessary, by
|
||||
setting the `order` property to specify ordering. Remember, the higher the order property,
|
||||
@@ -846,7 +846,7 @@ rendering without controller logic.
|
||||
|
||||
[[mvc-redirecting-redirect-prefix]]
|
||||
==== Redirecting
|
||||
[.small]#<<web-reactive.adoc#webflux-redirecting-redirect-prefix, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-redirecting-redirect-prefix, See equivalent in the Reactive stack>>#
|
||||
|
||||
The special `redirect:` prefix in a view name lets you perform a redirect. The
|
||||
`UrlBasedViewResolver` (and its subclasses) recognize this as an instruction that a
|
||||
@@ -876,7 +876,7 @@ Servlet/JSP engine. Note that you may also chain multiple view resolvers, instea
|
||||
|
||||
[[mvc-multiple-representations]]
|
||||
==== Content Negotiation
|
||||
[.small]#<<web-reactive.adoc#webflux-multiple-representations, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-multiple-representations, See equivalent in the Reactive stack>>#
|
||||
|
||||
{api-spring-framework}/web/servlet/view/ContentNegotiatingViewResolver.html[`ContentNegotiatingViewResolver`]
|
||||
does not resolve views itself but rather delegates
|
||||
@@ -978,7 +978,7 @@ The following table describes the properties `CookieLocaleResolver`:
|
||||
| Property | Default | Description
|
||||
|
||||
| `cookieName`
|
||||
| classname + LOCALE
|
||||
| class name + LOCALE
|
||||
| The name of the cookie
|
||||
|
||||
| `cookieMaxAge`
|
||||
@@ -1138,12 +1138,13 @@ request with a simple request parameter.
|
||||
|
||||
[[mvc-multipart]]
|
||||
=== Multipart Resolver
|
||||
[.small]#<<web-reactive.adoc#webflux-multipart, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-multipart, See equivalent in the Reactive stack>>#
|
||||
|
||||
`MultipartResolver` from the `org.springframework.web.multipart` package is a strategy
|
||||
for parsing multipart requests including file uploads. There is one implementation
|
||||
based on https://commons.apache.org/proper/commons-fileupload[Commons FileUpload] and
|
||||
another based on Servlet multipart request parsing.
|
||||
for parsing multipart requests including file uploads. There is a container-based
|
||||
`StandardServletMultipartResolver` implementation for Servlet multipart request parsing.
|
||||
Note that the outdated `CommonsMultipartResolver` based on Apache Commons FileUpload is
|
||||
not available anymore, as of Spring Framework 6.0 with its new Servlet 5.0+ baseline.
|
||||
|
||||
To enable multipart handling, you need to declare a `MultipartResolver` bean in your
|
||||
`DispatcherServlet` Spring configuration with a name of `multipartResolver`.
|
||||
@@ -1153,26 +1154,6 @@ content wraps the current `HttpServletRequest` as a `MultipartHttpServletRequest
|
||||
provide access to resolved files in addition to exposing parts as request parameters.
|
||||
|
||||
|
||||
[[mvc-multipart-resolver-commons]]
|
||||
==== Apache Commons `FileUpload`
|
||||
|
||||
To use Apache Commons `FileUpload`, you can configure a bean of type
|
||||
`CommonsMultipartResolver` with a name of `multipartResolver`. You also need to have
|
||||
the `commons-fileupload` jar as a dependency on your classpath.
|
||||
|
||||
This resolver variant delegates to a local library within the application, providing
|
||||
maximum portability across Servlet containers. As an alternative, consider standard
|
||||
Servlet multipart resolution through the container's own parser as discussed below.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Commons FileUpload traditionally applies to POST requests only but accepts any
|
||||
`multipart/` content type. See the
|
||||
{api-spring-framework}/web/multipart/commons/CommonsMultipartResolver.html[`CommonsMultipartResolver`]
|
||||
javadoc for details and configuration options.
|
||||
====
|
||||
|
||||
|
||||
[[mvc-multipart-resolver-standard]]
|
||||
==== Servlet Multipart Parsing
|
||||
|
||||
@@ -1233,7 +1214,7 @@ javadoc for details and configuration options.
|
||||
|
||||
[[mvc-logging]]
|
||||
=== Logging
|
||||
[.small]#<<web-reactive.adoc#webflux-logging, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-logging, See equivalent in the Reactive stack>>#
|
||||
|
||||
DEBUG-level logging in Spring MVC is designed to be compact, minimal, and
|
||||
human-friendly. It focuses on high-value bits of information that are useful over and
|
||||
@@ -1249,7 +1230,7 @@ not meet the stated goals, please let us know.
|
||||
|
||||
[[mvc-logging-sensitive-data]]
|
||||
==== Sensitive Data
|
||||
[.small]#<<web-reactive.adoc#webflux-logging-sensitive-data, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-logging-sensitive-data, See equivalent in the Reactive stack>>#
|
||||
|
||||
DEBUG and TRACE logging may log sensitive information. This is why request parameters and
|
||||
headers are masked by default and their logging in full must be enabled explicitly
|
||||
@@ -1313,7 +1294,7 @@ public class MyInitializer
|
||||
|
||||
[[filters]]
|
||||
== Filters
|
||||
[.small]#<<web-reactive.adoc#webflux-filters, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-filters, See equivalent in the Reactive stack>>#
|
||||
|
||||
The `spring-web` module provides some useful filters:
|
||||
|
||||
@@ -1340,7 +1321,7 @@ available through the `ServletRequest.getParameter{asterisk}()` family of method
|
||||
|
||||
[[filters-forwarded-headers]]
|
||||
=== Forwarded Headers
|
||||
[.small]#<<web-reactive.adoc#webflux-forwarded-headers, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-forwarded-headers, See equivalent in the Reactive stack>>#
|
||||
|
||||
As a request goes through proxies (such as load balancers) the host, port, and
|
||||
scheme may change, and that makes it a challenge to create links that point to the correct
|
||||
@@ -1401,7 +1382,7 @@ the filter via `web.xml` or in Spring Boot via a `FilterRegistrationBean` be sur
|
||||
|
||||
[[filters-cors]]
|
||||
=== CORS
|
||||
[.small]#<<web-reactive.adoc#webflux-filters-cors, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-filters-cors, See equivalent in the Reactive stack>>#
|
||||
|
||||
Spring MVC provides fine-grained support for CORS configuration through annotations on
|
||||
controllers. However, when used with Spring Security, we advise relying on the built-in
|
||||
@@ -1414,7 +1395,7 @@ See the sections on <<mvc-cors>> and the <<mvc-cors-filter>> for more details.
|
||||
|
||||
[[mvc-controller]]
|
||||
== Annotated Controllers
|
||||
[.small]#<<web-reactive.adoc#webflux-controller, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-controller, See equivalent in the Reactive stack>>#
|
||||
|
||||
Spring MVC provides an annotation-based programming model where `@Controller` and
|
||||
`@RestController` components use annotations to express request mappings, request input,
|
||||
@@ -1461,7 +1442,7 @@ programming model described in this section.
|
||||
|
||||
[[mvc-ann-controller]]
|
||||
=== Declaration
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-controller, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-controller, See equivalent in the Reactive stack>>#
|
||||
|
||||
You can define controller beans by using a standard Spring bean definition in the
|
||||
Servlet's `WebApplicationContext`. The `@Controller` stereotype allows for auto-detection,
|
||||
@@ -1523,7 +1504,7 @@ directly to the response body versus view resolution and rendering with an HTML
|
||||
|
||||
[[mvc-ann-requestmapping-proxying]]
|
||||
==== AOP Proxies
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-requestmapping-proxying, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-requestmapping-proxying, See equivalent in the Reactive stack>>#
|
||||
|
||||
In some cases, you may need to decorate a controller with an AOP proxy at runtime.
|
||||
One example is if you choose to have `@Transactional` annotations directly on the
|
||||
@@ -1545,7 +1526,7 @@ Please, enable class based proxying, or otherwise the interface must also have a
|
||||
|
||||
[[mvc-ann-requestmapping]]
|
||||
=== Request Mapping
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-requestmapping, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-requestmapping, See equivalent in the Reactive stack>>#
|
||||
|
||||
You can use the `@RequestMapping` annotation to map requests to controllers methods. It has
|
||||
various attributes to match by URL, HTTP method, request parameters, headers, and media
|
||||
@@ -1610,7 +1591,7 @@ The following example has type and method level mappings:
|
||||
|
||||
[[mvc-ann-requestmapping-uri-templates]]
|
||||
==== URI patterns
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-requestmapping-uri-templates, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-requestmapping-uri-templates, See equivalent in the Reactive stack>>#
|
||||
|
||||
`@RequestMapping` methods can be mapped using URL patterns. There are two alternatives:
|
||||
|
||||
@@ -1732,7 +1713,7 @@ some external configuration.
|
||||
|
||||
[[mvc-ann-requestmapping-pattern-comparison]]
|
||||
==== Pattern Comparison
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-requestmapping-pattern-comparison, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-requestmapping-pattern-comparison, See equivalent in the Reactive stack>>#
|
||||
|
||||
When multiple patterns match a URL, the best match must be selected. This is done with
|
||||
one of the following depending on whether use of parsed `PathPattern` is enabled for use or not:
|
||||
@@ -1813,7 +1794,7 @@ recommendations related to RFD.
|
||||
|
||||
[[mvc-ann-requestmapping-consumes]]
|
||||
==== Consumable Media Types
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-requestmapping-consumes, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-requestmapping-consumes, See equivalent in the Reactive stack>>#
|
||||
|
||||
You can narrow the request mapping based on the `Content-Type` of the request,
|
||||
as the following example shows:
|
||||
@@ -1851,7 +1832,7 @@ TIP: `MediaType` provides constants for commonly used media types, such as
|
||||
|
||||
[[mvc-ann-requestmapping-produces]]
|
||||
==== Producible Media Types
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-requestmapping-produces, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-requestmapping-produces, See equivalent in the Reactive stack>>#
|
||||
|
||||
You can narrow the request mapping based on the `Accept` request header and the list of
|
||||
content types that a controller method produces, as the following example shows:
|
||||
@@ -1891,7 +1872,7 @@ TIP: `MediaType` provides constants for commonly used media types, such as
|
||||
|
||||
[[mvc-ann-requestmapping-params-and-headers]]
|
||||
==== Parameters, headers
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-requestmapping-params-and-headers, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-requestmapping-params-and-headers, See equivalent in the Reactive stack>>#
|
||||
|
||||
You can narrow request mappings based on request parameter conditions. You can test for the
|
||||
presence of a request parameter (`myParam`), for the absence of one (`!myParam`), or for a
|
||||
@@ -1946,7 +1927,7 @@ instead.
|
||||
|
||||
[[mvc-ann-requestmapping-head-options]]
|
||||
==== HTTP HEAD, OPTIONS
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-requestmapping-head-options, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-requestmapping-head-options, See equivalent in the Reactive stack>>#
|
||||
|
||||
`@GetMapping` (and `@RequestMapping(method=HttpMethod.GET)`) support HTTP HEAD
|
||||
transparently for request mapping. Controller methods do not need to change.
|
||||
@@ -1972,7 +1953,7 @@ is not necessary in the common case.
|
||||
|
||||
[[mvc-ann-requestmapping-composed]]
|
||||
==== Custom Annotations
|
||||
[.small]#<<web-reactive.adoc#mvc-ann-requestmapping-head-options, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#mvc-ann-requestmapping-head-options, See equivalent in the Reactive stack>>#
|
||||
|
||||
Spring MVC supports the use of <<core.adoc#beans-meta-annotations, composed annotations>>
|
||||
for request mapping. Those are annotations that are themselves meta-annotated with
|
||||
@@ -1993,7 +1974,7 @@ you can check the custom attribute and return your own `RequestCondition`.
|
||||
|
||||
[[mvc-ann-requestmapping-registration]]
|
||||
==== Explicit Registrations
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-requestmapping-registration, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-requestmapping-registration, See equivalent in the Reactive stack>>#
|
||||
|
||||
You can programmatically register handler methods, which you can use for dynamic
|
||||
registrations or for advanced cases, such as different instances of the same handler
|
||||
@@ -2046,7 +2027,7 @@ under different URLs. The following example registers a handler method:
|
||||
|
||||
[[mvc-ann-methods]]
|
||||
=== Handler Methods
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-methods, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-methods, See equivalent in the Reactive stack>>#
|
||||
|
||||
`@RequestMapping` handler methods have a flexible signature and can choose from a range of
|
||||
supported controller method arguments and return values.
|
||||
@@ -2054,7 +2035,7 @@ supported controller method arguments and return values.
|
||||
|
||||
[[mvc-ann-arguments]]
|
||||
==== Method Arguments
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-arguments, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-arguments, See equivalent in the Reactive stack>>#
|
||||
|
||||
The next table describes the supported controller method arguments. Reactive types are not supported
|
||||
for any arguments.
|
||||
@@ -2195,7 +2176,7 @@ and others) and is equivalent to `required=false`.
|
||||
|
||||
[[mvc-ann-return-types]]
|
||||
==== Return Values
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-return-types, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-return-types, See equivalent in the Reactive stack>>#
|
||||
|
||||
The next table describes the supported controller method return values. Reactive types are
|
||||
supported for all return values.
|
||||
@@ -2299,7 +2280,7 @@ supported for all return values.
|
||||
|
||||
[[mvc-ann-typeconversion]]
|
||||
==== Type Conversion
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-typeconversion, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-typeconversion, See equivalent in the Reactive stack>>#
|
||||
|
||||
Some annotated controller method arguments that represent `String`-based request input (such as
|
||||
`@RequestParam`, `@RequestHeader`, `@PathVariable`, `@MatrixVariable`, and `@CookieValue`)
|
||||
@@ -2332,7 +2313,7 @@ an empty original value, so the corresponding `Missing...Exception` variants wil
|
||||
|
||||
[[mvc-ann-matrix-variables]]
|
||||
==== Matrix Variables
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-matrix-variables, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-matrix-variables, See equivalent in the Reactive stack>>#
|
||||
|
||||
https://tools.ietf.org/html/rfc3986#section-3.3[RFC 3986] discusses name-value pairs in
|
||||
path segments. In Spring MVC, we refer to those as "`matrix variables`" based on an
|
||||
@@ -2472,7 +2453,7 @@ you need to set a `UrlPathHelper` with `removeSemicolonContent=false` through
|
||||
|
||||
[[mvc-ann-requestparam]]
|
||||
==== `@RequestParam`
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-requestparam, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-requestparam, See equivalent in the Reactive stack>>#
|
||||
|
||||
You can use the `@RequestParam` annotation to bind Servlet request parameters (that is,
|
||||
query parameters or form data) to a method argument in a controller.
|
||||
@@ -2548,7 +2529,7 @@ with `@RequestParam`.
|
||||
|
||||
[[mvc-ann-requestheader]]
|
||||
==== `@RequestHeader`
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-requestheader, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-requestheader, See equivalent in the Reactive stack>>#
|
||||
|
||||
You can use the `@RequestHeader` annotation to bind a request header to a method argument in a
|
||||
controller.
|
||||
@@ -2609,7 +2590,7 @@ example, a method parameter annotated with `@RequestHeader("Accept")` can be of
|
||||
|
||||
[[mvc-ann-cookievalue]]
|
||||
==== `@CookieValue`
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-cookievalue, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-cookievalue, See equivalent in the Reactive stack>>#
|
||||
|
||||
You can use the `@CookieValue` annotation to bind the value of an HTTP cookie to a method argument
|
||||
in a controller.
|
||||
@@ -2649,7 +2630,7 @@ See <<mvc-ann-typeconversion>>.
|
||||
|
||||
[[mvc-ann-modelattrib-method-args]]
|
||||
==== `@ModelAttribute`
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-modelattrib-method-args, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-modelattrib-method-args, See equivalent in the Reactive stack>>#
|
||||
|
||||
You can use the `@ModelAttribute` annotation on a method argument to access an attribute from
|
||||
the model or have it be instantiated if not present. The model attribute is also overlain with
|
||||
@@ -2842,7 +2823,7 @@ with `@ModelAttribute`.
|
||||
|
||||
[[mvc-ann-sessionattributes]]
|
||||
==== `@SessionAttributes`
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-sessionattributes, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-sessionattributes, See equivalent in the Reactive stack>>#
|
||||
|
||||
`@SessionAttributes` is used to store model attributes in the HTTP Servlet session between
|
||||
requests. It is a type-level annotation that declares the session attributes used by a
|
||||
@@ -2926,7 +2907,7 @@ class EditPetForm {
|
||||
|
||||
[[mvc-ann-sessionattribute]]
|
||||
==== `@SessionAttribute`
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-sessionattribute, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-sessionattribute, See equivalent in the Reactive stack>>#
|
||||
|
||||
If you need access to pre-existing session attributes that are managed globally
|
||||
(that is, outside the controller -- for example, by a filter) and may or may not be present,
|
||||
@@ -2964,7 +2945,7 @@ workflow, consider using `@SessionAttributes` as described in
|
||||
|
||||
[[mvc-ann-requestattrib]]
|
||||
==== `@RequestAttribute`
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-requestattrib, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-requestattrib, See equivalent in the Reactive stack>>#
|
||||
|
||||
Similar to `@SessionAttribute`, you can use the `@RequestAttribute` annotations to
|
||||
access pre-existing request attributes created earlier (for example, by a Servlet `Filter`
|
||||
@@ -3091,7 +3072,7 @@ Therefore, we recommend that you use flash attributes mainly for redirect scenar
|
||||
|
||||
[[mvc-multipart-forms]]
|
||||
==== Multipart
|
||||
[.small]#<<web-reactive.adoc#webflux-multipart-forms, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-multipart-forms, See equivalent in the Reactive stack>>#
|
||||
|
||||
After a `MultipartResolver` has been <<mvc-multipart,enabled>>, the content of POST
|
||||
requests with `multipart/form-data` is parsed and accessible as regular request
|
||||
@@ -3276,7 +3257,7 @@ as the following example shows:
|
||||
|
||||
[[mvc-ann-requestbody]]
|
||||
==== `@RequestBody`
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-requestbody, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-requestbody, See equivalent in the Reactive stack>>#
|
||||
|
||||
You can use the `@RequestBody` annotation to have the request body read and deserialized into an
|
||||
`Object` through an <<integration.adoc#rest-message-conversion, `HttpMessageConverter`>>.
|
||||
@@ -3330,7 +3311,7 @@ as the following example shows:
|
||||
|
||||
[[mvc-ann-httpentity]]
|
||||
==== HttpEntity
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-httpentity, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-httpentity, See equivalent in the Reactive stack>>#
|
||||
|
||||
`HttpEntity` is more or less identical to using <<mvc-ann-requestbody>> but is based on a
|
||||
container object that exposes request headers and body. The following listing shows an example:
|
||||
@@ -3356,7 +3337,7 @@ container object that exposes request headers and body. The following listing sh
|
||||
|
||||
[[mvc-ann-responsebody]]
|
||||
==== `@ResponseBody`
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-responsebody, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-responsebody, See equivalent in the Reactive stack>>#
|
||||
|
||||
You can use the `@ResponseBody` annotation on a method to have the return serialized
|
||||
to the response body through an
|
||||
@@ -3398,7 +3379,7 @@ See <<mvc-ann-jackson>> for details.
|
||||
|
||||
[[mvc-ann-responseentity]]
|
||||
==== ResponseEntity
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-responseentity, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-responseentity, See equivalent in the Reactive stack>>#
|
||||
|
||||
`ResponseEntity` is like <<mvc-ann-responsebody>> but with status and headers. For example:
|
||||
|
||||
@@ -3442,7 +3423,7 @@ Spring offers support for the Jackson JSON library.
|
||||
|
||||
[[mvc-ann-jsonview]]
|
||||
===== JSON Views
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-jsonview, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-jsonview, See equivalent in the Reactive stack>>#
|
||||
|
||||
Spring MVC provides built-in support for
|
||||
https://www.baeldung.com/jackson-json-view-annotation[Jackson's Serialization Views],
|
||||
@@ -3582,7 +3563,7 @@ to the model, as the following example shows:
|
||||
|
||||
[[mvc-ann-modelattrib-methods]]
|
||||
=== Model
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-modelattrib-methods, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-modelattrib-methods, See equivalent in the Reactive stack>>#
|
||||
|
||||
You can use the `@ModelAttribute` annotation:
|
||||
|
||||
@@ -3680,7 +3661,7 @@ unless the return value is a `String` that would otherwise be interpreted as a v
|
||||
|
||||
[[mvc-ann-initbinder]]
|
||||
=== `DataBinder`
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-initbinder, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-initbinder, See equivalent in the Reactive stack>>#
|
||||
|
||||
`@Controller` or `@ControllerAdvice` classes can have `@InitBinder` methods that
|
||||
initialize instances of `WebDataBinder`, and those, in turn, can:
|
||||
@@ -3774,14 +3755,14 @@ controller-specific `Formatter` implementations, as the following example shows:
|
||||
|
||||
[[mvc-ann-initbinder-model-design]]
|
||||
==== Model Design
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-initbinder-model-design, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-initbinder-model-design, See equivalent in the Reactive stack>>#
|
||||
|
||||
include::web-data-binding-model-design.adoc[]
|
||||
|
||||
|
||||
[[mvc-ann-exceptionhandler]]
|
||||
=== Exceptions
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-controller-exceptions, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-controller-exceptions, See equivalent in the Reactive stack>>#
|
||||
|
||||
`@Controller` and <<mvc-ann-controller-advice, @ControllerAdvice>> classes can have
|
||||
`@ExceptionHandler` methods to handle exceptions from controller methods, as the following example shows:
|
||||
@@ -3907,7 +3888,7 @@ level, <<mvc-exceptionhandlers, HandlerExceptionResolver>> mechanism.
|
||||
|
||||
[[mvc-ann-exceptionhandler-args]]
|
||||
==== Method Arguments
|
||||
[.small]#<<webflux.adoc#webflux-ann-exceptionhandler-args, WebFlux>>#
|
||||
[.small]#<<webflux.adoc#webflux-ann-exceptionhandler-args, See equivalent in the Reactive stack>>#
|
||||
|
||||
`@ExceptionHandler` methods support the following arguments:
|
||||
|
||||
@@ -3972,7 +3953,7 @@ level, <<mvc-exceptionhandlers, HandlerExceptionResolver>> mechanism.
|
||||
|
||||
[[mvc-ann-exceptionhandler-return-values]]
|
||||
==== Return Values
|
||||
[.small]#<<webflux.adoc#webflux-ann-exceptionhandler-return-values, WebFlux>>#
|
||||
[.small]#<<webflux.adoc#webflux-ann-exceptionhandler-return-values, See equivalent in the Reactive stack>>#
|
||||
|
||||
`@ExceptionHandler` methods support the following return values:
|
||||
|
||||
@@ -4042,7 +4023,7 @@ see <<mvc-ann-rest-exceptions>>
|
||||
|
||||
[[mvc-ann-controller-advice]]
|
||||
=== Controller Advice
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-controller-advice, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-controller-advice, See equivalent in the Reactive stack>>#
|
||||
|
||||
`@ExceptionHandler`, `@InitBinder`, and `@ModelAttribute` methods apply only to the
|
||||
`@Controller` class, or class hierarchy, in which they are declared. If, instead, they
|
||||
@@ -4106,7 +4087,7 @@ include::webmvc-functional.adoc[leveloffset=+1]
|
||||
|
||||
[[mvc-uri-building]]
|
||||
== URI Links
|
||||
[.small]#<<web-reactive.adoc#webflux-uri-building, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-uri-building, See equivalent in the Reactive stack>>#
|
||||
|
||||
This section describes various options available in the Spring Framework to work with URI's.
|
||||
|
||||
@@ -4385,7 +4366,6 @@ capital letters of the class and the method name (for example, the `getThing` me
|
||||
|
||||
[[mvc-ann-async]]
|
||||
== Asynchronous Requests
|
||||
[.small]#<<mvc-ann-async-vs-webflux, Compared to WebFlux>>#
|
||||
|
||||
Spring MVC has an extensive integration with Servlet asynchronous request
|
||||
<<mvc-ann-async-processing,processing>>:
|
||||
@@ -4398,11 +4378,10 @@ return value.
|
||||
* Controllers can use reactive clients and return
|
||||
<<mvc-ann-async-reactive-types, reactive types>> for response handling.
|
||||
|
||||
|
||||
For an overview of how this differs from Spring WebFlux, see the <<mvc-ann-async-vs-webflux>> section below.
|
||||
|
||||
[[mvc-ann-async-deferredresult]]
|
||||
=== `DeferredResult`
|
||||
[.small]#<<mvc-ann-async-vs-webflux, Compared to WebFlux>>#
|
||||
|
||||
Once the asynchronous request processing feature is <<mvc-ann-async-configuration, enabled>>
|
||||
in the Servlet container, controller methods can wrap any supported controller method
|
||||
@@ -4444,7 +4423,6 @@ example, in response to an external event (JMS message), a scheduled task, or ot
|
||||
|
||||
[[mvc-ann-async-callable]]
|
||||
=== `Callable`
|
||||
[.small]#<<mvc-ann-async-vs-webflux, Compared to WebFlux>>#
|
||||
|
||||
A controller can wrap any supported return value with `java.util.concurrent.Callable`,
|
||||
as the following example shows:
|
||||
@@ -4474,7 +4452,6 @@ The return value can then be obtained by running the given task through the
|
||||
|
||||
[[mvc-ann-async-processing]]
|
||||
=== Processing
|
||||
[.small]#<<mvc-ann-async-vs-webflux, Compared to WebFlux>>#
|
||||
|
||||
Here is a very concise overview of Servlet asynchronous request processing:
|
||||
|
||||
@@ -4552,7 +4529,7 @@ methods for timeout and completion callbacks.
|
||||
|
||||
|
||||
[[mvc-ann-async-vs-webflux]]
|
||||
==== Compared to WebFlux
|
||||
==== Async Spring MVC compared to WebFlux
|
||||
|
||||
The Servlet API was originally built for making a single pass through the Filter-Servlet
|
||||
chain. Asynchronous request processing lets applications exit the Filter-Servlet chain
|
||||
@@ -4579,11 +4556,13 @@ types in controller method arguments (for example, `@RequestBody`, `@RequestPart
|
||||
nor does it have any explicit support for asynchronous and reactive types as model attributes.
|
||||
Spring WebFlux does support all that.
|
||||
|
||||
Finally, from a configuration perspective the asynchronous request processing feature must be
|
||||
<<mvc-ann-async-configuration, enabled at the Servlet container level>>.
|
||||
|
||||
|
||||
[[mvc-ann-async-http-streaming]]
|
||||
=== HTTP Streaming
|
||||
[.small]#<<web-reactive.adoc#webflux-codecs-streaming, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-codecs-streaming, See equivalent in the Reactive stack>>#
|
||||
|
||||
You can use `DeferredResult` and `Callable` for a single asynchronous return value.
|
||||
What if you want to produce multiple asynchronous values and have those written to the
|
||||
@@ -4736,7 +4715,7 @@ customize the status and headers of the response.
|
||||
|
||||
[[mvc-ann-async-reactive-types]]
|
||||
=== Reactive Types
|
||||
[.small]#<<web-reactive.adoc#webflux-codecs-streaming, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-codecs-streaming, See equivalent in the Reactive stack>>#
|
||||
|
||||
Spring MVC supports use of reactive client libraries in a controller (also read
|
||||
<<web-reactive.adoc#webflux-reactive-libraries, Reactive Libraries>> in the WebFlux section).
|
||||
@@ -4798,7 +4777,7 @@ directly. For example:
|
||||
ContextSnapshot snapshot = ContextSnapshot.captureAll();
|
||||
|
||||
// On a different thread: restore ThreadLocal values
|
||||
try (ContextSnapshot.Scope scoped = snapshot.setThreadLocals()) {
|
||||
try (ContextSnapshot.Scope scope = snapshot.setThreadLocals()) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
@@ -4811,7 +4790,7 @@ Propagation library.
|
||||
|
||||
[[mvc-ann-async-disconnects]]
|
||||
=== Disconnects
|
||||
[.small]#<<web-reactive.adoc#webflux-codecs-streaming, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-codecs-streaming, See equivalent in the Reactive stack>>#
|
||||
|
||||
The Servlet API does not provide any notification when a remote client goes away.
|
||||
Therefore, while streaming to the response, whether through <<mvc-ann-async-sse, SseEmitter>>
|
||||
@@ -4828,7 +4807,6 @@ that have a built-in heartbeat mechanism.
|
||||
|
||||
[[mvc-ann-async-configuration]]
|
||||
=== Configuration
|
||||
[.small]#<<mvc-ann-async-vs-webflux, Compared to WebFlux>>#
|
||||
|
||||
The asynchronous request processing feature must be enabled at the Servlet container level.
|
||||
The MVC configuration also exposes several options for asynchronous requests.
|
||||
@@ -4878,7 +4856,7 @@ include::webmvc-cors.adoc[leveloffset=+1]
|
||||
|
||||
[[mvc-ann-rest-exceptions]]
|
||||
== Error Responses
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-rest-exceptions, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-rest-exceptions, See equivalent in the Reactive stack>>#
|
||||
|
||||
A common requirement for REST services is to include details in the body of error
|
||||
responses. The Spring Framework supports the "Problem Details for HTTP APIs"
|
||||
@@ -4902,7 +4880,7 @@ and any `ErrorResponseException`, and renders an error response with a body.
|
||||
|
||||
[[mvc-ann-rest-exceptions-render]]
|
||||
=== Render
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-rest-exceptions-render, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-rest-exceptions-render, See equivalent in the Reactive stack>>#
|
||||
|
||||
You can return `ProblemDetail` or `ErrorResponse` from any `@ExceptionHandler` or from
|
||||
any `@RequestMapping` method to render an RFC 7807 response. This is processed as follows:
|
||||
@@ -4925,7 +4903,7 @@ use a protected method to map any exception to a `ProblemDetail`.
|
||||
|
||||
[[mvc-ann-rest-exceptions-non-standard]]
|
||||
=== Non-Standard Fields
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-rest-exceptions-non-standard, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-rest-exceptions-non-standard, See equivalent in the Reactive stack>>#
|
||||
|
||||
You can extend an RFC 7807 response with non-standard fields in one of two ways.
|
||||
|
||||
@@ -4945,7 +4923,7 @@ from an existing `ProblemDetail`. This could be done centrally, e.g. from an
|
||||
|
||||
[[mvc-ann-rest-exceptions-i18n]]
|
||||
=== Internationalization
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-rest-exceptions-i18n, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-rest-exceptions-i18n, See equivalent in the Reactive stack>>#
|
||||
|
||||
It is a common requirement to internationalize error response details, and good practice
|
||||
to customize the problem details for Spring MVC exceptions. This is supported as follows:
|
||||
@@ -5055,7 +5033,7 @@ qualified exception class name.
|
||||
|
||||
[[mvc-ann-rest-exceptions-client]]
|
||||
=== Client Handling
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-rest-exceptions-client, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-rest-exceptions-client, See equivalent in the Reactive stack>>#
|
||||
|
||||
A client application can catch `WebClientResponseException`, when using the `WebClient`,
|
||||
or `RestClientResponseException` when using the `RestTemplate`, and use their
|
||||
@@ -5066,7 +5044,7 @@ or `RestClientResponseException` when using the `RestTemplate`, and use their
|
||||
|
||||
[[mvc-web-security]]
|
||||
== Web Security
|
||||
[.small]#<<web-reactive.adoc#webflux-web-security, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-web-security, See equivalent in the Reactive stack>>#
|
||||
|
||||
The https://spring.io/projects/spring-security[Spring Security] project provides support
|
||||
for protecting web applications from malicious exploits. See the Spring Security
|
||||
@@ -5084,7 +5062,7 @@ https://hdiv.org/[HDIV] is another web security framework that integrates with S
|
||||
|
||||
[[mvc-caching]]
|
||||
== HTTP Caching
|
||||
[.small]#<<web-reactive.adoc#webflux-caching, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-caching, See equivalent in the Reactive stack>>#
|
||||
|
||||
HTTP caching can significantly improve the performance of a web application. HTTP caching
|
||||
revolves around the `Cache-Control` response header and, subsequently, conditional request
|
||||
@@ -5100,7 +5078,7 @@ This section describes the HTTP caching-related options that are available in Sp
|
||||
|
||||
[[mvc-caching-cachecontrol]]
|
||||
=== `CacheControl`
|
||||
[.small]#<<web-reactive.adoc#webflux-caching-cachecontrol, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-caching-cachecontrol, See equivalent in the Reactive stack>>#
|
||||
|
||||
{api-spring-framework}/http/CacheControl.html[`CacheControl`] provides support for
|
||||
configuring settings related to the `Cache-Control` header and is accepted as an argument
|
||||
@@ -5156,13 +5134,14 @@ works as follows:
|
||||
|
||||
[[mvc-caching-etag-lastmodified]]
|
||||
=== Controllers
|
||||
[.small]#<<web-reactive.adoc#webflux-caching-etag-lastmodified, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-caching-etag-lastmodified, See equivalent in the Reactive stack>>#
|
||||
|
||||
Controllers can add explicit support for HTTP caching. We recommended doing so, since the
|
||||
`lastModified` or `ETag` value for a resource needs to be calculated before it can be compared
|
||||
against conditional request headers. A controller can add an `ETag` header and `Cache-Control`
|
||||
settings to a `ResponseEntity`, as the following example shows:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -5195,6 +5174,7 @@ settings to a `ResponseEntity`, as the following example shows:
|
||||
.body(book)
|
||||
}
|
||||
----
|
||||
--
|
||||
|
||||
The preceding example sends a 304 (NOT_MODIFIED) response with an empty body if the comparison
|
||||
to the conditional request headers indicates that the content has not changed. Otherwise, the
|
||||
@@ -5203,6 +5183,7 @@ to the conditional request headers indicates that the content has not changed. O
|
||||
You can also make the check against conditional request headers in the controller,
|
||||
as the following example shows:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@@ -5242,6 +5223,7 @@ as the following example shows:
|
||||
<1> Application-specific calculation.
|
||||
<2> The response has been set to 304 (NOT_MODIFIED) -- no further processing.
|
||||
<3> Continue with the request processing.
|
||||
--
|
||||
|
||||
|
||||
There are three variants for checking conditional requests against `eTag` values, `lastModified`
|
||||
@@ -5253,7 +5235,7 @@ to 412 (PRECONDITION_FAILED), to prevent concurrent modification.
|
||||
|
||||
[[mvc-caching-static-resources]]
|
||||
=== Static Resources
|
||||
[.small]#<<web-reactive.adoc#webflux-caching-static-resources, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-caching-static-resources, See equivalent in the Reactive stack>>#
|
||||
|
||||
You should serve static resources with a `Cache-Control` and conditional response headers
|
||||
for optimal performance. See the section on configuring <<mvc-config-static-resources>>.
|
||||
@@ -5273,7 +5255,7 @@ include::webmvc-view.adoc[leveloffset=+1]
|
||||
|
||||
[[mvc-config]]
|
||||
== MVC Config
|
||||
[.small]#<<web-reactive.adoc#webflux-config, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-config, See equivalent in the Reactive stack>>#
|
||||
|
||||
The MVC Java configuration and the MVC XML namespace provide default configuration
|
||||
suitable for most applications and a configuration API to customize it.
|
||||
@@ -5289,7 +5271,7 @@ and <<mvc-servlet-config>>.
|
||||
|
||||
[[mvc-config-enable]]
|
||||
=== Enable MVC Configuration
|
||||
[.small]#<<web-reactive.adoc#webflux-config-enable, WebFlux>>#
|
||||
[.small]#<<web-reactive.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:
|
||||
@@ -5338,7 +5320,7 @@ available on the classpath (for example, payload converters for JSON, XML, and o
|
||||
|
||||
[[mvc-config-customize]]
|
||||
=== MVC Config API
|
||||
[.small]#<<web-reactive.adoc#webflux-config-customize, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-config-customize, See equivalent in the Reactive stack>>#
|
||||
|
||||
In Java configuration, you can implement the `WebMvcConfigurer` interface, as the
|
||||
following example shows:
|
||||
@@ -5374,7 +5356,7 @@ sub-elements are available.
|
||||
|
||||
[[mvc-config-conversion]]
|
||||
=== Type Conversion
|
||||
[.small]#<<web-reactive.adoc#webflux-config-conversion, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-config-conversion, See equivalent in the Reactive stack>>#
|
||||
|
||||
By default, formatters for various number and date types are installed, along with support
|
||||
for customization via `@NumberFormat` and `@DateTimeFormat` on fields.
|
||||
@@ -5489,7 +5471,7 @@ FormatterRegistrar implementations.
|
||||
|
||||
[[mvc-config-validation]]
|
||||
=== Validation
|
||||
[.small]#<<web-reactive.adoc#webflux-config-validation, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-config-validation, See equivalent in the Reactive stack>>#
|
||||
|
||||
By default, if <<core.adoc#validation-beanvalidation-overview, Bean Validation>> is present
|
||||
on the classpath (for example, Hibernate Validator), the `LocalValidatorFactoryBean` is
|
||||
@@ -5639,7 +5621,7 @@ unwanted characters in URL paths.
|
||||
|
||||
[[mvc-config-content-negotiation]]
|
||||
=== Content Types
|
||||
[.small]#<<web-reactive.adoc#webflux-config-content-negotiation, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-config-content-negotiation, See equivalent in the Reactive stack>>#
|
||||
|
||||
You can configure how Spring MVC determines the requested media types from the request
|
||||
(for example, `Accept` header, URL path extension, query parameter, and others).
|
||||
@@ -5703,7 +5685,7 @@ The following example shows how to achieve the same configuration in XML:
|
||||
|
||||
[[mvc-config-message-converters]]
|
||||
=== Message Converters
|
||||
[.small]#<<web-reactive.adoc#webflux-config-message-codecs, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-config-message-codecs, See equivalent in the Reactive stack>>#
|
||||
|
||||
You can customize `HttpMessageConverter` in Java configuration by overriding
|
||||
{api-spring-framework}/web/servlet/config/annotation/WebMvcConfigurer.html#configureMessageConverters-java.util.List-[`configureMessageConverters()`]
|
||||
@@ -5856,7 +5838,7 @@ splitting URL handling across an annotated controller and a view controller.
|
||||
|
||||
[[mvc-config-view-resolvers]]
|
||||
=== View Resolvers
|
||||
[.small]#<<web-reactive.adoc#webflux-config-view-resolvers, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-config-view-resolvers, See equivalent in the Reactive stack>>#
|
||||
|
||||
The MVC configuration simplifies the registration of view resolvers.
|
||||
|
||||
@@ -5906,7 +5888,7 @@ The following example shows how to achieve the same configuration in XML:
|
||||
</mvc:view-resolvers>
|
||||
----
|
||||
|
||||
Note, however, that FreeMarker, Tiles, Groovy Markup, and script templates also require
|
||||
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:
|
||||
@@ -5974,7 +5956,7 @@ as the following example shows:
|
||||
|
||||
[[mvc-config-static-resources]]
|
||||
=== Static Resources
|
||||
[.small]#<<web-reactive.adoc#webflux-config-static-resources, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-config-static-resources, See equivalent in the Reactive stack>>#
|
||||
|
||||
This option provides a convenient way to serve static resources from a list of
|
||||
{api-spring-framework}/core/io/Resource.html[`Resource`]-based locations.
|
||||
@@ -6214,7 +6196,7 @@ The following example shows how to achieve the same configuration in XML:
|
||||
|
||||
[[mvc-config-path-matching]]
|
||||
=== Path Matching
|
||||
[.small]#<<web-reactive.adoc#webflux-config-path-matching, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-config-path-matching, See equivalent in the Reactive stack>>#
|
||||
|
||||
You can customize options related to path matching and treatment of the URL.
|
||||
For details on the individual options, see the
|
||||
@@ -6274,7 +6256,7 @@ The following example shows how to customize path matching in XML configuration:
|
||||
|
||||
[[mvc-config-advanced-java]]
|
||||
=== Advanced Java Config
|
||||
[.small]#<<web-reactive.adoc#webflux-config-advanced-java, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-config-advanced-java, See equivalent in the Reactive stack>>#
|
||||
|
||||
`@EnableWebMvc` imports `DelegatingWebMvcConfiguration`, which:
|
||||
|
||||
@@ -6349,7 +6331,7 @@ by letting it be detected through a `<component-scan/>` declaration.
|
||||
|
||||
[[mvc-http2]]
|
||||
== HTTP/2
|
||||
[.small]#<<web-reactive.adoc#webflux-http2, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-http2, See equivalent in the Reactive stack>>#
|
||||
|
||||
Servlet 4 containers are required to support HTTP/2, and Spring Framework 5 is compatible
|
||||
with Servlet API 4. From a programming model perspective, there is nothing specific that
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[[{chapter}.websocket-intro]]
|
||||
[id={chapter}.websocket-intro]
|
||||
= Introduction to WebSocket
|
||||
|
||||
The WebSocket protocol, https://tools.ietf.org/html/rfc6455[RFC 6455], provides a standardized
|
||||
@@ -54,7 +54,7 @@ instructions of the cloud provider related to WebSocket support.
|
||||
|
||||
|
||||
|
||||
[[{chapter}.websocket-intro-architecture]]
|
||||
[id={chapter}.websocket-intro-architecture]
|
||||
== HTTP Versus WebSocket
|
||||
|
||||
Even though WebSocket is designed to be HTTP-compatible and starts with an HTTP request,
|
||||
@@ -80,11 +80,11 @@ In the absence of that, they need to come up with their own conventions.
|
||||
|
||||
|
||||
|
||||
[[{chapter}.websocket-intro-when-to-use]]
|
||||
[id={chapter}.websocket-intro-when-to-use]
|
||||
== When to Use WebSockets
|
||||
|
||||
WebSockets can make a web page be dynamic and interactive. However, in many cases,
|
||||
a combination of Ajax and HTTP streaming or long polling can provide a simple and
|
||||
a combination of AJAX and HTTP streaming or long polling can provide a simple and
|
||||
effective solution.
|
||||
|
||||
For example, news, mail, and social feeds need to update dynamically, but it may be
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[[websocket]]
|
||||
= WebSockets
|
||||
[.small]#<<web-reactive.adoc#webflux-websocket, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-websocket, See equivalent in the Reactive stack>>#
|
||||
|
||||
This part of the reference documentation covers support for Servlet stack, WebSocket
|
||||
messaging that includes raw WebSocket interactions, WebSocket emulation through SockJS, and
|
||||
@@ -13,7 +13,7 @@ include::websocket-intro.adoc[leveloffset=+1]
|
||||
|
||||
[[websocket-server]]
|
||||
== WebSocket API
|
||||
[.small]#<<web-reactive.adoc#webflux-websocket-server, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-websocket-server, See equivalent in the Reactive stack>>#
|
||||
|
||||
The Spring Framework provides a WebSocket API that you can use to write client- and
|
||||
server-side applications that handle WebSocket messages.
|
||||
@@ -22,7 +22,7 @@ server-side applications that handle WebSocket messages.
|
||||
|
||||
[[websocket-server-handler]]
|
||||
=== `WebSocketHandler`
|
||||
[.small]#<<web-reactive.adoc#webflux-websocket-server-handler, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-websocket-server-handler, See equivalent in the Reactive stack>>#
|
||||
|
||||
Creating a WebSocket server is as simple as implementing `WebSocketHandler` or, more
|
||||
likely, extending either `TextWebSocketHandler` or `BinaryWebSocketHandler`. The following
|
||||
@@ -108,7 +108,7 @@ sending. One option is to wrap the `WebSocketSession` with
|
||||
|
||||
[[websocket-server-handshake]]
|
||||
=== WebSocket Handshake
|
||||
[.small]#<<web-reactive.adoc#webflux-websocket-server-handshake, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-websocket-server-handshake, See equivalent in the Reactive stack>>#
|
||||
|
||||
The easiest way to customize the initial HTTP WebSocket handshake request is through
|
||||
a `HandshakeInterceptor`, which exposes methods for "`before`" and "`after`" the handshake.
|
||||
@@ -184,7 +184,7 @@ HTTP requests. It is also easy to integrate into other HTTP processing scenarios
|
||||
by invoking `WebSocketHttpRequestHandler`. This is convenient and easy to
|
||||
understand. However, special considerations apply with regards to JSR-356 runtimes.
|
||||
|
||||
The Java WebSocket API (JSR-356) provides two deployment mechanisms. The first
|
||||
The Jakarta WebSocket API (JSR-356) provides two deployment mechanisms. The first
|
||||
involves a Servlet container classpath scan (a Servlet 3 feature) at startup.
|
||||
The other is a registration API to use at Servlet container initialization.
|
||||
Neither of these mechanism makes it possible to use a single "`front controller`"
|
||||
@@ -193,17 +193,9 @@ requests -- such as Spring MVC's `DispatcherServlet`.
|
||||
|
||||
This is a significant limitation of JSR-356 that Spring's WebSocket support addresses with
|
||||
server-specific `RequestUpgradeStrategy` implementations even when running in a JSR-356 runtime.
|
||||
Such strategies currently exist for Tomcat, Jetty, GlassFish, WebLogic, WebSphere, and
|
||||
Undertow (and WildFly).
|
||||
|
||||
|
||||
|
||||
NOTE: A request to overcome the preceding limitation in the Java WebSocket API has been
|
||||
created and can be followed at
|
||||
https://github.com/eclipse-ee4j/websocket-api/issues/211[eclipse-ee4j/websocket-api#211].
|
||||
Tomcat, Undertow, and WebSphere provide their own API alternatives that
|
||||
make it possible to do this, and it is also possible with Jetty. We are hopeful
|
||||
that more servers will do the same.
|
||||
Such strategies currently exist for Tomcat, Jetty, GlassFish, WebLogic, WebSphere, and Undertow
|
||||
(and WildFly). As of Jakarta WebSocket 2.1, a standard request upgrade strategy is available
|
||||
which Spring chooses on Jakarta EE 10 based web containers such as Tomcat 10.1 and Jetty 12.
|
||||
|
||||
A secondary consideration is that Servlet containers with JSR-356 support are expected
|
||||
to perform a `ServletContainerInitializer` (SCI) scan that can slow down application
|
||||
@@ -250,7 +242,7 @@ Java initialization API. The following example shows how to do so:
|
||||
|
||||
[[websocket-server-runtime-configuration]]
|
||||
=== Server Configuration
|
||||
[.small]#<<web-reactive.adoc#webflux-websocket-server-config, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-websocket-server-config, See equivalent in the Reactive stack>>#
|
||||
|
||||
Each underlying WebSocket engine exposes configuration properties that control
|
||||
runtime characteristics, such as the size of message buffer sizes, idle timeout,
|
||||
@@ -373,7 +365,7 @@ The following example shows the XML configuration equivalent of the preceding ex
|
||||
|
||||
[[websocket-server-allowed-origins]]
|
||||
=== Allowed Origins
|
||||
[.small]#<<web-reactive.adoc#webflux-websocket-server-cors, WebFlux>>#
|
||||
[.small]#<<web-reactive.adoc#webflux-websocket-server-cors, See equivalent in the Reactive stack>>#
|
||||
|
||||
As of Spring Framework 4.1.5, the default behavior for WebSocket and SockJS is to accept
|
||||
only same-origin requests. It is also possible to allow all or a specified list of origins.
|
||||
@@ -970,7 +962,7 @@ endpoints, over WebSocket with <<websocket-fallback>>, as the following example
|
||||
|
||||
@Override
|
||||
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
||||
registry.addEndpoint("/portfolio").withSockJS(); // <1>
|
||||
registry.addEndpoint("/portfolio").withSockJS(); // <1>
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-3
@@ -39,9 +39,8 @@ class SampleReflectionRuntimeHintsTests {
|
||||
void shouldRegisterReflectionHints() {
|
||||
RuntimeHints runtimeHints = new RuntimeHints();
|
||||
// Call a RuntimeHintsRegistrar that contributes hints like:
|
||||
runtimeHints.reflection().registerType(SpringVersion.class, typeHint -> {
|
||||
typeHint.withMethod("getVersion", List.of(), ExecutableMode.INVOKE);
|
||||
});
|
||||
runtimeHints.reflection().registerType(SpringVersion.class, typeHint ->
|
||||
typeHint.withMethod("getVersion", List.of(), ExecutableMode.INVOKE));
|
||||
|
||||
// Invoke the relevant piece of code we want to test within a recording lambda
|
||||
RuntimeHintsInvocations invocations = RuntimeHintsRecorder.record(() -> {
|
||||
|
||||
+3
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -30,6 +30,8 @@ public class AotProcessingSample {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
||||
context.register(MyApplication.class);
|
||||
context.refreshForAotProcessing(hints);
|
||||
// ...
|
||||
context.close();
|
||||
// end::aotcontext[]
|
||||
}
|
||||
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.integration.observability.config.conventions;
|
||||
|
||||
import io.micrometer.common.KeyValue;
|
||||
import io.micrometer.common.KeyValues;
|
||||
|
||||
import org.springframework.http.server.observation.ServerHttpObservationDocumentation;
|
||||
import org.springframework.http.server.observation.ServerRequestObservationContext;
|
||||
import org.springframework.http.server.observation.ServerRequestObservationConvention;
|
||||
|
||||
public class CustomServerRequestObservationConvention implements ServerRequestObservationConvention {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
// will be used as the metric name
|
||||
return "http.server.requests";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContextualName(ServerRequestObservationContext context) {
|
||||
// will be used for the trace name
|
||||
return "http " + context.getCarrier().getMethod().toLowerCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyValues getLowCardinalityKeyValues(ServerRequestObservationContext context) {
|
||||
return KeyValues.of(method(context), status(context), exception(context));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public KeyValues getHighCardinalityKeyValues(ServerRequestObservationContext context) {
|
||||
return KeyValues.of(httpUrl(context));
|
||||
}
|
||||
|
||||
private KeyValue method(ServerRequestObservationContext context) {
|
||||
// You should reuse as much as possible the corresponding ObservationDocumentation for key names
|
||||
return KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.METHOD, context.getCarrier().getMethod());
|
||||
}
|
||||
|
||||
// @fold:on // status(), exception(), httpUrl()...
|
||||
private KeyValue status(ServerRequestObservationContext context) {
|
||||
return KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.STATUS, String.valueOf(context.getResponse().getStatus()));
|
||||
}
|
||||
|
||||
private KeyValue exception(ServerRequestObservationContext context) {
|
||||
String exception = (context.getError() != null) ? context.getError().getClass().getSimpleName() : KeyValue.NONE_VALUE;
|
||||
return KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.EXCEPTION, exception);
|
||||
}
|
||||
|
||||
private KeyValue httpUrl(ServerRequestObservationContext context) {
|
||||
return KeyValue.of(ServerHttpObservationDocumentation.HighCardinalityKeyNames.HTTP_URL, context.getCarrier().getRequestURI());
|
||||
}
|
||||
// @fold:off
|
||||
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.integration.observability.config.conventions;
|
||||
|
||||
import io.micrometer.common.KeyValue;
|
||||
import io.micrometer.common.KeyValues;
|
||||
|
||||
import org.springframework.http.server.observation.DefaultServerRequestObservationConvention;
|
||||
import org.springframework.http.server.observation.ServerRequestObservationContext;
|
||||
|
||||
public class ExtendedServerRequestObservationConvention extends DefaultServerRequestObservationConvention {
|
||||
|
||||
@Override
|
||||
public KeyValues getLowCardinalityKeyValues(ServerRequestObservationContext context) {
|
||||
// here, we just want to have an additional KeyValue to the observation, keeping the default values
|
||||
return super.getLowCardinalityKeyValues(context).and(custom(context));
|
||||
}
|
||||
|
||||
private KeyValue custom(ServerRequestObservationContext context) {
|
||||
return KeyValue.of("custom.method", context.getCarrier().getMethod());
|
||||
}
|
||||
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.integration.observability.config.conventions;
|
||||
|
||||
|
||||
import io.micrometer.common.KeyValue;
|
||||
import io.micrometer.observation.Observation;
|
||||
import io.micrometer.observation.ObservationFilter;
|
||||
|
||||
import org.springframework.http.server.observation.ServerRequestObservationContext;
|
||||
|
||||
public class ServerRequestObservationFilter implements ObservationFilter {
|
||||
|
||||
@Override
|
||||
public Observation.Context map(Observation.Context context) {
|
||||
if (context instanceof ServerRequestObservationContext serverContext) {
|
||||
context.setName("custom.observation.name");
|
||||
context.addLowCardinalityKeyValue(KeyValue.of("project", "spring"));
|
||||
String customAttribute = (String) serverContext.getCarrier().getAttribute("customAttribute");
|
||||
context.addLowCardinalityKeyValue(KeyValue.of("custom.attribute", customAttribute));
|
||||
}
|
||||
return context;
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.integration.observability.httpserver.reactive;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.filter.reactive.ServerHttpObservationFilter;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
@Controller
|
||||
public class UserController {
|
||||
|
||||
@ExceptionHandler(MissingUserException.class)
|
||||
ResponseEntity<Void> handleMissingUser(ServerWebExchange exchange, MissingUserException exception) {
|
||||
// We want to record this exception with the observation
|
||||
ServerHttpObservationFilter.findObservationContext(exchange)
|
||||
.ifPresent(context -> context.setError(exception));
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
// @fold:on
|
||||
@SuppressWarnings("serial")
|
||||
static class MissingUserException extends RuntimeException {
|
||||
}
|
||||
// @fold:off
|
||||
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.integration.observability.httpserver.servlet;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.filter.ServerHttpObservationFilter;
|
||||
|
||||
@Controller
|
||||
public class UserController {
|
||||
|
||||
@ExceptionHandler(MissingUserException.class)
|
||||
ResponseEntity<Void> handleMissingUser(HttpServletRequest request, MissingUserException exception) {
|
||||
// We want to record this exception with the observation
|
||||
ServerHttpObservationFilter.findObservationContext(request)
|
||||
.ifPresent(context -> context.setError(exception));
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
// @fold:on
|
||||
@SuppressWarnings("serial")
|
||||
static class MissingUserException extends RuntimeException {
|
||||
}
|
||||
// @fold:off
|
||||
|
||||
}
|
||||
@@ -7,29 +7,29 @@ javaPlatform {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(platform("com.fasterxml.jackson:jackson-bom:2.14.1"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.10.0"))
|
||||
api(platform("io.netty:netty-bom:4.1.85.Final"))
|
||||
api(platform("com.fasterxml.jackson:jackson-bom:2.14.2"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.10.4"))
|
||||
api(platform("io.netty:netty-bom:4.1.89.Final"))
|
||||
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
|
||||
api(platform("io.projectreactor:reactor-bom:2022.0.0"))
|
||||
api(platform("io.projectreactor:reactor-bom:2022.0.4"))
|
||||
api(platform("io.rsocket:rsocket-bom:1.1.3"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.5"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.8"))
|
||||
api(platform("org.apache.logging.log4j:log4j-bom:2.19.0"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:11.0.12"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:11.0.13"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.4"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.4.0"))
|
||||
api(platform("org.junit:junit-bom:5.9.1"))
|
||||
api(platform("org.mockito:mockito-bom:4.8.1"))
|
||||
api(platform("org.junit:junit-bom:5.9.2"))
|
||||
api(platform("org.mockito:mockito-bom:5.1.1"))
|
||||
|
||||
constraints {
|
||||
api("com.fasterxml.woodstox:woodstox-core:6.4.0")
|
||||
api("com.fasterxml:aalto-xml:1.3.1")
|
||||
api("com.github.ben-manes.caffeine:caffeine:3.1.1")
|
||||
api("com.fasterxml.woodstox:woodstox-core:6.5.0")
|
||||
api("com.github.ben-manes.caffeine:caffeine:3.1.2")
|
||||
api("com.github.librepdf:openpdf:1.3.30")
|
||||
api("com.google.code.findbugs:findbugs:3.0.1")
|
||||
api("com.google.code.findbugs:jsr305:3.0.2")
|
||||
api("com.google.code.gson:gson:2.9.1")
|
||||
api("com.google.protobuf:protobuf-java-util:3.21.5")
|
||||
api("com.google.code.gson:gson:2.10")
|
||||
api("com.google.protobuf:protobuf-java-util:3.21.12")
|
||||
api("com.googlecode.protobuf-java-format:protobuf-java-format:1.4")
|
||||
api("com.h2database:h2:2.1.214")
|
||||
api("com.jayway.jsonpath:json-path:2.7.0")
|
||||
@@ -45,7 +45,7 @@ dependencies {
|
||||
api("com.thoughtworks.xstream:xstream:1.4.19")
|
||||
api("commons-io:commons-io:2.11.0")
|
||||
api("de.bechte.junit:junit-hierarchicalcontextrunner:4.12.1")
|
||||
api("info.picocli:picocli:4.6.3")
|
||||
api("info.picocli:picocli:4.7.0")
|
||||
api("io.micrometer:context-propagation:1.0.0")
|
||||
api("io.mockk:mockk:1.12.1")
|
||||
api("io.projectreactor.netty:reactor-netty5-http:2.0.0-M3")
|
||||
@@ -55,9 +55,9 @@ dependencies {
|
||||
api("io.r2dbc:r2dbc-spi:1.0.0.RELEASE")
|
||||
api("io.reactivex.rxjava3:rxjava:3.1.5")
|
||||
api("io.smallrye.reactive:mutiny:1.8.0")
|
||||
api("io.undertow:undertow-core:2.3.0.Final")
|
||||
api("io.undertow:undertow-servlet:2.3.0.Final")
|
||||
api("io.undertow:undertow-websockets-jsr:2.3.0.Final")
|
||||
api("io.undertow:undertow-core:2.3.3.Final")
|
||||
api("io.undertow:undertow-servlet:2.3.3.Final")
|
||||
api("io.undertow:undertow-websockets-jsr:2.3.3.Final")
|
||||
api("io.vavr:vavr:0.10.4")
|
||||
api("jakarta.activation:jakarta.activation-api:2.0.1")
|
||||
api("jakarta.annotation:jakarta.annotation-api:2.0.0")
|
||||
@@ -87,7 +87,7 @@ dependencies {
|
||||
api("jaxen:jaxen:1.2.0")
|
||||
api("junit:junit:4.13.2")
|
||||
api("net.sf.jopt-simple:jopt-simple:5.0.4")
|
||||
api("net.sourceforge.htmlunit:htmlunit:2.66.0")
|
||||
api("net.sourceforge.htmlunit:htmlunit:2.70.0")
|
||||
api("org.apache-extras.beanshell:bsh:2.0b6")
|
||||
api("org.apache.activemq:activemq-broker:5.16.2")
|
||||
api("org.apache.activemq:activemq-kahadb-store:5.16.2")
|
||||
@@ -96,17 +96,17 @@ dependencies {
|
||||
api("org.apache.derby:derby:10.16.1.1")
|
||||
api("org.apache.derby:derbyclient:10.16.1.1")
|
||||
api("org.apache.derby:derbytools:10.16.1.1")
|
||||
api("org.apache.httpcomponents.client5:httpclient5:5.1.3")
|
||||
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.1.3")
|
||||
api("org.apache.poi:poi-ooxml:5.2.2")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.1")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.1")
|
||||
api("org.apache.tomcat:tomcat-util:10.1.1")
|
||||
api("org.apache.tomcat:tomcat-websocket:10.1.1")
|
||||
api("org.apache.httpcomponents.client5:httpclient5:5.2.1")
|
||||
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.2.1")
|
||||
api("org.apache.poi:poi-ooxml:5.2.3")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.5")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.5")
|
||||
api("org.apache.tomcat:tomcat-util:10.1.5")
|
||||
api("org.apache.tomcat:tomcat-websocket:10.1.5")
|
||||
api("org.aspectj:aspectjrt:1.9.9.1")
|
||||
api("org.aspectj:aspectjtools:1.9.9.1")
|
||||
api("org.aspectj:aspectjweaver:1.9.9.1")
|
||||
api("org.assertj:assertj-core:3.23.1")
|
||||
api("org.assertj:assertj-core:3.24.2")
|
||||
api("org.awaitility:awaitility:3.1.6")
|
||||
api("org.bouncycastle:bcpkix-jdk18on:1.71")
|
||||
api("org.codehaus.jettison:jettison:1.3.8")
|
||||
@@ -116,7 +116,7 @@ dependencies {
|
||||
api("org.eclipse:yasson:2.0.4")
|
||||
api("org.ehcache:ehcache:3.4.0")
|
||||
api("org.ehcache:jcache:1.0.1")
|
||||
api("org.freemarker:freemarker:2.3.31")
|
||||
api("org.freemarker:freemarker:2.3.32")
|
||||
// Substitute for "javax.management:jmxremote_optional:1.0.1_04" which
|
||||
// is not available on Maven Central
|
||||
api("org.glassfish.external:opendmk_jmxremote_optional_jar:1.0-b01-ea")
|
||||
@@ -124,25 +124,25 @@ dependencies {
|
||||
api("org.glassfish:jakarta.el:4.0.2")
|
||||
api("org.graalvm.sdk:graal-sdk:22.3.0")
|
||||
api("org.hamcrest:hamcrest:2.2")
|
||||
api("org.hibernate:hibernate-core-jakarta:5.6.12.Final")
|
||||
api("org.hibernate:hibernate-core-jakarta:5.6.15.Final")
|
||||
api("org.hibernate:hibernate-validator:7.0.5.Final")
|
||||
api("org.hsqldb:hsqldb:2.7.0")
|
||||
api("org.hsqldb:hsqldb:2.7.1")
|
||||
api("org.javamoney:moneta:1.4.2")
|
||||
api("org.jruby:jruby:9.3.8.0")
|
||||
api("org.jruby:jruby:9.4.0.0")
|
||||
api("org.junit.support:testng-engine:1.0.4")
|
||||
api("org.mozilla:rhino:1.7.11")
|
||||
api("org.ogce:xpp3:1.1.6")
|
||||
api("org.python:jython-standalone:2.7.1")
|
||||
api("org.quartz-scheduler:quartz:2.3.2")
|
||||
api("org.seleniumhq.selenium:htmlunit-driver:2.66.0")
|
||||
api("org.seleniumhq.selenium:htmlunit-driver:2.70.0")
|
||||
api("org.seleniumhq.selenium:selenium-java:3.141.59")
|
||||
api("org.skyscreamer:jsonassert:1.5.0")
|
||||
api("org.slf4j:slf4j-api:2.0.3")
|
||||
api("org.testng:testng:7.6.1")
|
||||
api("org.slf4j:slf4j-api:2.0.6")
|
||||
api("org.testng:testng:7.7.1")
|
||||
api("org.webjars:underscorejs:1.8.3")
|
||||
api("org.webjars:webjars-locator-core:0.48")
|
||||
api("org.xmlunit:xmlunit-assertj:2.9.0")
|
||||
api("org.xmlunit:xmlunit-matchers:2.9.0")
|
||||
api("org.yaml:snakeyaml:1.30")
|
||||
api("org.webjars:webjars-locator-core:0.52")
|
||||
api("org.xmlunit:xmlunit-assertj:2.9.1")
|
||||
api("org.xmlunit:xmlunit-matchers:2.9.1")
|
||||
api("org.yaml:snakeyaml:1.33")
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
version=6.0.1-SNAPSHOT
|
||||
version=6.0.6
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
|
||||
@@ -8,8 +8,8 @@ apply plugin: 'me.champeau.jmh'
|
||||
apply from: "$rootDir/gradle/publications.gradle"
|
||||
|
||||
dependencies {
|
||||
jmh 'org.openjdk.jmh:jmh-core:1.32'
|
||||
jmh 'org.openjdk.jmh:jmh-generator-annprocess:1.32'
|
||||
jmh 'org.openjdk.jmh:jmh-core:1.36'
|
||||
jmh 'org.openjdk.jmh:jmh-generator-annprocess:1.36'
|
||||
jmh 'net.sf.jopt-simple:jopt-simple'
|
||||
}
|
||||
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
* One can choose the toolchain to use for compiling the MAIN sources and/or compiling
|
||||
* and running the TEST sources. These options apply to Java, Kotlin and Groovy sources
|
||||
* when available.
|
||||
* {@code "./gradlew check -PmainToolchain=17 -PtestToolchain=18"} will use:
|
||||
* {@code "./gradlew check -PmainToolchain=17 -PtestToolchain=19"} will use:
|
||||
* <ul>
|
||||
* <li>a JDK17 toolchain for compiling the main SourceSet
|
||||
* <li>a JDK18 toolchain for compiling and running the test SourceSet
|
||||
* <li>a JDK19 toolchain for compiling and running the test SourceSet
|
||||
* </ul>
|
||||
*
|
||||
* By default, the build will fall back to using the current JDK and 17 language level for all sourceSets.
|
||||
@@ -23,9 +23,9 @@
|
||||
* {@code
|
||||
* $ echo JDK17
|
||||
* /opt/openjdk/java17
|
||||
* $ echo JDK18
|
||||
* $ echo JDK19
|
||||
* /opt/openjdk/java18
|
||||
* $ ./gradlew -Porg.gradle.java.installations.fromEnv=JDK17,JDK18 check
|
||||
* $ ./gradlew -Porg.gradle.java.installations.fromEnv=JDK17,JDK19 check
|
||||
* }
|
||||
*
|
||||
* @author Brian Clozel
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+2
-1
@@ -1,5 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
|
||||
networkTimeout=10000
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@@ -80,10 +80,10 @@ do
|
||||
esac
|
||||
done
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
@@ -143,12 +143,16 @@ fi
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
@@ -205,6 +209,12 @@ set -- \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
|
||||
Vendored
+9
-6
@@ -14,7 +14,7 @@
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@@ -25,7 +25,8 @@
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
@@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -34,7 +34,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for scoped proxy use in conjunction with aop: namespace.
|
||||
* Deemed an integration test because .web mocks and application contexts are required.
|
||||
* Deemed an integration test because web mocks and application contexts are required.
|
||||
*
|
||||
* @author Rob Harrop
|
||||
* @author Juergen Hoeller
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@ class EnableCachingIntegrationTests {
|
||||
ctx.register(Config.class, AspectJCacheConfig.class);
|
||||
// this test is a bit fragile, but gets the job done, proving that an
|
||||
// attempt was made to look up the AJ aspect. It's due to classpath issues
|
||||
// in .integration-tests that it's not found.
|
||||
// in integration-tests that it's not found.
|
||||
assertThatException().isThrownBy(ctx::refresh)
|
||||
.withMessageContaining("AspectJCachingConfiguration");
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -151,7 +151,7 @@ class ScheduledAndTransactionalAnnotationIntegrationTests {
|
||||
|
||||
@Bean
|
||||
PersistenceExceptionTranslator peTranslator() {
|
||||
return mock(PersistenceExceptionTranslator.class);
|
||||
return mock();
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
+1
-1
@@ -97,7 +97,7 @@ class EnableTransactionManagementIntegrationTests {
|
||||
ctx.register(Config.class, AspectJTxConfig.class);
|
||||
// this test is a bit fragile, but gets the job done, proving that an
|
||||
// attempt was made to look up the AJ aspect. It's due to classpath issues
|
||||
// in .integration-tests that it's not found.
|
||||
// in integration-tests that it's not found.
|
||||
assertThatException()
|
||||
.isThrownBy(ctx::refresh)
|
||||
.withMessageContaining("AspectJJtaTransactionManagementConfiguration");
|
||||
|
||||
+2
-2
@@ -7,8 +7,8 @@ pluginManagement {
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "com.gradle.enterprise" version "3.11.1"
|
||||
id "io.spring.ge.conventions" version "0.0.11"
|
||||
id "com.gradle.enterprise" version "3.12.3"
|
||||
id "io.spring.ge.conventions" version "0.0.13"
|
||||
}
|
||||
|
||||
include "spring-aop"
|
||||
|
||||
+13
-15
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -268,18 +268,16 @@ public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedence
|
||||
this.argumentNames[i] + "' that is not a valid Java identifier");
|
||||
}
|
||||
}
|
||||
if (this.argumentNames != null) {
|
||||
if (this.aspectJAdviceMethod.getParameterCount() == this.argumentNames.length + 1) {
|
||||
// May need to add implicit join point arg name...
|
||||
Class<?> firstArgType = this.aspectJAdviceMethod.getParameterTypes()[0];
|
||||
if (firstArgType == JoinPoint.class ||
|
||||
firstArgType == ProceedingJoinPoint.class ||
|
||||
firstArgType == JoinPoint.StaticPart.class) {
|
||||
String[] oldNames = this.argumentNames;
|
||||
this.argumentNames = new String[oldNames.length + 1];
|
||||
this.argumentNames[0] = "THIS_JOIN_POINT";
|
||||
System.arraycopy(oldNames, 0, this.argumentNames, 1, oldNames.length);
|
||||
}
|
||||
if (this.aspectJAdviceMethod.getParameterCount() == this.argumentNames.length + 1) {
|
||||
// May need to add implicit join point arg name...
|
||||
Class<?> firstArgType = this.aspectJAdviceMethod.getParameterTypes()[0];
|
||||
if (firstArgType == JoinPoint.class ||
|
||||
firstArgType == ProceedingJoinPoint.class ||
|
||||
firstArgType == JoinPoint.StaticPart.class) {
|
||||
String[] oldNames = this.argumentNames;
|
||||
this.argumentNames = new String[oldNames.length + 1];
|
||||
this.argumentNames[0] = "THIS_JOIN_POINT";
|
||||
System.arraycopy(oldNames, 0, this.argumentNames, 1, oldNames.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -655,10 +653,10 @@ public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedence
|
||||
@Nullable
|
||||
protected JoinPointMatch getJoinPointMatch() {
|
||||
MethodInvocation mi = ExposeInvocationInterceptor.currentInvocation();
|
||||
if (!(mi instanceof ProxyMethodInvocation)) {
|
||||
if (!(mi instanceof ProxyMethodInvocation pmi)) {
|
||||
throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
|
||||
}
|
||||
return getJoinPointMatch((ProxyMethodInvocation) mi);
|
||||
return getJoinPointMatch(pmi);
|
||||
}
|
||||
|
||||
// Note: We can't use JoinPointMatch.getClass().getName() as the key, since
|
||||
|
||||
+42
-70
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -38,6 +38,14 @@ import org.springframework.util.StringUtils;
|
||||
* for an advice method from the pointcut expression, returning, and throwing clauses.
|
||||
* If an unambiguous interpretation is not available, it returns {@code null}.
|
||||
*
|
||||
* <h3>Algorithm Summary</h3>
|
||||
* <p>If an unambiguous binding can be deduced, then it is.
|
||||
* If the advice requirements cannot possibly be satisfied, then {@code null}
|
||||
* is returned. By setting the {@link #setRaiseExceptions(boolean) raiseExceptions}
|
||||
* property to {@code true}, descriptive exceptions will be thrown instead of
|
||||
* returning {@code null} in the case that the parameter names cannot be discovered.
|
||||
*
|
||||
* <h3>Algorithm Details</h3>
|
||||
* <p>This class interprets arguments in the following way:
|
||||
* <ol>
|
||||
* <li>If the first parameter of the method is of type {@link JoinPoint}
|
||||
@@ -65,15 +73,15 @@ import org.springframework.util.StringUtils;
|
||||
* zero we proceed to the next stage. If {@code a} > 1 then an
|
||||
* {@code AmbiguousBindingException} is raised. If {@code a} == 1,
|
||||
* and there are no unbound arguments of type {@code Annotation+},
|
||||
* then an {@code IllegalArgumentException} is raised. if there is
|
||||
* then an {@code IllegalArgumentException} is raised. If there is
|
||||
* exactly one such argument, then the corresponding parameter name is
|
||||
* assigned the value from the pointcut expression.</li>
|
||||
* <li>If a returningName has been set, and there are no unbound arguments
|
||||
* <li>If a {@code returningName} has been set, and there are no unbound arguments
|
||||
* then an {@code IllegalArgumentException} is raised. If there is
|
||||
* more than one unbound argument then an
|
||||
* {@code AmbiguousBindingException} is raised. If there is exactly
|
||||
* one unbound argument then the corresponding parameter name is assigned
|
||||
* the value <returningName>.</li>
|
||||
* the value of the {@code returningName}.</li>
|
||||
* <li>If there remain unbound arguments, then the pointcut expression is
|
||||
* examined once more for {@code this}, {@code target}, and
|
||||
* {@code args} pointcut expressions used in the binding form (binding
|
||||
@@ -99,20 +107,12 @@ import org.springframework.util.StringUtils;
|
||||
* <p>The behavior on raising an {@code IllegalArgumentException} or
|
||||
* {@code AmbiguousBindingException} is configurable to allow this discoverer
|
||||
* to be used as part of a chain-of-responsibility. By default the condition will
|
||||
* be logged and the {@code getParameterNames(..)} method will simply return
|
||||
* be logged and the {@link #getParameterNames(Method)} method will simply return
|
||||
* {@code null}. If the {@link #setRaiseExceptions(boolean) raiseExceptions}
|
||||
* property is set to {@code true}, the conditions will be thrown as
|
||||
* {@code IllegalArgumentException} and {@code AmbiguousBindingException},
|
||||
* respectively.
|
||||
*
|
||||
* <p>Was that perfectly clear? ;)
|
||||
*
|
||||
* <p>Short version: If an unambiguous binding can be deduced, then it is.
|
||||
* If the advice requirements cannot possibly be satisfied, then {@code null}
|
||||
* is returned. By setting the {@link #setRaiseExceptions(boolean) raiseExceptions}
|
||||
* property to {@code true}, descriptive exceptions will be thrown instead of
|
||||
* returning {@code null} in the case that the parameter names cannot be discovered.
|
||||
*
|
||||
* @author Adrian Colyer
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.0
|
||||
@@ -197,7 +197,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
|
||||
/**
|
||||
* If {@code afterReturning} advice binds the return value, the
|
||||
* returning variable name must be specified.
|
||||
* {@code returning} variable name must be specified.
|
||||
* @param returningName the name of the returning variable
|
||||
*/
|
||||
public void setReturningName(@Nullable String returningName) {
|
||||
@@ -206,18 +206,17 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
|
||||
/**
|
||||
* If {@code afterThrowing} advice binds the thrown value, the
|
||||
* throwing variable name must be specified.
|
||||
* {@code throwing} variable name must be specified.
|
||||
* @param throwingName the name of the throwing variable
|
||||
*/
|
||||
public void setThrowingName(@Nullable String throwingName) {
|
||||
this.throwingName = throwingName;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deduce the parameter names for an advice method.
|
||||
* <p>See the {@link AspectJAdviceParameterNameDiscoverer class level javadoc}
|
||||
* for this class for details of the algorithm used.
|
||||
* <p>See the {@link AspectJAdviceParameterNameDiscoverer class-level javadoc}
|
||||
* for this class for details on the algorithm used.
|
||||
* @param method the target {@link Method}
|
||||
* @return the parameter names
|
||||
*/
|
||||
@@ -244,31 +243,18 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
int algorithmicStep = STEP_JOIN_POINT_BINDING;
|
||||
while ((this.numberOfRemainingUnboundArguments > 0) && algorithmicStep < STEP_FINISHED) {
|
||||
switch (algorithmicStep++) {
|
||||
case STEP_JOIN_POINT_BINDING:
|
||||
case STEP_JOIN_POINT_BINDING -> {
|
||||
if (!maybeBindThisJoinPoint()) {
|
||||
maybeBindThisJoinPointStaticPart();
|
||||
}
|
||||
break;
|
||||
case STEP_THROWING_BINDING:
|
||||
maybeBindThrowingVariable();
|
||||
break;
|
||||
case STEP_ANNOTATION_BINDING:
|
||||
maybeBindAnnotationsFromPointcutExpression();
|
||||
break;
|
||||
case STEP_RETURNING_BINDING:
|
||||
maybeBindReturningVariable();
|
||||
break;
|
||||
case STEP_PRIMITIVE_ARGS_BINDING:
|
||||
maybeBindPrimitiveArgsFromPointcutExpression();
|
||||
break;
|
||||
case STEP_THIS_TARGET_ARGS_BINDING:
|
||||
maybeBindThisOrTargetOrArgsFromPointcutExpression();
|
||||
break;
|
||||
case STEP_REFERENCE_PCUT_BINDING:
|
||||
maybeBindReferencePointcutParameter();
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unknown algorithmic step: " + (algorithmicStep - 1));
|
||||
}
|
||||
case STEP_THROWING_BINDING -> maybeBindThrowingVariable();
|
||||
case STEP_ANNOTATION_BINDING -> maybeBindAnnotationsFromPointcutExpression();
|
||||
case STEP_RETURNING_BINDING -> maybeBindReturningVariable();
|
||||
case STEP_PRIMITIVE_ARGS_BINDING -> maybeBindPrimitiveArgsFromPointcutExpression();
|
||||
case STEP_THIS_TARGET_ARGS_BINDING -> maybeBindThisOrTargetOrArgsFromPointcutExpression();
|
||||
case STEP_REFERENCE_PCUT_BINDING -> maybeBindReferencePointcutParameter();
|
||||
default -> throw new IllegalStateException("Unknown algorithmic step: " + (algorithmicStep - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -322,7 +308,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
}
|
||||
|
||||
/**
|
||||
* If the first parameter is of type JoinPoint or ProceedingJoinPoint,bind "thisJoinPoint" as
|
||||
* If the first parameter is of type JoinPoint or ProceedingJoinPoint, bind "thisJoinPoint" as
|
||||
* parameter name and return true, else return false.
|
||||
*/
|
||||
private boolean maybeBindThisJoinPoint() {
|
||||
@@ -361,14 +347,14 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
// Second candidate we've found - ambiguous binding
|
||||
throw new AmbiguousBindingException("Binding of throwing parameter '" +
|
||||
this.throwingName + "' is ambiguous: could be bound to argument " +
|
||||
throwableIndex + " or argument " + i);
|
||||
throwableIndex + " or " + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (throwableIndex == -1) {
|
||||
throw new IllegalStateException("Binding of throwing parameter '" + this.throwingName
|
||||
+ "' could not be completed as no available arguments are a subtype of Throwable");
|
||||
throw new IllegalStateException("Binding of throwing parameter '" + this.throwingName +
|
||||
"' could not be completed as no available arguments are a subtype of Throwable");
|
||||
}
|
||||
else {
|
||||
bindParameterName(throwableIndex, this.throwingName);
|
||||
@@ -387,7 +373,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
if (this.returningName != null) {
|
||||
if (this.numberOfRemainingUnboundArguments > 1) {
|
||||
throw new AmbiguousBindingException("Binding of returning parameter '" + this.returningName +
|
||||
"' is ambiguous, there are " + this.numberOfRemainingUnboundArguments + " candidates.");
|
||||
"' is ambiguous: there are " + this.numberOfRemainingUnboundArguments + " candidates.");
|
||||
}
|
||||
|
||||
// We're all set... find the unbound parameter, and bind it.
|
||||
@@ -400,7 +386,6 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse the string pointcut expression looking for:
|
||||
* @this, @target, @args, @within, @withincode, @annotation.
|
||||
@@ -444,7 +429,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
int numAnnotationSlots = countNumberOfUnboundAnnotationArguments();
|
||||
if (numAnnotationSlots > 1) {
|
||||
throw new AmbiguousBindingException("Found " + varNames.size() +
|
||||
" potential annotation variable(s), and " +
|
||||
" potential annotation variable(s) and " +
|
||||
numAnnotationSlots + " potential argument slots");
|
||||
}
|
||||
else if (numAnnotationSlots == 1) {
|
||||
@@ -465,7 +450,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* If the token starts meets Java identifier conventions, it's in.
|
||||
*/
|
||||
@Nullable
|
||||
@@ -501,7 +486,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
private void maybeBindThisOrTargetOrArgsFromPointcutExpression() {
|
||||
if (this.numberOfRemainingUnboundArguments > 1) {
|
||||
throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments
|
||||
+ " unbound args at this(),target(),args() binding stage, with no way to determine between them");
|
||||
+ " unbound args at this()/target()/args() binding stage, with no way to determine between them");
|
||||
}
|
||||
|
||||
List<String> varNames = new ArrayList<>();
|
||||
@@ -533,10 +518,9 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (varNames.size() > 1) {
|
||||
throw new AmbiguousBindingException("Found " + varNames.size() +
|
||||
" candidate this(), target() or args() variables but only one unbound argument slot");
|
||||
" candidate this(), target(), or args() variables but only one unbound argument slot");
|
||||
}
|
||||
else if (varNames.size() == 1) {
|
||||
for (int j = 0; j < this.parameterNameBindings.length; j++) {
|
||||
@@ -609,7 +593,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
// else varNames.size must be 0 and we have nothing to bind.
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* We've found the start of a binding pointcut at the given index into the
|
||||
* token array. Now we need to extract the pointcut body and return it.
|
||||
*/
|
||||
@@ -662,8 +646,8 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
private void maybeBindPrimitiveArgsFromPointcutExpression() {
|
||||
int numUnboundPrimitives = countNumberOfUnboundPrimitiveArguments();
|
||||
if (numUnboundPrimitives > 1) {
|
||||
throw new AmbiguousBindingException("Found '" + numUnboundPrimitives +
|
||||
"' unbound primitive arguments with no way to distinguish between them.");
|
||||
throw new AmbiguousBindingException("Found " + numUnboundPrimitives +
|
||||
" unbound primitive arguments with no way to distinguish between them.");
|
||||
}
|
||||
if (numUnboundPrimitives == 1) {
|
||||
// Look for arg variable and bind it if we find exactly one...
|
||||
@@ -709,7 +693,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* Return {@code true} if the given argument type is a subclass
|
||||
* of the given supertype.
|
||||
*/
|
||||
@@ -737,7 +721,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
return count;
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* Find the argument index with the given type, and bind the given
|
||||
* {@code varName} in that position.
|
||||
*/
|
||||
@@ -754,22 +738,10 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
|
||||
|
||||
/**
|
||||
* Simple struct to hold the extracted text from a pointcut body, together
|
||||
* Simple record to hold the extracted text from a pointcut body, together
|
||||
* with the number of tokens consumed in extracting it.
|
||||
*/
|
||||
private static class PointcutBody {
|
||||
|
||||
private final int numTokensConsumed;
|
||||
|
||||
@Nullable
|
||||
private final String text;
|
||||
|
||||
public PointcutBody(int tokens, @Nullable String text) {
|
||||
this.numTokensConsumed = tokens;
|
||||
this.text = text;
|
||||
}
|
||||
}
|
||||
|
||||
private record PointcutBody(int numTokensConsumed, @Nullable String text) {}
|
||||
|
||||
/**
|
||||
* Thrown in response to an ambiguous binding being detected when
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -61,12 +61,12 @@ public abstract class AspectJAopUtils {
|
||||
*/
|
||||
@Nullable
|
||||
public static AspectJPrecedenceInformation getAspectJPrecedenceInformationFor(Advisor anAdvisor) {
|
||||
if (anAdvisor instanceof AspectJPrecedenceInformation) {
|
||||
return (AspectJPrecedenceInformation) anAdvisor;
|
||||
if (anAdvisor instanceof AspectJPrecedenceInformation ajpi) {
|
||||
return ajpi;
|
||||
}
|
||||
Advice advice = anAdvisor.getAdvice();
|
||||
if (advice instanceof AspectJPrecedenceInformation) {
|
||||
return (AspectJPrecedenceInformation) advice;
|
||||
if (advice instanceof AspectJPrecedenceInformation ajpi) {
|
||||
return ajpi;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
+9
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -200,8 +200,8 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
||||
*/
|
||||
@Nullable
|
||||
private ClassLoader determinePointcutClassLoader() {
|
||||
if (this.beanFactory instanceof ConfigurableBeanFactory) {
|
||||
return ((ConfigurableBeanFactory) this.beanFactory).getBeanClassLoader();
|
||||
if (this.beanFactory instanceof ConfigurableBeanFactory cbf) {
|
||||
return cbf.getBeanClassLoader();
|
||||
}
|
||||
if (this.pointcutDeclarationScope != null) {
|
||||
return this.pointcutDeclarationScope.getClassLoader();
|
||||
@@ -335,10 +335,10 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
||||
try {
|
||||
MethodInvocation mi = ExposeInvocationInterceptor.currentInvocation();
|
||||
targetObject = mi.getThis();
|
||||
if (!(mi instanceof ProxyMethodInvocation)) {
|
||||
if (!(mi instanceof ProxyMethodInvocation _pmi)) {
|
||||
throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
|
||||
}
|
||||
pmi = (ProxyMethodInvocation) mi;
|
||||
pmi = _pmi;
|
||||
thisObject = pmi.getProxy();
|
||||
}
|
||||
catch (IllegalStateException ex) {
|
||||
@@ -404,8 +404,8 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
||||
}
|
||||
|
||||
private RuntimeTestWalker getRuntimeTestWalker(ShadowMatch shadowMatch) {
|
||||
if (shadowMatch instanceof DefensiveShadowMatch) {
|
||||
return new RuntimeTestWalker(((DefensiveShadowMatch) shadowMatch).primary);
|
||||
if (shadowMatch instanceof DefensiveShadowMatch defensiveShadowMatch) {
|
||||
return new RuntimeTestWalker(defensiveShadowMatch.primary);
|
||||
}
|
||||
return new RuntimeTestWalker(shadowMatch);
|
||||
}
|
||||
@@ -422,7 +422,8 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
||||
|
||||
private ShadowMatch getTargetShadowMatch(Method method, Class<?> targetClass) {
|
||||
Method targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);
|
||||
if (targetMethod.getDeclaringClass().isInterface()) {
|
||||
if (targetMethod.getDeclaringClass().isInterface() && targetMethod.getDeclaringClass() != targetClass &&
|
||||
obtainPointcutExpression().getPointcutExpression().contains("." + targetMethod.getName() + "(")) {
|
||||
// Try to build the most specific interface possible for inherited methods to be
|
||||
// considered for sub-interface matches as well, in particular for proxy classes.
|
||||
// Note: AspectJ is only going to take Method.getDeclaringClass() into account.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -71,8 +71,8 @@ public abstract class AspectJProxyUtils {
|
||||
private static boolean isAspectJAdvice(Advisor advisor) {
|
||||
return (advisor instanceof InstantiationModelAwarePointcutAdvisor ||
|
||||
advisor.getAdvice() instanceof AbstractAspectJAdvice ||
|
||||
(advisor instanceof PointcutAdvisor &&
|
||||
((PointcutAdvisor) advisor).getPointcut() instanceof AspectJExpressionPointcut));
|
||||
(advisor instanceof PointcutAdvisor pointcutAdvisor &&
|
||||
pointcutAdvisor.getPointcut() instanceof AspectJExpressionPointcut));
|
||||
}
|
||||
|
||||
static boolean isVariableName(@Nullable String name) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -202,8 +202,8 @@ class RuntimeTestWalker {
|
||||
}
|
||||
Class<?> typeClass = null;
|
||||
ResolvedType type = (ResolvedType) i.getType();
|
||||
if (type instanceof ReferenceType) {
|
||||
ReferenceTypeDelegate delegate = ((ReferenceType) type).getDelegate();
|
||||
if (type instanceof ReferenceType referenceType) {
|
||||
ReferenceTypeDelegate delegate = referenceType.getDelegate();
|
||||
if (delegate instanceof ReflectionBasedReferenceTypeDelegate) {
|
||||
try {
|
||||
ReflectionUtils.makeAccessible(myClassField);
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -69,8 +69,8 @@ public class SingletonAspectInstanceFactory implements AspectInstanceFactory, Se
|
||||
*/
|
||||
@Override
|
||||
public int getOrder() {
|
||||
if (this.aspectInstance instanceof Ordered) {
|
||||
return ((Ordered) this.aspectInstance).getOrder();
|
||||
if (this.aspectInstance instanceof Ordered ordered) {
|
||||
return ordered.getOrder();
|
||||
}
|
||||
return getOrderForAspectClass(this.aspectInstance.getClass());
|
||||
}
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -117,9 +117,9 @@ public class TypePatternClassFilter implements ClassFilter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
return (this == other || (other instanceof TypePatternClassFilter &&
|
||||
ObjectUtils.nullSafeEquals(this.typePattern, ((TypePatternClassFilter) other).typePattern)));
|
||||
public boolean equals(Object obj) {
|
||||
return (this == obj || (obj instanceof TypePatternClassFilter that &&
|
||||
ObjectUtils.nullSafeEquals(this.typePattern, that.typePattern)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+39
-50
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,8 +20,6 @@ import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
@@ -53,6 +51,7 @@ import org.springframework.lang.Nullable;
|
||||
* @author Rod Johnson
|
||||
* @author Adrian Colyer
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @since 2.0
|
||||
*/
|
||||
public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFactory {
|
||||
@@ -102,14 +101,6 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
|
||||
|
||||
@Override
|
||||
public void validate(Class<?> aspectClass) throws AopConfigException {
|
||||
// If the parent has the annotation and isn't abstract it's an error
|
||||
Class<?> superclass = aspectClass.getSuperclass();
|
||||
if (superclass.getAnnotation(Aspect.class) != null &&
|
||||
!Modifier.isAbstract(superclass.getModifiers())) {
|
||||
throw new AopConfigException("[" + aspectClass.getName() + "] cannot extend concrete aspect [" +
|
||||
superclass.getName() + "]");
|
||||
}
|
||||
|
||||
AjType<?> ajType = AjTypeSystem.getAjType(aspectClass);
|
||||
if (!ajType.isAspect()) {
|
||||
throw new NotAnAtAspectException(aspectClass);
|
||||
@@ -130,21 +121,21 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
protected static AspectJAnnotation<?> findAspectJAnnotationOnMethod(Method method) {
|
||||
for (Class<?> clazz : ASPECTJ_ANNOTATION_CLASSES) {
|
||||
AspectJAnnotation<?> foundAnnotation = findAnnotation(method, (Class<Annotation>) clazz);
|
||||
if (foundAnnotation != null) {
|
||||
return foundAnnotation;
|
||||
protected static AspectJAnnotation findAspectJAnnotationOnMethod(Method method) {
|
||||
for (Class<?> annotationType : ASPECTJ_ANNOTATION_CLASSES) {
|
||||
AspectJAnnotation annotation = findAnnotation(method, (Class<Annotation>) annotationType);
|
||||
if (annotation != null) {
|
||||
return annotation;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static <A extends Annotation> AspectJAnnotation<A> findAnnotation(Method method, Class<A> toLookFor) {
|
||||
A result = AnnotationUtils.findAnnotation(method, toLookFor);
|
||||
if (result != null) {
|
||||
return new AspectJAnnotation<>(result);
|
||||
private static AspectJAnnotation findAnnotation(Method method, Class<? extends Annotation> annotationType) {
|
||||
Annotation annotation = AnnotationUtils.findAnnotation(method, annotationType);
|
||||
if (annotation != null) {
|
||||
return new AspectJAnnotation(annotation);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
@@ -163,26 +154,23 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
|
||||
|
||||
|
||||
/**
|
||||
* Class modelling an AspectJ annotation, exposing its type enumeration and
|
||||
* Class modeling an AspectJ annotation, exposing its type enumeration and
|
||||
* pointcut String.
|
||||
* @param <A> the annotation type
|
||||
*/
|
||||
protected static class AspectJAnnotation<A extends Annotation> {
|
||||
protected static class AspectJAnnotation {
|
||||
|
||||
private static final String[] EXPRESSION_ATTRIBUTES = new String[] {"pointcut", "value"};
|
||||
private static final String[] EXPRESSION_ATTRIBUTES = {"pointcut", "value"};
|
||||
|
||||
private static Map<Class<?>, AspectJAnnotationType> annotationTypeMap = new HashMap<>(8);
|
||||
private static final Map<Class<?>, AspectJAnnotationType> annotationTypeMap = Map.of(
|
||||
Pointcut.class, AspectJAnnotationType.AtPointcut, //
|
||||
Around.class, AspectJAnnotationType.AtAround, //
|
||||
Before.class, AspectJAnnotationType.AtBefore, //
|
||||
After.class, AspectJAnnotationType.AtAfter, //
|
||||
AfterReturning.class, AspectJAnnotationType.AtAfterReturning, //
|
||||
AfterThrowing.class, AspectJAnnotationType.AtAfterThrowing //
|
||||
);
|
||||
|
||||
static {
|
||||
annotationTypeMap.put(Pointcut.class, AspectJAnnotationType.AtPointcut);
|
||||
annotationTypeMap.put(Around.class, AspectJAnnotationType.AtAround);
|
||||
annotationTypeMap.put(Before.class, AspectJAnnotationType.AtBefore);
|
||||
annotationTypeMap.put(After.class, AspectJAnnotationType.AtAfter);
|
||||
annotationTypeMap.put(AfterReturning.class, AspectJAnnotationType.AtAfterReturning);
|
||||
annotationTypeMap.put(AfterThrowing.class, AspectJAnnotationType.AtAfterThrowing);
|
||||
}
|
||||
|
||||
private final A annotation;
|
||||
private final Annotation annotation;
|
||||
|
||||
private final AspectJAnnotationType annotationType;
|
||||
|
||||
@@ -190,20 +178,20 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
|
||||
|
||||
private final String argumentNames;
|
||||
|
||||
public AspectJAnnotation(A annotation) {
|
||||
public AspectJAnnotation(Annotation annotation) {
|
||||
this.annotation = annotation;
|
||||
this.annotationType = determineAnnotationType(annotation);
|
||||
try {
|
||||
this.pointcutExpression = resolveExpression(annotation);
|
||||
this.pointcutExpression = resolvePointcutExpression(annotation);
|
||||
Object argNames = AnnotationUtils.getValue(annotation, "argNames");
|
||||
this.argumentNames = (argNames instanceof String ? (String) argNames : "");
|
||||
this.argumentNames = (argNames instanceof String names ? names : "");
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalArgumentException(annotation + " is not a valid AspectJ annotation", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private AspectJAnnotationType determineAnnotationType(A annotation) {
|
||||
private AspectJAnnotationType determineAnnotationType(Annotation annotation) {
|
||||
AspectJAnnotationType type = annotationTypeMap.get(annotation.annotationType());
|
||||
if (type != null) {
|
||||
return type;
|
||||
@@ -211,23 +199,21 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
|
||||
throw new IllegalStateException("Unknown annotation type: " + annotation);
|
||||
}
|
||||
|
||||
private String resolveExpression(A annotation) {
|
||||
private String resolvePointcutExpression(Annotation annotation) {
|
||||
for (String attributeName : EXPRESSION_ATTRIBUTES) {
|
||||
Object val = AnnotationUtils.getValue(annotation, attributeName);
|
||||
if (val instanceof String str) {
|
||||
if (!str.isEmpty()) {
|
||||
return str;
|
||||
}
|
||||
if (val instanceof String str && !str.isEmpty()) {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Failed to resolve expression: " + annotation);
|
||||
throw new IllegalStateException("Failed to resolve pointcut expression in: " + annotation);
|
||||
}
|
||||
|
||||
public AspectJAnnotationType getAnnotationType() {
|
||||
return this.annotationType;
|
||||
}
|
||||
|
||||
public A getAnnotation() {
|
||||
public Annotation getAnnotation() {
|
||||
return this.annotation;
|
||||
}
|
||||
|
||||
@@ -252,19 +238,22 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
|
||||
*/
|
||||
private static class AspectJAnnotationParameterNameDiscoverer implements ParameterNameDiscoverer {
|
||||
|
||||
private static final String[] EMPTY_ARRAY = new String[0];
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String[] getParameterNames(Method method) {
|
||||
if (method.getParameterCount() == 0) {
|
||||
return new String[0];
|
||||
return EMPTY_ARRAY;
|
||||
}
|
||||
AspectJAnnotation<?> annotation = findAspectJAnnotationOnMethod(method);
|
||||
AspectJAnnotation annotation = findAspectJAnnotationOnMethod(method);
|
||||
if (annotation == null) {
|
||||
return null;
|
||||
}
|
||||
StringTokenizer nameTokens = new StringTokenizer(annotation.getArgumentNames(), ",");
|
||||
if (nameTokens.countTokens() > 0) {
|
||||
String[] names = new String[nameTokens.countTokens()];
|
||||
int numTokens = nameTokens.countTokens();
|
||||
if (numTokens > 0) {
|
||||
String[] names = new String[numTokens];
|
||||
for (int i = 0; i < names.length; i++) {
|
||||
names[i] = nameTokens.nextToken();
|
||||
}
|
||||
|
||||
+5
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -93,9 +93,8 @@ public class BeanFactoryAspectInstanceFactory implements MetadataAwareAspectInst
|
||||
@Override
|
||||
@Nullable
|
||||
public ClassLoader getAspectClassLoader() {
|
||||
return (this.beanFactory instanceof ConfigurableBeanFactory ?
|
||||
((ConfigurableBeanFactory) this.beanFactory).getBeanClassLoader() :
|
||||
ClassUtils.getDefaultClassLoader());
|
||||
return (this.beanFactory instanceof ConfigurableBeanFactory cbf ?
|
||||
cbf.getBeanClassLoader() : ClassUtils.getDefaultClassLoader());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -110,11 +109,11 @@ public class BeanFactoryAspectInstanceFactory implements MetadataAwareAspectInst
|
||||
// Rely on singleton semantics provided by the factory -> no local lock.
|
||||
return null;
|
||||
}
|
||||
else if (this.beanFactory instanceof ConfigurableBeanFactory) {
|
||||
else if (this.beanFactory instanceof ConfigurableBeanFactory cbf) {
|
||||
// No singleton guarantees from the factory -> let's lock locally but
|
||||
// reuse the factory's singleton lock, just in case a lazy dependency
|
||||
// of our advice bean happens to trigger the singleton lock implicitly...
|
||||
return ((ConfigurableBeanFactory) this.beanFactory).getSingletonMutex();
|
||||
return cbf.getSingletonMutex();
|
||||
}
|
||||
else {
|
||||
return this;
|
||||
|
||||
+6
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -35,7 +35,8 @@ import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Internal implementation of AspectJPointcutAdvisor.
|
||||
* Note that there will be one instance of this advisor for each target method.
|
||||
*
|
||||
* <p>Note that there will be one instance of this advisor for each target method.
|
||||
*
|
||||
* @author Rod Johnson
|
||||
* @author Juergen Hoeller
|
||||
@@ -212,7 +213,7 @@ final class InstantiationModelAwarePointcutAdvisorImpl
|
||||
* creation of the advice.
|
||||
*/
|
||||
private void determineAdviceType() {
|
||||
AspectJAnnotation<?> aspectJAnnotation =
|
||||
AspectJAnnotation aspectJAnnotation =
|
||||
AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(this.aspectJAdviceMethod);
|
||||
if (aspectJAnnotation == null) {
|
||||
this.isBeforeAdvice = false;
|
||||
@@ -274,8 +275,8 @@ final class InstantiationModelAwarePointcutAdvisorImpl
|
||||
|
||||
this.declaredPointcut = declaredPointcut;
|
||||
this.preInstantiationPointcut = preInstantiationPointcut;
|
||||
if (aspectInstanceFactory instanceof LazySingletonAspectInstanceFactoryDecorator) {
|
||||
this.aspectInstanceFactory = (LazySingletonAspectInstanceFactoryDecorator) aspectInstanceFactory;
|
||||
if (aspectInstanceFactory instanceof LazySingletonAspectInstanceFactoryDecorator lazyFactory) {
|
||||
this.aspectInstanceFactory = lazyFactory;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,10 +23,6 @@ import org.springframework.lang.Nullable;
|
||||
* Subinterface of {@link org.springframework.aop.aspectj.AspectInstanceFactory}
|
||||
* that returns {@link AspectMetadata} associated with AspectJ-annotated classes.
|
||||
*
|
||||
* <p>Ideally, AspectInstanceFactory would include this method itself, but because
|
||||
* AspectMetadata uses Java-5-only {@link org.aspectj.lang.reflect.AjType},
|
||||
* we need to split out this subinterface.
|
||||
*
|
||||
* @author Rod Johnson
|
||||
* @since 2.0
|
||||
* @see AspectMetadata
|
||||
@@ -35,13 +31,13 @@ import org.springframework.lang.Nullable;
|
||||
public interface MetadataAwareAspectInstanceFactory extends AspectInstanceFactory {
|
||||
|
||||
/**
|
||||
* Return the AspectJ AspectMetadata for this factory's aspect.
|
||||
* Get the AspectJ AspectMetadata for this factory's aspect.
|
||||
* @return the aspect metadata
|
||||
*/
|
||||
AspectMetadata getAspectMetadata();
|
||||
|
||||
/**
|
||||
* Return the best possible creation mutex for this factory.
|
||||
* Get the best possible creation mutex for this factory.
|
||||
* @return the mutex object (may be {@code null} for no mutex to use)
|
||||
* @since 4.3
|
||||
*/
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -87,7 +87,7 @@ public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFacto
|
||||
new InstanceComparator<>(
|
||||
Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class),
|
||||
(Converter<Method, Annotation>) method -> {
|
||||
AspectJAnnotation<?> ann = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(method);
|
||||
AspectJAnnotation ann = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(method);
|
||||
return (ann != null ? ann.getAnnotation() : null);
|
||||
});
|
||||
Comparator<Method> methodNameComparator = new ConvertingComparator<>(Method::getName);
|
||||
@@ -216,7 +216,7 @@ public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFacto
|
||||
|
||||
@Nullable
|
||||
private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {
|
||||
AspectJAnnotation<?> aspectJAnnotation =
|
||||
AspectJAnnotation aspectJAnnotation =
|
||||
AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
|
||||
if (aspectJAnnotation == null) {
|
||||
return null;
|
||||
@@ -240,7 +240,7 @@ public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFacto
|
||||
Class<?> candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
|
||||
validate(candidateAspectClass);
|
||||
|
||||
AspectJAnnotation<?> aspectJAnnotation =
|
||||
AspectJAnnotation aspectJAnnotation =
|
||||
AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
|
||||
if (aspectJAnnotation == null) {
|
||||
return null;
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -93,8 +93,8 @@ public abstract class AbstractInterceptorDrivenBeanDefinitionDecorator implement
|
||||
// copy autowire settings from original bean definition.
|
||||
proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate());
|
||||
proxyDefinition.setPrimary(targetDefinition.isPrimary());
|
||||
if (targetDefinition instanceof AbstractBeanDefinition) {
|
||||
proxyDefinition.copyQualifiersFrom((AbstractBeanDefinition) targetDefinition);
|
||||
if (targetDefinition instanceof AbstractBeanDefinition abd) {
|
||||
proxyDefinition.copyQualifiersFrom(abd);
|
||||
}
|
||||
// wrap it in a BeanDefinitionHolder with bean name
|
||||
result = new BeanDefinitionHolder(proxyDefinition, existingBeanName);
|
||||
|
||||
+13
-17
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -108,14 +108,10 @@ class ConfigBeanDefinitionParser implements BeanDefinitionParser {
|
||||
List<Element> childElts = DomUtils.getChildElements(element);
|
||||
for (Element elt: childElts) {
|
||||
String localName = parserContext.getDelegate().getLocalName(elt);
|
||||
if (POINTCUT.equals(localName)) {
|
||||
parsePointcut(elt, parserContext);
|
||||
}
|
||||
else if (ADVISOR.equals(localName)) {
|
||||
parseAdvisor(elt, parserContext);
|
||||
}
|
||||
else if (ASPECT.equals(localName)) {
|
||||
parseAspect(elt, parserContext);
|
||||
switch (localName) {
|
||||
case POINTCUT -> parsePointcut(elt, parserContext);
|
||||
case ADVISOR -> parseAdvisor(elt, parserContext);
|
||||
case ASPECT -> parseAspect(elt, parserContext);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,13 +149,13 @@ class ConfigBeanDefinitionParser implements BeanDefinitionParser {
|
||||
}
|
||||
|
||||
Object pointcut = parsePointcutProperty(advisorElement, parserContext);
|
||||
if (pointcut instanceof BeanDefinition) {
|
||||
if (pointcut instanceof BeanDefinition beanDefinition) {
|
||||
advisorDef.getPropertyValues().add(POINTCUT, pointcut);
|
||||
parserContext.registerComponent(
|
||||
new AdvisorComponentDefinition(advisorBeanName, advisorDef, (BeanDefinition) pointcut));
|
||||
new AdvisorComponentDefinition(advisorBeanName, advisorDef, beanDefinition));
|
||||
}
|
||||
else if (pointcut instanceof String) {
|
||||
advisorDef.getPropertyValues().add(POINTCUT, new RuntimeBeanReference((String) pointcut));
|
||||
else if (pointcut instanceof String beanName) {
|
||||
advisorDef.getPropertyValues().add(POINTCUT, new RuntimeBeanReference(beanName));
|
||||
parserContext.registerComponent(
|
||||
new AdvisorComponentDefinition(advisorBeanName, advisorDef));
|
||||
}
|
||||
@@ -389,12 +385,12 @@ class ConfigBeanDefinitionParser implements BeanDefinitionParser {
|
||||
cav.addIndexedArgumentValue(METHOD_INDEX, methodDef);
|
||||
|
||||
Object pointcut = parsePointcutProperty(adviceElement, parserContext);
|
||||
if (pointcut instanceof BeanDefinition) {
|
||||
if (pointcut instanceof BeanDefinition beanDefinition) {
|
||||
cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcut);
|
||||
beanDefinitions.add((BeanDefinition) pointcut);
|
||||
beanDefinitions.add(beanDefinition);
|
||||
}
|
||||
else if (pointcut instanceof String) {
|
||||
RuntimeBeanReference pointcutRef = new RuntimeBeanReference((String) pointcut);
|
||||
else if (pointcut instanceof String beanName) {
|
||||
RuntimeBeanReference pointcutRef = new RuntimeBeanReference(beanName);
|
||||
cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcutRef);
|
||||
beanReferences.add(pointcutRef);
|
||||
}
|
||||
|
||||
+2
-2
@@ -71,8 +71,8 @@ public class SimpleBeanFactoryAwareAspectInstanceFactory implements AspectInstan
|
||||
@Override
|
||||
@Nullable
|
||||
public ClassLoader getAspectClassLoader() {
|
||||
if (this.beanFactory instanceof ConfigurableBeanFactory) {
|
||||
return ((ConfigurableBeanFactory) this.beanFactory).getBeanClassLoader();
|
||||
if (this.beanFactory instanceof ConfigurableBeanFactory cbf) {
|
||||
return cbf.getBeanClassLoader();
|
||||
}
|
||||
else {
|
||||
return ClassUtils.getDefaultClassLoader();
|
||||
|
||||
+14
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -74,8 +74,9 @@ public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSu
|
||||
|
||||
// Use original ClassLoader if bean class not locally loaded in overriding class loader
|
||||
ClassLoader classLoader = getProxyClassLoader();
|
||||
if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) {
|
||||
classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();
|
||||
if (classLoader instanceof SmartClassLoader smartClassLoader &&
|
||||
classLoader != beanClass.getClassLoader()) {
|
||||
classLoader = smartClassLoader.getOriginalClassLoader();
|
||||
}
|
||||
return proxyFactory.getProxyClass(classLoader);
|
||||
}
|
||||
@@ -92,10 +93,16 @@ public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSu
|
||||
|
||||
if (bean instanceof Advised advised) {
|
||||
if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {
|
||||
// Add our local Advisor to the existing proxy's Advisor chain...
|
||||
// Add our local Advisor to the existing proxy's Advisor chain.
|
||||
if (this.beforeExistingAdvisors) {
|
||||
advised.addAdvisor(0, this.advisor);
|
||||
}
|
||||
else if (advised.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE &&
|
||||
advised.getAdvisorCount() > 0) {
|
||||
// No target, leave last Advisor in place and add new Advisor right before.
|
||||
advised.addAdvisor(advised.getAdvisorCount() - 1, this.advisor);
|
||||
return bean;
|
||||
}
|
||||
else {
|
||||
advised.addAdvisor(this.advisor);
|
||||
}
|
||||
@@ -113,8 +120,9 @@ public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSu
|
||||
|
||||
// Use original ClassLoader if bean class not locally loaded in overriding class loader
|
||||
ClassLoader classLoader = getProxyClassLoader();
|
||||
if (classLoader instanceof SmartClassLoader && classLoader != bean.getClass().getClassLoader()) {
|
||||
classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();
|
||||
if (classLoader instanceof SmartClassLoader smartClassLoader &&
|
||||
classLoader != bean.getClass().getClassLoader()) {
|
||||
classLoader = smartClassLoader.getOriginalClassLoader();
|
||||
}
|
||||
return proxyFactory.getProxy(classLoader);
|
||||
}
|
||||
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -194,8 +194,8 @@ public abstract class AbstractSingletonProxyFactoryBean extends ProxyConfig
|
||||
* @return a TargetSource for this object
|
||||
*/
|
||||
protected TargetSource createTargetSource(Object target) {
|
||||
if (target instanceof TargetSource) {
|
||||
return (TargetSource) target;
|
||||
if (target instanceof TargetSource targetSource) {
|
||||
return targetSource;
|
||||
}
|
||||
else {
|
||||
return new SingletonTargetSource(target);
|
||||
@@ -229,8 +229,8 @@ public abstract class AbstractSingletonProxyFactoryBean extends ProxyConfig
|
||||
if (this.proxyInterfaces != null && this.proxyInterfaces.length == 1) {
|
||||
return this.proxyInterfaces[0];
|
||||
}
|
||||
if (this.target instanceof TargetSource) {
|
||||
return ((TargetSource) this.target).getTargetClass();
|
||||
if (this.target instanceof TargetSource targetSource) {
|
||||
return targetSource.getTargetClass();
|
||||
}
|
||||
if (this.target != null) {
|
||||
return this.target.getClass();
|
||||
|
||||
@@ -258,8 +258,8 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
|
||||
@Override
|
||||
public void addAdvisor(int pos, Advisor advisor) throws AopConfigException {
|
||||
if (advisor instanceof IntroductionAdvisor) {
|
||||
validateIntroductionAdvisor((IntroductionAdvisor) advisor);
|
||||
if (advisor instanceof IntroductionAdvisor introductionAdvisor) {
|
||||
validateIntroductionAdvisor(introductionAdvisor);
|
||||
}
|
||||
addAdvisorInternal(pos, advisor);
|
||||
}
|
||||
@@ -287,9 +287,9 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
}
|
||||
|
||||
Advisor advisor = this.advisors.remove(index);
|
||||
if (advisor instanceof IntroductionAdvisor ia) {
|
||||
if (advisor instanceof IntroductionAdvisor introductionAdvisor) {
|
||||
// We need to remove introduction interfaces.
|
||||
for (Class<?> ifc : ia.getInterfaces()) {
|
||||
for (Class<?> ifc : introductionAdvisor.getInterfaces()) {
|
||||
removeInterface(ifc);
|
||||
}
|
||||
}
|
||||
@@ -387,10 +387,10 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
@Override
|
||||
public void addAdvice(int pos, Advice advice) throws AopConfigException {
|
||||
Assert.notNull(advice, "Advice must not be null");
|
||||
if (advice instanceof IntroductionInfo) {
|
||||
if (advice instanceof IntroductionInfo introductionInfo) {
|
||||
// We don't need an IntroductionAdvisor for this kind of introduction:
|
||||
// It's fully self-describing.
|
||||
addAdvisor(pos, new DefaultIntroductionAdvisor(advice, (IntroductionInfo) advice));
|
||||
addAdvisor(pos, new DefaultIntroductionAdvisor(advice, introductionInfo));
|
||||
}
|
||||
else if (advice instanceof DynamicIntroductionAdvice) {
|
||||
// We need an IntroductionAdvisor for this kind of introduction.
|
||||
@@ -506,8 +506,8 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
this.advisorChainFactory = other.advisorChainFactory;
|
||||
this.interfaces = new ArrayList<>(other.interfaces);
|
||||
for (Advisor advisor : advisors) {
|
||||
if (advisor instanceof IntroductionAdvisor) {
|
||||
validateIntroductionAdvisor((IntroductionAdvisor) advisor);
|
||||
if (advisor instanceof IntroductionAdvisor introductionAdvisor) {
|
||||
validateIntroductionAdvisor(introductionAdvisor);
|
||||
}
|
||||
Assert.notNull(advisor, "Advisor must not be null");
|
||||
this.advisors.add(advisor);
|
||||
@@ -580,8 +580,8 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
return (this == other || (other instanceof MethodCacheKey &&
|
||||
this.method == ((MethodCacheKey) other).method));
|
||||
return (this == other || (other instanceof MethodCacheKey methodCacheKey &&
|
||||
this.method == methodCacheKey.method));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -257,23 +257,21 @@ public abstract class AopProxyUtils {
|
||||
if (ObjectUtils.isEmpty(arguments)) {
|
||||
return new Object[0];
|
||||
}
|
||||
if (method.isVarArgs()) {
|
||||
if (method.getParameterCount() == arguments.length) {
|
||||
Class<?>[] paramTypes = method.getParameterTypes();
|
||||
int varargIndex = paramTypes.length - 1;
|
||||
Class<?> varargType = paramTypes[varargIndex];
|
||||
if (varargType.isArray()) {
|
||||
Object varargArray = arguments[varargIndex];
|
||||
if (varargArray instanceof Object[] && !varargType.isInstance(varargArray)) {
|
||||
Object[] newArguments = new Object[arguments.length];
|
||||
System.arraycopy(arguments, 0, newArguments, 0, varargIndex);
|
||||
Class<?> targetElementType = varargType.getComponentType();
|
||||
int varargLength = Array.getLength(varargArray);
|
||||
Object newVarargArray = Array.newInstance(targetElementType, varargLength);
|
||||
System.arraycopy(varargArray, 0, newVarargArray, 0, varargLength);
|
||||
newArguments[varargIndex] = newVarargArray;
|
||||
return newArguments;
|
||||
}
|
||||
if (method.isVarArgs() && (method.getParameterCount() == arguments.length)) {
|
||||
Class<?>[] paramTypes = method.getParameterTypes();
|
||||
int varargIndex = paramTypes.length - 1;
|
||||
Class<?> varargType = paramTypes[varargIndex];
|
||||
if (varargType.isArray()) {
|
||||
Object varargArray = arguments[varargIndex];
|
||||
if (varargArray instanceof Object[] && !varargType.isInstance(varargArray)) {
|
||||
Object[] newArguments = new Object[arguments.length];
|
||||
System.arraycopy(arguments, 0, newArguments, 0, varargIndex);
|
||||
Class<?> targetElementType = varargType.getComponentType();
|
||||
int varargLength = Array.getLength(varargArray);
|
||||
Object newVarargArray = Array.newInstance(targetElementType, varargLength);
|
||||
System.arraycopy(varargArray, 0, newVarargArray, 0, varargLength);
|
||||
newArguments[varargIndex] = newVarargArray;
|
||||
return newArguments;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -188,8 +188,8 @@ class CglibAopProxy implements AopProxy, Serializable {
|
||||
Enhancer enhancer = createEnhancer();
|
||||
if (classLoader != null) {
|
||||
enhancer.setClassLoader(classLoader);
|
||||
if (classLoader instanceof SmartClassLoader &&
|
||||
((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
|
||||
if (classLoader instanceof SmartClassLoader smartClassLoader &&
|
||||
smartClassLoader.isClassReloadable(proxySuperClass)) {
|
||||
enhancer.setUseCache(false);
|
||||
}
|
||||
}
|
||||
@@ -251,11 +251,10 @@ class CglibAopProxy implements AopProxy, Serializable {
|
||||
private void validateClassIfNecessary(Class<?> proxySuperClass, @Nullable ClassLoader proxyClassLoader) {
|
||||
if (!this.advised.isOptimize() && logger.isInfoEnabled()) {
|
||||
synchronized (validatedClasses) {
|
||||
if (!validatedClasses.containsKey(proxySuperClass)) {
|
||||
doValidateClass(proxySuperClass, proxyClassLoader,
|
||||
ClassUtils.getAllInterfacesForClassAsSet(proxySuperClass));
|
||||
validatedClasses.put(proxySuperClass, Boolean.TRUE);
|
||||
}
|
||||
validatedClasses.computeIfAbsent(proxySuperClass, clazz -> {
|
||||
doValidateClass(clazz, proxyClassLoader, ClassUtils.getAllInterfacesForClassAsSet(clazz));
|
||||
return Boolean.TRUE;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -365,8 +364,8 @@ class CglibAopProxy implements AopProxy, Serializable {
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
return (this == other || (other instanceof CglibAopProxy &&
|
||||
AopProxyUtils.equalsInProxy(this.advised, ((CglibAopProxy) other).advised)));
|
||||
return (this == other || (other instanceof CglibAopProxy cglibAopProxy &&
|
||||
AopProxyUtils.equalsInProxy(this.advised, cglibAopProxy.advised)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -590,12 +589,12 @@ class CglibAopProxy implements AopProxy, Serializable {
|
||||
if (proxy == other) {
|
||||
return true;
|
||||
}
|
||||
if (other instanceof Factory) {
|
||||
Callback callback = ((Factory) other).getCallback(INVOKE_EQUALS);
|
||||
if (!(callback instanceof EqualsInterceptor)) {
|
||||
if (other instanceof Factory factory) {
|
||||
Callback callback = factory.getCallback(INVOKE_EQUALS);
|
||||
if (!(callback instanceof EqualsInterceptor equalsInterceptor)) {
|
||||
return false;
|
||||
}
|
||||
AdvisedSupport otherAdvised = ((EqualsInterceptor) callback).advised;
|
||||
AdvisedSupport otherAdvised = equalsInterceptor.advised;
|
||||
return AopProxyUtils.equalsInProxy(this.advised, otherAdvised);
|
||||
}
|
||||
else {
|
||||
@@ -702,8 +701,7 @@ class CglibAopProxy implements AopProxy, Serializable {
|
||||
// We need to create a method invocation...
|
||||
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
|
||||
}
|
||||
retVal = processReturnType(proxy, target, method, retVal);
|
||||
return retVal;
|
||||
return processReturnType(proxy, target, method, retVal);
|
||||
}
|
||||
finally {
|
||||
if (target != null && !targetSource.isStatic()) {
|
||||
@@ -719,8 +717,8 @@ class CglibAopProxy implements AopProxy, Serializable {
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
return (this == other ||
|
||||
(other instanceof DynamicAdvisedInterceptor &&
|
||||
this.advised.equals(((DynamicAdvisedInterceptor) other).advised)));
|
||||
(other instanceof DynamicAdvisedInterceptor dynamicAdvisedInterceptor &&
|
||||
this.advised.equals(dynamicAdvisedInterceptor.advised)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -962,9 +960,9 @@ class CglibAopProxy implements AopProxy, Serializable {
|
||||
private static boolean equalsPointcuts(Advisor a, Advisor b) {
|
||||
// If only one of the advisor (but not both) is PointcutAdvisor, then it is a mismatch.
|
||||
// Takes care of the situations where an IntroductionAdvisor is used (see SPR-3959).
|
||||
return (!(a instanceof PointcutAdvisor) ||
|
||||
(b instanceof PointcutAdvisor &&
|
||||
ObjectUtils.nullSafeEquals(((PointcutAdvisor) a).getPointcut(), ((PointcutAdvisor) b).getPointcut())));
|
||||
return (!(a instanceof PointcutAdvisor pointcutAdvisor1) ||
|
||||
(b instanceof PointcutAdvisor pointcutAdvisor2 &&
|
||||
ObjectUtils.nullSafeEquals(pointcutAdvisor1.getPointcut(), pointcutAdvisor2.getPointcut())));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user