Compare commits

..

1 Commits

Author SHA1 Message Date
Brian Clozel 02cdd3615e Release v7.0.3 2026-01-15 08:49:24 +01:00
295 changed files with 2117 additions and 6336 deletions
@@ -1,13 +1,6 @@
changelog:
repository: spring-projects/spring-framework
sections:
- title: ":warning: Attention Required"
labels:
- "for: upgrade-attention"
summary:
mode: "member-comment"
config:
prefix: "Attention Required:"
- title: ":star: New Features"
labels:
- "type: enhancement"
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
uses: ./.github/actions/print-jvm-thread-dumps
- name: Upload Build Reports
if: failure()
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v5
with:
name: build-reports
path: '**/build/reports/'
+1 -1
View File
@@ -64,7 +64,7 @@ jobs:
run: ./gradlew spring-framework-release-verification-tests:test
- name: Upload Build Reports on Failure
if: failure()
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v5
with:
name: build-reports
path: '**/build/reports/'
+1 -1
View File
@@ -6,7 +6,7 @@ plugins {
id 'com.github.bjornvester.xjc' version '1.8.2' apply false
id 'com.gradleup.shadow' version "9.2.2" apply false
id 'me.champeau.jmh' version '0.7.2' apply false
id 'io.spring.nullability' version '0.0.11' apply false
id 'io.spring.nullability' version '0.0.9' apply false
}
ext {
@@ -50,7 +50,7 @@ public class CheckstyleConventions {
project.getPlugins().apply(CheckstylePlugin.class);
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g"));
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
checkstyle.setToolVersion("13.2.0");
checkstyle.setToolVersion("13.0.0");
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 63 KiB

@@ -17,7 +17,6 @@
class="st5"
id="svg5499"
version="1.1"
font-family="Helvetica, Arial, sans-serif"
inkscape:version="0.91 r13725"
sodipodi:docname="mvc-splitted-contexts.svg"
style="font-size:12px;overflow:visible;color-interpolation-filters:sRGB;fill:none;fill-rule:evenodd;stroke-linecap:square;stroke-miterlimit:3"
@@ -37,7 +36,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path9164" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -48,7 +47,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path8836" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -59,7 +58,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path8520" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -70,7 +69,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path8216" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -81,7 +80,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path7924" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -92,7 +91,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path7644" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -103,7 +102,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path7375" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -114,7 +113,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path7119" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -125,7 +124,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path6875" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -136,7 +135,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path6643" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -147,7 +146,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path6423" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -158,7 +157,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path6215" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -169,7 +168,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path6019" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -180,7 +179,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path5835" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -191,7 +190,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path5663" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -202,7 +201,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path5503" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -213,7 +212,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path5355" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -224,7 +223,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path5219" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -235,7 +234,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path5095" /></marker><marker
inkscape:isstock="true"
style="overflow:visible;"
@@ -246,7 +245,7 @@
inkscape:stockid="Arrow2Mend"><path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
id="path4983" /></marker><marker
inkscape:stockid="Arrow2Mend"
orient="auto"
@@ -257,7 +256,7 @@
inkscape:isstock="true"
inkscape:collect="always"><path
id="path7394"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
transform="scale(0.6) rotate(180) translate(0,0)" /></marker><marker
inkscape:stockid="Arrow2Lend"
@@ -268,7 +267,7 @@
style="overflow:visible;"
inkscape:isstock="true"><path
id="path8123"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
transform="scale(1.1) rotate(180) translate(1,0)" /></marker><marker
inkscape:stockid="Arrow1Mend"
@@ -280,7 +279,7 @@
inkscape:isstock="true"><path
id="path8035"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
style="fill-rule:evenodd;stroke:#333333;stroke-width:1pt;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
transform="scale(0.4) rotate(180) translate(10,0)" /></marker><marker
inkscape:stockid="Arrow1Mend"
orient="auto"
@@ -291,7 +290,7 @@
inkscape:isstock="true"><path
id="path7959"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
style="fill-rule:evenodd;stroke:#333333;stroke-width:1pt;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
transform="scale(0.4) rotate(180) translate(10,0)" /></marker><marker
inkscape:stockid="Arrow1Mend"
orient="auto"
@@ -302,7 +301,7 @@
inkscape:isstock="true"><path
id="path7376"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
style="fill-rule:evenodd;stroke:#333333;stroke-width:1pt;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
transform="scale(0.4) rotate(180) translate(10,0)" /></marker><marker
inkscape:stockid="Arrow2Lend"
orient="auto"
@@ -312,7 +311,7 @@
style="overflow:visible;"
inkscape:isstock="true"><path
id="path7388"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#333333;stroke-opacity:1"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
transform="scale(1.1) rotate(180) translate(1,0)" /></marker><marker
inkscape:stockid="Arrow1Lend"
@@ -324,7 +323,7 @@
inkscape:isstock="true"><path
id="path7370"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
style="fill-rule:evenodd;stroke:#333333;stroke-width:1pt;stroke-opacity:1;fill:#333333;fill-opacity:1"
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
transform="scale(0.8) rotate(180) translate(12.5,0)" /></marker></defs><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
@@ -353,9 +352,9 @@
v:viewMarkup="false" /><style
type="text/css"
id="style5501"><![CDATA[
.st1 {fill:#969696;stroke:#333333;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
.st2 {fill:#F8F9FA;stroke:#333333;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
.st3 {fill:#333333;font-family:Helvetica, Arial, sans-serif;font-size:2.50001em;font-weight:bold}
.st1 {fill:#969696;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
.st2 {fill:#dde2cd;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
.st3 {fill:#000000;font-family:Arial;font-size:2.50001em;font-weight:bold}
.st4 {font-size:0.333333em;font-weight:normal}
.st5 {fill:none;fill-rule:evenodd;font-size:12;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
]]></style><g
@@ -364,22 +363,20 @@
v:groupContext="foregroundPage"
id="g5503"
transform="matrix(0.99998201,0,0,1.0824094,-40.812382,-98.908648)"><rect
style="fill:#F8F9FA;fill-opacity:1;stroke:#6db33f;stroke-width:2;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
style="fill:#dde2cd;fill-opacity:1;stroke:#000000;stroke-width:1.53790233;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
id="rect6599"
width="382.68423"
height="146.09897"
x="87.884865"
y="148.26482"
rx="8" /><v:userDefs><v:ud
y="148.26482" /><v:userDefs><v:ud
v:nameU="SchemeName"
v:val="VT4(Default)" /></v:userDefs><rect
style="fill:none;fill-opacity:1;stroke:#6db33f;stroke-width:2;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1.53790233;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
id="rect5725"
width="464.31128"
height="374.11411"
x="41.684383"
y="112.3262"
rx="8" /><title
y="112.3262" /><title
id="title5505">Page-1</title><v:pageProperties
v:drawingScale="0.0393701"
v:pageScale="0.0393701"
@@ -388,16 +385,15 @@
v:shadowOffsetY="-8.50394" /><v:layer
v:name="Connector"
v:index="0" /><rect
style="fill:#F8F9FA;fill-opacity:1;fill-rule:evenodd;stroke:#6db33f;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
style="fill:#dde2cd;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.53790233;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
id="rect5715"
width="322.8194"
height="43.63184"
x="119.95335"
y="-135.66222"
transform="scale(1,-1)"
rx="8" /><text
transform="scale(1,-1)" /><text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:30.7580471px;line-height:125%;font-family:sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="168.843"
y="124.32391"
id="text5717"
@@ -405,12 +401,12 @@
transform="scale(1.0403984,0.96117025)"><tspan
sodipodi:role="line"
id="tspan5719"
x="172.843"
x="168.843"
y="124.32391"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;fill:#333333">DispatcherServlet</tspan></text>
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:sans-serif;-inkscape-font-specification:sans-serif">DispatcherServlet</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="89.770851"
y="181.20923"
id="text6589"
@@ -418,12 +414,12 @@
transform="scale(1.0403984,0.96117025)"><tspan
sodipodi:role="line"
id="tspan6591"
x="106.770851"
x="89.770851"
y="181.20923"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;fill:#333333">Servlet WebApplicationContext</tspan></text>
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:sans-serif;-inkscape-font-specification:sans-serif">Servlet WebApplicationContext</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="260.00443"
y="198.41273"
id="text6593"
@@ -433,39 +429,36 @@
id="tspan6595"
x="260.00443"
y="198.41273"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle;fill:#555555">(containing controllers, view resolvers,</tspan><tspan
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle">(containing controllers, view resolvers,</tspan><tspan
sodipodi:role="line"
x="260.00443"
y="212.83057"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle;fill:#555555"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle"
id="tspan6597">and other web-related beans)</tspan></text>
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.86203903;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
id="rect6620"
width="82.040657"
height="36.72575"
x="114.52653"
y="-259.43161"
transform="scale(1,-1)"
rx="4" /><rect
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
transform="scale(1,-1)" /><rect
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.89166164;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
id="rect6622"
width="87.843979"
height="36.697304"
x="223.39864"
y="-287.19809"
transform="scale(1,-1)"
rx="4" /><rect
transform="scale(1,-1)" /><rect
transform="scale(1,-1)"
y="-264.81918"
x="117.92834"
height="36.72575"
width="82.040657"
id="rect6614"
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
rx="4" /><text
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.86203903;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="121.24728"
y="260.14957"
id="text6616"
@@ -473,20 +466,20 @@
transform="scale(1.0403984,0.96117025)"><tspan
sodipodi:role="line"
id="tspan6618"
x="124.24728"
x="121.24728"
y="260.14957"
style="font-size:11.53426838px;fill:#333333">Controllers</tspan></text>
style="font-size:11.53426838px">Controllers</tspan></text>
<text
transform="scale(1.0403984,0.96117025)"
sodipodi:linespacing="125%"
id="text6624"
y="282.70709"
x="219.61203"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"><tspan
style="font-size:11.53426838px;fill:#333333"
style="font-size:11.53426838px"
y="282.70709"
x="221.61203"
x="219.61203"
id="tspan6626"
sodipodi:role="line">ViewResolver</tspan></text>
<rect
@@ -496,10 +489,9 @@
height="36.577778"
width="114.4539"
id="rect6628"
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
rx="4" /><text
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.0161339;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="327.51276"
y="255.28464"
id="text6630"
@@ -507,27 +499,26 @@
transform="scale(1.0403984,0.96117025)"><tspan
sodipodi:role="line"
id="tspan6632"
x="332.51276"
x="327.51276"
y="255.28464"
style="font-size:11.53426838px;fill:#333333">HandlerMapping</tspan></text>
style="font-size:11.53426838px">HandlerMapping</tspan></text>
<rect
y="338.69724"
x="87.803261"
height="121.5683"
width="382.84744"
id="rect6634"
style="fill:#F8F9FA;fill-opacity:1;stroke:#6db33f;stroke-width:2;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
rx="8" /><text
style="fill:#dde2cd;fill-opacity:1;stroke:#000000;stroke-width:1.53790233;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><text
transform="scale(1.0403984,0.96117025)"
sodipodi:linespacing="125%"
id="text6636"
y="376.61673"
x="108.61351"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"><tspan
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;fill:#333333"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:23.06853676px;font-family:sans-serif;-inkscape-font-specification:sans-serif"
y="376.61673"
x="113.61351"
x="108.61351"
id="tspan6638"
sodipodi:role="line">Root WebApplicationContext</tspan></text>
<text
@@ -536,10 +527,10 @@
id="text6640"
y="395.35812"
x="260.93863"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"><tspan
id="tspan6644"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle;fill:#555555"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle"
y="395.35812"
x="260.93863"
sodipodi:role="line">(containing middle-tier services, datasources, etc.)</tspan></text>
@@ -550,47 +541,43 @@
height="36.72575"
width="82.040657"
id="rect6648"
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
rx="4" /><rect
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.86203903;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><rect
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.86203903;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
id="rect6650"
width="82.040657"
height="36.72575"
x="167.73116"
y="-445.45563"
transform="scale(1,-1)"
rx="4" /><text
transform="scale(1,-1)" /><text
transform="scale(1.0403984,0.96117025)"
sodipodi:linespacing="125%"
id="text6652"
y="448.55054"
x="175.87148"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"><tspan
style="font-size:11.53426838px;fill:#333333"
style="font-size:11.53426838px"
y="448.55054"
x="178.87148"
x="175.87148"
id="tspan6654"
sodipodi:role="line">Services</tspan></text>
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.88435173;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
id="rect6656"
width="86.393044"
height="36.704323"
x="306.86328"
y="-439.60837"
transform="scale(1,-1)"
rx="4" /><rect
transform="scale(1,-1)" /><rect
transform="scale(1,-1)"
y="-444.99475"
x="310.26624"
height="36.701977"
width="86.876686"
id="rect6658"
style="fill:#ffffff;fill-opacity:1;stroke:#6db33f;stroke-width:1.5;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1"
rx="4" /><text
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.88679528;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1" /><text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="305.30771"
y="448.55054"
id="text6660"
@@ -598,20 +585,20 @@
transform="scale(1.0403984,0.96117025)"><tspan
sodipodi:role="line"
id="tspan6662"
x="308.30771"
x="305.30771"
y="448.55054"
style="font-size:11.53426838px;fill:#333333">Repositories</tspan></text>
style="font-size:11.53426838px">Repositories</tspan></text>
<path
style="fill:none;fill-rule:evenodd;stroke:#333333;stroke-width:0.76895118px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.76895118px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 265.33234,295.60379 c 0,42.65169 0,42.65169 0,0 z"
id="path7643"
inkscape:connector-curvature="0" /><path
style="fill:#333333;fill-opacity:1;fill-rule:evenodd;stroke:#333333;stroke-width:3.46028023;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow2Mend)"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:3.46028023;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow2Mend)"
d="m 270.43505,294.70585 c 0,39.4721 0,39.87903 0,39.87903"
id="path7645"
inkscape:connector-curvature="0" /><text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Helvetica, Arial, sans-serif;letter-spacing:0px;word-spacing:0px;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="font-style:normal;font-weight:normal;font-size:30.7580471px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="352.55331"
y="333.03622"
id="text4963"
@@ -620,6 +607,6 @@
sodipodi:role="line"
x="352.55331"
y="333.03622"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:Helvetica, Arial, sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle;fill:#555555"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.53426838px;font-family:sans-serif;-inkscape-font-specification:sans-serif;text-align:center;text-anchor:middle"
id="tspan4965">Delegates if no bean found</tspan></text>
</g></svg>

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 30 KiB

@@ -19,6 +19,10 @@ its behavior changes.
| Initialization of `SmartInitializingSingleton` beans.
| `beanName` the name of the bean.
| `spring.context.annotated-bean-reader.create`
| Creation of the `AnnotatedBeanDefinitionReader`.
|
| `spring.context.base-packages.scan`
| Scanning of base packages.
| `packages` array of base packages for scanning.
@@ -1,17 +1,16 @@
[[beans-standard-annotations]]
= Using JSR-330 Standard Annotations
= Using JSR 330 Standard Annotations
Spring offers support for JSR-330 standard _Dependency Injection_ annotations which are
available in the `jakarta.inject` package. These annotations may optionally be used as
alternatives to Spring annotations.
To use them, you need to have the relevant jar in your classpath. For example, the
`jakarta.inject` artifact is available in the standard Maven repository
(`https://repo.maven.apache.org/maven2/jakarta/inject/jakarta.inject-api/2.0.0/`),
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, you can add the following dependency to your `pom.xml` file.
If you use Maven, the `jakarta.inject` artifact is available in the standard Maven
repository (
https://repo.maven.apache.org/maven2/jakarta/inject/jakarta.inject-api/2.0.0/[https://repo.maven.apache.org/maven2/jakarta/inject/jakarta.inject-api/2.0.0/]).
You can add the following dependency to your file pom.xml:
[source,xml,indent=0,subs="verbatim,quotes"]
----
@@ -27,14 +26,13 @@ If you use Maven, you can add the following dependency to your `pom.xml` file.
[[beans-inject-named]]
== Dependency Injection with `@Inject` and `@Named`
Instead of using `@Autowired` for dependency injection, you may optionally choose to use
`@jakarta.inject.Inject` as follows.
Instead of `@Autowired`, you can use `@jakarta.inject.Inject` as follows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
[source,java,indent=0,subs="verbatim,quotes"]
----
import jakarta.inject.Inject;
@@ -56,7 +54,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
import jakarta.inject.Inject
@@ -74,19 +72,17 @@ Kotlin::
----
======
As with `@Autowired`, you can use `@Inject` at the field level, method level, and
constructor-argument level.
Furthermore, as an alternative to Spring's `ObjectProvider` mechanism, you may choose to
declare your injection point as a `jakarta.inject.Provider`, allowing for on-demand
access to beans of shorter scopes or lazy access to other beans through a
`Provider.get()` call. The following example offers a variant of the preceding example.
As with `@Autowired`, you can use `@Inject` at the field level, method level
and constructor-argument level. Furthermore, you may declare your injection point as a
`Provider`, allowing for on-demand access to beans of shorter scopes or lazy access to
other beans through a `Provider.get()` call. The following example offers a variant of the
preceding example:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
[source,java,indent=0,subs="verbatim,quotes"]
----
import jakarta.inject.Inject;
import jakarta.inject.Provider;
@@ -109,10 +105,9 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
import jakarta.inject.Inject
import jakarta.inject.Provider
class SimpleMovieLister {
@@ -128,15 +123,14 @@ Kotlin::
----
======
If you would like to use a qualified name for the dependency that should be injected, you
may choose to use the `@Named` annotation as an alternative to Spring's `@Qualifier`
support, as the following example shows.
If you would like to use a qualified name for the dependency that should be injected,
you should use the `@Named` annotation, as the following example shows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
[source,java,indent=0,subs="verbatim,quotes"]
----
import jakarta.inject.Inject;
import jakarta.inject.Named;
@@ -156,7 +150,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
import jakarta.inject.Inject
import jakarta.inject.Named
@@ -176,15 +170,12 @@ Kotlin::
======
As with `@Autowired`, `@Inject` can also be used with `java.util.Optional` or
`@Nullable`. This is even more applicable here, since `@Inject` does not have a
`required` attribute. The following examples show how to use `@Inject` with `Optional`,
`@Nullable`, and Kotlin's built-in support for nullable types.
`@Nullable`. This is even more applicable here, since `@Inject` does not have
a `required` attribute. The following pair of examples show how to use `@Inject` and
`@Nullable`:
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
[source,java,indent=0,subs="verbatim,quotes"]
----
import jakarta.inject.Inject;
import java.util.Optional;
public class SimpleMovieLister {
@Inject
@@ -198,11 +189,8 @@ As with `@Autowired`, `@Inject` can also be used with `java.util.Optional` or
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
[source,java,indent=0,subs="verbatim,quotes"]
----
import jakarta.inject.Inject;
import org.jspecify.annotations.Nullable;
public class SimpleMovieLister {
@Inject
@@ -214,10 +202,8 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
import jakarta.inject.Inject
class SimpleMovieLister {
@Inject
@@ -228,21 +214,21 @@ Kotlin::
[[beans-named]]
== `@Named`: Standard Equivalent to the `@Component` Annotation
== `@Named` and `@ManagedBean`: Standard Equivalents to the `@Component` Annotation
Instead of `@Component` or other Spring stereotype annotations, you may optionally choose
to use `@jakarta.inject.Named`, as the following example shows.
Instead of `@Component`, you can use `@jakarta.inject.Named` or `jakarta.annotation.ManagedBean`,
as the following example shows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
[source,java,indent=0,subs="verbatim,quotes"]
----
import jakarta.inject.Inject;
import jakarta.inject.Named;
@Named("movieListener")
@Named("movieListener") // @ManagedBean("movieListener") could be used as well
public class SimpleMovieLister {
private MovieFinder movieFinder;
@@ -258,12 +244,12 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
import jakarta.inject.Inject
import jakarta.inject.Named
@Named("movieListener")
@Named("movieListener") // @ManagedBean("movieListener") could be used as well
class SimpleMovieLister {
@Inject
@@ -274,15 +260,14 @@ Kotlin::
----
======
It is very common to use `@Component` or other Spring stereotype annotations without
specifying an explicit name for the component, and `@Named` can be used in a similar
fashion, as the following example shows.
It is very common to use `@Component` without specifying a name for the component.
`@Named` can be used in a similar fashion, as the following example shows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
[source,java,indent=0,subs="verbatim,quotes"]
----
import jakarta.inject.Inject;
import jakarta.inject.Named;
@@ -303,7 +288,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
import jakarta.inject.Inject
import jakarta.inject.Named
@@ -319,14 +304,14 @@ Kotlin::
----
======
When you use `@Named`, you can use component scanning in the exact same way as when you
use Spring annotations, as the following example shows.
When you use `@Named` or `@ManagedBean`, you can use component scanning in the
exact same way as when you use Spring annotations, as the following example shows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
[source,java,indent=0,subs="verbatim,quotes"]
----
@Configuration
@ComponentScan(basePackages = "org.example")
@@ -337,7 +322,7 @@ Java::
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@Configuration
@ComponentScan(basePackages = ["org.example"])
@@ -347,106 +332,55 @@ Kotlin::
----
======
NOTE: In contrast to `@Component`, the JSR-330 `@Named` annotation is not composable. You
should use Spring's stereotype model for building custom component annotations.
[TIP]
====
If you work with legacy systems that still use `@javax.inject.Named` or
`@javax.annotation.ManagedBean` for components (note the `javax` package namespace), you
can explicitly configure component scanning to include those annotation types, as shown
in the following example.
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",fold="-imports"]
----
@Configuration
@ComponentScan(
basePackages = "org.example",
includeFilters = @Filter({
javax.inject.Named.class,
javax.annotation.ManagedBean.class
})
)
public class AppConfig {
// ...
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",fold="-imports"]
----
@Configuration
@ComponentScan(
basePackages = ["org.example"],
includeFilters = [Filter([
javax.inject.Named::class,
javax.annotation.ManagedBean::class
])]
)
class AppConfig {
// ...
}
----
======
In addition, if you would like for the `value` attributes in `@javax.inject.Named` and
`@javax.annotation.ManagedBean` to be used as component names, you need to override the
`isStereotypeWithNameValue(...)` method in `AnnotationBeanNameGenerator` to add explicit
support for `javax.annotation.ManagedBean` and `javax.inject.Named` and register your
custom `AnnotationBeanNameGenerator` via the `nameGenerator` attribute in
`@ComponentScan`.
====
NOTE: In contrast to `@Component`, the JSR-330 `@Named` and the JSR-250 `@ManagedBean`
annotations are not composable. You should use Spring's stereotype model for building
custom component annotations.
[[beans-standard-annotations-limitations]]
== Limitations of JSR-330 Standard Annotations
When you work with JSR-330 standard annotations, you should know that some significant
features are not available, as the following table shows.
When you work with standard annotations, you should know that some significant
features are not available, as the following table shows:
[[annotations-comparison]]
.Spring component model versus JSR-330 variants
.Spring component model elements versus JSR-330 variants
|===
| Spring | JSR-330 | JSR-330 restrictions / comments
| Spring| jakarta.inject.*| jakarta.inject restrictions / comments
| `@Autowired`
| `@Inject`
| `@Inject` has no `required` attribute. Can be used with Java's `Optional` instead.
| @Autowired
| @Inject
| `@Inject` has no 'required' attribute. Can be used with Java 8's `Optional` instead.
| `@Component`
| `@Named`
| @Component
| @Named / @ManagedBean
| JSR-330 does not provide a composable model, only a way to identify named components.
| `@Scope("singleton")`
| `@Singleton`
| @Scope("singleton")
| @Singleton
| The JSR-330 default scope is like Spring's `prototype`. However, in order to keep it
consistent with Spring's general defaults, a JSR-330 bean declared in the Spring
container is a `singleton` by default. In order to use a scope other than `singleton`,
you should use Spring's `@Scope` annotation. `jakarta.inject` also provides a
`jakarta.inject.Scope` annotation; however, this one is only intended to be used
`jakarta.inject.Scope` annotation: however, this one is only intended to be used
for creating custom annotations.
| `@Qualifier`
| `@Qualifier` / `@Named`
| @Qualifier
| @Qualifier / @Named
| `jakarta.inject.Qualifier` is just a meta-annotation for building custom qualifiers.
Concrete `String` qualifiers (like Spring's `@Qualifier` with a value) can be associated
through `jakarta.inject.Named`.
| `@Value`
| @Value
| -
| no equivalent
| `@Lazy`
| @Lazy
| -
| no equivalent
| `ObjectFactory`
| `Provider`
| ObjectFactory
| Provider
| `jakarta.inject.Provider` is a direct alternative to Spring's `ObjectFactory`,
only with a shorter `get()` method name. It can also be used in combination with
Spring's `@Autowired` or with non-annotated constructors and setter methods.
@@ -14,7 +14,7 @@ As of Spring Framework 7.0, Spring requires Hibernate ORM 7.x for Spring's
`HibernateJpaVendorAdapter`.
The `org.springframework.orm.jpa.hibernate` package supersedes the former `orm.hibernate5`:
now for use with Hibernate ORM 7.x, tightly integrated with `HibernateJpaVendorAdapter`
now for use with Hibernate ORM 7.1+, tightly integrated with `HibernateJpaVendorAdapter`
as well as supporting Hibernate's native `SessionFactory.getCurrentSession()` style.
====
@@ -1,8 +1,8 @@
[[coroutines]]
= Coroutines
Kotlin {kotlin-docs}/coroutines-overview.html[Coroutines] are instances of
suspendable computations allowing to write non-blocking code in an imperative way. On language side,
Kotlin {kotlin-docs}/coroutines-overview.html[Coroutines] are Kotlin
lightweight threads allowing to write non-blocking code in an imperative way. On language side,
suspending functions provides an abstraction for asynchronous operations while on library side
{kotlin-github-org}/kotlinx.coroutines[kotlinx.coroutines] provides functions like
{kotlin-coroutines-api}/kotlinx-coroutines-core/kotlinx.coroutines/async.html[`async { }`]
@@ -5,7 +5,6 @@ The following annotations are supported when used in conjunction with the
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-extension[`SpringExtension`]
and JUnit Jupiter (that is, the programming model in JUnit):
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-springextensionconfig[`@SpringExtensionConfig`]
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-junit-jupiter-springjunitconfig[`@SpringJUnitConfig`]
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-junit-jupiter-springjunitwebconfig[`@SpringJUnitWebConfig`]
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-testconstructor[`@TestConstructor`]
@@ -15,47 +14,6 @@ and JUnit Jupiter (that is, the programming model in JUnit):
* xref:testing/annotations/integration-spring/annotation-disabledinaotmode.adoc[`@DisabledInAotMode`]
[[integration-testing-annotations-springextensionconfig]]
== `@SpringExtensionConfig`
`@SpringExtensionConfig` is a type-level annotation that can be used to configure the
behavior of the `SpringExtension`.
As of Spring Framework 7.0, the `SpringExtension` is configured to use a test-method
scoped `ExtensionContext`, which enables consistent dependency injection into fields and
constructors from the `ApplicationContext` for the current test method in a `@Nested`
test class hierarchy. However, if a third-party `TestExecutionListener` is not compatible
with the semantics associated with a test-method scoped extension context — or if a
developer wishes to switch to test-class scoped semantics — the `SpringExtension` can be
configured to use a test-class scoped `ExtensionContext` by annotating a top-level test
class with `@SpringExtensionConfig(useTestClassScopedExtensionContext = true)`.
[TIP]
====
If your top-level test class is configured to use JUnit Jupiter's
`@TestInstance(Lifecycle.PER_CLASS)` semantics, the `SpringExtension` will always use a
test-class scoped `ExtensionContext`, and there is no need to declare
`@SpringExtensionConfig(useTestClassScopedExtensionContext = true)`.
====
[NOTE]
====
This annotation is currently only applicable to `@Nested` test class hierarchies and
should be applied to the top-level enclosing class of a `@Nested` test class hierarchy.
Consequently, there is no need to declare this annotation on a test class that does not
contain `@Nested` test classes.
====
[NOTE]
====
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration[`@NestedTestConfiguration`]
does not apply to this annotation.
`@SpringExtensionConfig` will always be detected within a `@Nested` test class hierarchy,
effectively disregarding any `@NestedTestConfiguration(OVERRIDE)` declarations.
====
[[integration-testing-annotations-junit-jupiter-springjunitconfig]]
== `@SpringJUnitConfig`
@@ -229,7 +187,7 @@ default mode may be set via the
xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism.
The default mode may also be configured as a
https://docs.junit.org/current/running-tests/configuration-parameters.html[JUnit Platform configuration parameter].
https://docs.junit.org/current/user-guide/#running-tests-config-params[JUnit Platform configuration parameter].
If the `spring.test.constructor.autowire.mode` property is not set, test class
constructors will not be automatically autowired.
@@ -9,6 +9,7 @@ and can be used anywhere in the Spring Framework.
* `@Qualifier`
* `@Value`
* `@Resource` (jakarta.annotation) if JSR-250 is present
* `@ManagedBean` (jakarta.annotation) if JSR-250 is present
* `@Inject` (jakarta.inject) if JSR-330 is present
* `@Named` (jakarta.inject) if JSR-330 is present
* `@PersistenceContext` (jakarta.persistence) if JPA is present
@@ -25,7 +25,7 @@ TestNG:
* Dependency injection for test constructors, test methods, and test lifecycle callback
methods. See xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-di[Dependency
Injection with the `SpringExtension`] for further details.
* Powerful support for link:https://docs.junit.org/current/extensions/conditional-test-execution.html[conditional
* Powerful support for link:https://docs.junit.org/current/user-guide/#extensions-conditions[conditional
test execution] based on SpEL expressions, environment variables, system properties,
and so on. See the documentation for `@EnabledIf` and `@DisabledIf` in
xref:testing/annotations/integration-junit-jupiter.adoc[Spring JUnit Jupiter Testing Annotations]
@@ -163,7 +163,7 @@ for further details.
=== Dependency Injection with the `SpringExtension`
The `SpringExtension` implements the
link:https://docs.junit.org/current/extensions/parameter-resolution.html[`ParameterResolver`]
link:https://docs.junit.org/current/user-guide/#extensions-parameter-resolution[`ParameterResolver`]
extension API from JUnit Jupiter, which lets Spring provide dependency injection for test
constructors, test methods, and test lifecycle callback methods.
@@ -392,17 +392,6 @@ any of its subclasses and nested classes. Thus, you may annotate a top-level tes
with `@NestedTestConfiguration`, and that will apply to all of its nested test classes
recursively.
[NOTE]
====
As of Spring Framework 7.0, the `SpringExtension` uses a test-method scoped
`ExtensionContext` within `@Nested` test class hierarchies by default. However, the
`SpringExtension` can be configured to use a test-class scoped `ExtensionContext`.
See the documentation for
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-springextensionconfig[`@SpringExtensionConfig`]
for details.
====
[TIP]
====
If you are developing a component that integrates with the Spring TestContext Framework
@@ -39,9 +39,9 @@ required CORS response headers set.
In order to enable cross-origin requests (that is, the `Origin` header is present and
differs from the host of the request), you need to have some explicitly declared CORS
configuration. If no matching CORS configuration is found, no CORS headers are added to
the responses to preflight, simple and actual CORS requests and, consequently, browsers
reject them.
configuration. If no matching CORS configuration is found, preflight requests are
rejected. No CORS headers are added to the responses of simple and actual CORS requests
and, consequently, browsers reject them.
Each `HandlerMapping` can be
{spring-framework-api}/web/reactive/handler/AbstractHandlerMapping.html#setCorsConfigurations(java.util.Map)[configured]
@@ -32,9 +32,9 @@ any `@RequestMapping` method to render an RFC 9457 response. This is processed a
- The `status` property of `ProblemDetail` determines the HTTP status.
- The `instance` property of `ProblemDetail` is set from the current URL path, if not
already set.
- The Jackson JSON and XML message converters use "application/problem+json" or
"application/problem+xml" respectively as the producible media types for `ProblemDetail`
to ensure they are favored for content negotiation.
- For content negotiation, the Jackson `HttpMessageConverter` prefers
"application/problem+json" over "application/json" when rendering a `ProblemDetail`,
and also falls back on it if no compatible media type is found.
To enable RFC 9457 responses for Spring WebFlux exceptions and for any
`ErrorResponseException`, extend `ResponseEntityExceptionHandler` and declare it as an
@@ -381,23 +381,16 @@ See the section on xref:web/webflux-cors.adoc[CORS] and the xref:web/webflux-cor
You may want your controller endpoints to match routes with or without a trailing slash in the URL path.
For example, both "GET /home" and "GET /home/" should be handled by a controller method annotated with `@GetMapping("/home")`.
Spring provides `UrlHandlerFilter` that removes the trailing slash from URL paths to ensure a consistent view of paths with or without a trailing slash.
This is important to avoid a mismatch between URL-based authorization decisions and web framework request mappings.
The filter can remove the trailing slash in one of a couple of ways:
Adding trailing slash variants to all mapping declarations is not the best way to handle this use case.
The `UrlHandlerFilter` web filter has been designed for this purpose. It can be configured to:
* respond with an HTTP redirect status that sends clients to the same path without a trailing slash.
* mutate the request to remove the trailing slash.
* respond with an HTTP redirect status when receiving URLs with trailing slashes, sending browsers to the non-trailing slash URL variant.
* mutate the request to act as if the request was sent without a trailing slash and continue the processing of the request.
Here is how you can instantiate and configure a `UrlHandlerFilter` for a blog application:
include-code::./UrlHandlerFilterConfiguration[tag=config,indent=0]
Keep in mind the following:
- the root path `"/"` is excluded from trailing slash handling.
- `@RequestMapping("/")` adds a trailing slash to a type-level mapping, and therefore will
not map when trailing slash handling applies; use `@RequestMapping` (no path attribute) instead.
[[webflux-exception-handler]]
== Exceptions
@@ -66,9 +66,9 @@ required CORS response headers set.
In order to enable cross-origin requests (that is, the `Origin` header is present and
differs from the host of the request), you need to have some explicitly declared CORS
configuration. If no matching CORS configuration is found, no CORS headers are added to
the responses to preflight, simple and actual CORS requests and, consequently, browsers
reject them.
configuration. If no matching CORS configuration is found, preflight requests are
rejected. No CORS headers are added to the responses of simple and actual CORS requests
and, consequently, browsers reject them.
Each `HandlerMapping` can be
{spring-framework-api}/web/servlet/handler/AbstractHandlerMapping.html#setCorsConfigurations(java.util.Map)[configured]
@@ -836,6 +836,7 @@ Java::
[source,java,indent=0,subs="verbatim,quotes"]
----
ClassPathResource index = new ClassPathResource("static/index.html");
List<String> extensions = List.of("js", "css", "ico", "png", "jpg", "gif");
RequestPredicate spaPredicate = path("/api/**").or(path("/error")).negate();
RouterFunction<ServerResponse> redirectToIndex = route()
.resource(spaPredicate, index)
@@ -120,26 +120,17 @@ See the sections on xref:web/webmvc-cors.adoc[CORS] and the xref:web/webmvc-cors
== URL Handler
[.small]#xref:web/webflux/reactive-spring.adoc#filters.url-handler[See equivalent in the Reactive stack]#
You may want your controller endpoints to match routes with or without a trailing slash in the URL path.
For example, both "GET /home" and "GET /home/" should be handled by a controller method annotated with `@GetMapping("/home")`.
In previous Spring Framework versions, Spring MVC could be configured to ignore trailing slashes in URL paths
when mapping incoming requests on controller methods. This means that sending a "GET /home/" request would be
handled by a controller method annotated with `@GetMapping("/home")`.
Spring provides `UrlHandlerFilter` that removes the trailing slash from URL paths to ensure a consistent view of paths with or without a trailing slash.
This is important to avoid a mismatch between URL-based authorization decisions and web framework request mappings.
The filter can remove the trailing slash in one of a couple of ways:
This option was deprecated in 6.0 and removed in 7.0, but applications are still expected to handle such
requests in a safe way. The `UrlHandlerFilter` Servlet filter has been designed for this purpose.
It can be configured to:
* respond with an HTTP redirect status that sends clients to the same path without a trailing slash.
* wrap the request to remove the trailing slash.
NOTE: Historically Spring MVC supported trailing slash matching of URL paths.
This capability was deprecated in 6.0 for security reasons and removed in 7.0 with
`UrlHandlerFilter` providing a safer alternative.
* respond with an HTTP redirect status when receiving URLs with trailing slashes, sending browsers to the non-trailing slash URL variant.
* wrap the request to act as if the request was sent without a trailing slash and continue the processing of the request.
Here is how you can instantiate and configure a `UrlHandlerFilter` for a blog application:
include-code::./UrlHandlerFilterConfiguration[tag=config,indent=0]
Keep in mind the following:
- the root path `"/"` is excluded from trailing slash handling.
- `@RequestMapping("/")` adds a trailing slash to a type-level mapping, and therefore will
not map when trailing slash handling applies; use `@RequestMapping` (no path attribute) instead.
@@ -45,7 +45,7 @@ By default, this converter supports `text/xml` and `application/xml`.
| `JacksonJsonHttpMessageConverter`
| An `HttpMessageConverter` implementation that can read and write JSON by using Jackson's `JsonMapper`.
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 `JsonMapper` through the `JsonMapper` or `JsonMapper.Builder` constructor parameters.
When you need further control (for cases where custom JSON serializers/deserializers need to be provided for specific types), you can inject a custom `JsonMapper` through the `JsonMapper` or `JsonMapper.Builder ` constructor parameters.
By default, this converter supports `application/json`. This requires the `tools.jackson.core:jackson-databind` dependency.
| `JacksonXmlHttpMessageConverter`
@@ -32,9 +32,9 @@ any `@RequestMapping` method to render an RFC 9457 response. This is processed a
- The `status` property of `ProblemDetail` determines the HTTP status.
- The `instance` property of `ProblemDetail` is set from the current URL path, if not
already set.
- The Jackson JSON and XML codecs use "application/problem+json" or
"application/problem+xml" respectively as the producible media types for `ProblemDetail`
to ensure they are favored for content negotiation.
- For content negotiation, the Jackson `HttpMessageConverter` prefers
"application/problem+json" over "application/json" when rendering a `ProblemDetail`,
and also falls back on it if no compatible media type is found.
To enable RFC 9457 responses for Spring MVC exceptions and for any
`ErrorResponseException`, extend `ResponseEntityExceptionHandler` and declare it as an
@@ -14,12 +14,55 @@ In turn, the `DispatcherServlet` uses Spring configuration to discover
the delegate components it needs for request mapping, view resolution, exception
handling, xref:web/webmvc/mvc-servlet/special-bean-types.adoc[and more].
The following example shows the programmatic registration and initialization of
The following example of the Java configuration registers and initializes
the `DispatcherServlet`, which is auto-detected by the Servlet container
(see xref:web/webmvc/mvc-servlet/container-config.adoc[Servlet Config]), and the
equivalent `web.xml`:
(see xref:web/webmvc/mvc-servlet/container-config.adoc[Servlet Config]):
include-code::./MyWebApplicationInitializer[tag=snippet,indent=0]
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) {
// Load Spring web application configuration
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(AppConfig.class);
// Create and register the DispatcherServlet
DispatcherServlet servlet = new DispatcherServlet(context);
ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("/app/*");
}
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
class MyWebApplicationInitializer : WebApplicationInitializer {
override fun onStartup(servletContext: ServletContext) {
// Load Spring web application configuration
val context = AnnotationConfigWebApplicationContext()
context.register(AppConfig::class.java)
// Create and register the DispatcherServlet
val servlet = DispatcherServlet(context)
val registration = servletContext.addServlet("app", servlet)
registration.setLoadOnStartup(1)
registration.addMapping("/app/*")
}
}
----
======
NOTE: In addition to using the ServletContext API directly, you can also extend
`AbstractAnnotationConfigDispatcherServletInitializer` and override specific methods
@@ -30,6 +73,39 @@ alternative to `AnnotationConfigWebApplicationContext`. See the
{spring-framework-api}/web/context/support/GenericWebApplicationContext.html[`GenericWebApplicationContext`]
javadoc for details.
The following example of `web.xml` configuration registers and initializes the `DispatcherServlet`:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<web-app>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app-context.xml</param-value>
</context-param>
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
----
NOTE: Spring Boot follows a different initialization sequence. Rather than hooking into
the lifecycle of the Servlet container, Spring Boot uses Spring configuration to
bootstrap itself and the embedded Servlet container. `Filter` and `Servlet` declarations
@@ -5,7 +5,48 @@ In a Servlet environment, you have the option of configuring the Servlet contain
programmatically as an alternative or in combination with a `web.xml` file.
The following example registers a `DispatcherServlet`:
include-code::./MyWebApplicationInitializer[tag=snippet,indent=0]
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
import org.springframework.web.WebApplicationInitializer;
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext container) {
XmlWebApplicationContext appContext = new XmlWebApplicationContext();
appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet(appContext));
registration.setLoadOnStartup(1);
registration.addMapping("/");
}
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
import org.springframework.web.WebApplicationInitializer
class MyWebApplicationInitializer : WebApplicationInitializer {
override fun onStartup(container: ServletContext) {
val appContext = XmlWebApplicationContext()
appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml")
val registration = container.addServlet("dispatcher", DispatcherServlet(appContext))
registration.setLoadOnStartup(1)
registration.addMapping("/")
}
}
----
======
`WebApplicationInitializer` is an interface provided by Spring MVC that ensures your
implementation is detected and automatically used to initialize any Servlet 3 container.
@@ -14,21 +55,144 @@ An abstract base class implementation of `WebApplicationInitializer` named
`DispatcherServlet` by overriding methods to specify the servlet mapping and the
location of the `DispatcherServlet` configuration.
This is recommended for applications that use programmatic Spring configuration, as the
This is recommended for applications that use Java-based Spring configuration, as the
following example shows:
include-code::./MyWebAppInitializer[tag=snippet,indent=0]
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { MyWebConfig.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
class MyWebAppInitializer : AbstractAnnotationConfigDispatcherServletInitializer() {
override fun getRootConfigClasses(): Array<Class<*>>? {
return null
}
override fun getServletConfigClasses(): Array<Class<*>>? {
return arrayOf(MyWebConfig::class.java)
}
override fun getServletMappings(): Array<String> {
return arrayOf("/")
}
}
----
======
If you use XML-based Spring configuration, you should extend directly from
`AbstractDispatcherServletInitializer`, as the following example shows:
include-code::./MyXmlDispatcherServletInitializer[tag=snippet,indent=0]
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {
@Override
protected WebApplicationContext createRootApplicationContext() {
return null;
}
@Override
protected WebApplicationContext createServletApplicationContext() {
XmlWebApplicationContext cxt = new XmlWebApplicationContext();
cxt.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
return cxt;
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
class MyWebAppInitializer : AbstractDispatcherServletInitializer() {
override fun createRootApplicationContext(): WebApplicationContext? {
return null
}
override fun createServletApplicationContext(): WebApplicationContext {
return XmlWebApplicationContext().apply {
setConfigLocation("/WEB-INF/spring/dispatcher-config.xml")
}
}
override fun getServletMappings(): Array<String> {
return arrayOf("/")
}
}
----
======
`AbstractDispatcherServletInitializer` also provides a convenient way to add `Filter`
instances and have them be automatically mapped to the `DispatcherServlet`, as the
following example shows:
include-code::./MyFilterDispatcherServletInitializer[tag=snippet,indent=0]
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {
// ...
@Override
protected Filter[] getServletFilters() {
return new Filter[] {
new HiddenHttpMethodFilter(), new CharacterEncodingFilter() };
}
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
class MyWebAppInitializer : AbstractDispatcherServletInitializer() {
// ...
override fun getServletFilters(): Array<Filter> {
return arrayOf(HiddenHttpMethodFilter(), CharacterEncodingFilter())
}
}
----
======
Each filter is added with a default name based on its concrete type and automatically
mapped to the `DispatcherServlet`.
@@ -20,15 +20,91 @@ are effectively inherited and can be overridden (that is, re-declared) in the Se
child `WebApplicationContext`, which typically contains beans local to the given `Servlet`.
The following image shows this relationship:
image::mvc-context-hierarchy.png[width=60%,align="center"]
image::mvc-context-hierarchy.png[]
The following example configures a `WebApplicationContext` hierarchy, and the equivalent `web.xml`:
The following example configures a `WebApplicationContext` hierarchy:
include-code::./MyWebAppInitializer[tag=snippet,indent=0]
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { RootConfig.class };
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { App1Config.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/app1/*" };
}
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
class MyWebAppInitializer : AbstractAnnotationConfigDispatcherServletInitializer() {
override fun getRootConfigClasses(): Array<Class<*>> {
return arrayOf(RootConfig::class.java)
}
override fun getServletConfigClasses(): Array<Class<*>> {
return arrayOf(App1Config::class.java)
}
override fun getServletMappings(): Array<String> {
return arrayOf("/app1/*")
}
}
----
======
TIP: If an application context hierarchy is not required, applications can return all
configuration through `getRootConfigClasses()` and `null` from `getServletConfigClasses()`.
The following example shows the `web.xml` equivalent:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<web-app>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/root-context.xml</param-value>
</context-param>
<servlet>
<servlet-name>app1</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app1-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app1</servlet-name>
<url-pattern>/app1/*</url-pattern>
</servlet-mapping>
</web-app>
----
TIP: If an application context hierarchy is not required, applications may configure a
"`root`" context only and leave the `contextConfigLocation` Servlet parameter empty.
@@ -74,7 +74,42 @@ Servlet container makes an ERROR dispatch within the container to the configured
to a `@Controller`, which could be implemented to return an error view name with a model
or to render a JSON response, as the following example shows:
include-code::./ErrorController[tag=snippet,indent=0]
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@RestController
public class ErrorController {
@RequestMapping(path = "/error")
public Map<String, Object> handle(HttpServletRequest request) {
Map<String, Object> map = new HashMap<>();
map.put("status", request.getAttribute("jakarta.servlet.error.status_code"));
map.put("reason", request.getAttribute("jakarta.servlet.error.message"));
return map;
}
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@RestController
class ErrorController {
@RequestMapping(path = ["/error"])
fun handle(request: HttpServletRequest): Map<String, Any> {
val map = HashMap<String, Any>()
map["status"] = request.getAttribute("jakarta.servlet.error.status_code")
map["reason"] = request.getAttribute("jakarta.servlet.error.message")
return map
}
}
----
======
TIP: The Servlet API does not provide a way to create error page mappings in Java. You can,
however, use both a `WebApplicationInitializer` and a minimal `web.xml`.
@@ -25,7 +25,62 @@ through the `enableLoggingRequestDetails` property on `DispatcherServlet`.
The following example shows how to do so by using Java configuration:
include-code::./MyInitializer[tag=snippet,indent=0]
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
public class MyInitializer
extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return ... ;
}
@Override
protected Class<?>[] getServletConfigClasses() {
return ... ;
}
@Override
protected String[] getServletMappings() {
return ... ;
}
@Override
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
registration.setInitParameter("enableLoggingRequestDetails", "true");
}
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
class MyInitializer : AbstractAnnotationConfigDispatcherServletInitializer() {
override fun getRootConfigClasses(): Array<Class<*>>? {
return ...
}
override fun getServletConfigClasses(): Array<Class<*>>? {
return ...
}
override fun getServletMappings(): Array<String> {
return ...
}
override fun customizeRegistration(registration: ServletRegistration.Dynamic) {
registration.setInitParameter("enableLoggingRequestDetails", "true")
}
}
----
======
@@ -26,7 +26,43 @@ To do so:
The following example shows how to set a `MultipartConfigElement` on the Servlet registration:
include-code::./AppInitializer[tag=snippet,indent=0]
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
// ...
@Override
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
// Optionally also set maxFileSize, maxRequestSize, fileSizeThreshold
registration.setMultipartConfig(new MultipartConfigElement("/tmp"));
}
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
class AppInitializer : AbstractAnnotationConfigDispatcherServletInitializer() {
// ...
override fun customizeRegistration(registration: ServletRegistration.Dynamic) {
// Optionally also set maxFileSize, maxRequestSize, fileSizeThreshold
registration.setMultipartConfig(MultipartConfigElement("/tmp"))
}
}
----
======
Once the Servlet multipart configuration is in place, you can add a bean of type
`StandardServletMultipartResolver` with a name of `multipartResolver`.
@@ -59,7 +59,7 @@ destination:/queue/trade
content-type:application/json
content-length:44
{"action":"BUY","ticker":"MMM","shares":44}^@
{"action":"BUY","ticker":"MMM","shares",44}^@
----
After the execution, the server can
+1 -1
View File
@@ -5,7 +5,7 @@
"@antora/collector-extension": "1.0.2",
"@asciidoctor/tabs": "1.0.0-beta.6",
"@springio/antora-extensions": "1.14.7",
"fast-xml-parser": "5.3.4",
"fast-xml-parser": "4.5.2",
"@springio/asciidoctor-extensions": "1.0.0-alpha.17"
}
}
+15 -12
View File
@@ -200,7 +200,6 @@
See the License for the specific language governing permissions and
limitations under the License.
=======================================================================
SPRING FRAMEWORK ${version} SUBCOMPONENTS:
@@ -213,7 +212,7 @@ code for these subcomponents is subject to the terms and
conditions of the following licenses.
>>> ASM 9.9.1 (org.ow2.asm:asm:9.9.1):
>>> ASM 9.1 (org.ow2.asm:asm:9.1, org.ow2.asm:asm-commons:9.1):
Copyright (c) 2000-2011 INRIA, France Telecom
All rights reserved.
@@ -250,28 +249,32 @@ Copyright (c) 1999-2009, OW2 Consortium <https://www.ow2.org/>
>>> CGLIB 3.3 (cglib:cglib:3.3):
Per the LICENSE file in the CGLIB distribution, CGLIB 3.3 is licensed
under the Apache License, version 2.0, the text of which is included above.
Per the LICENSE file in the CGLIB JAR distribution downloaded from
https://github.com/cglib/cglib/releases/download/RELEASE_3_3_0/cglib-3.3.0.jar,
CGLIB 3.3 is licensed under the Apache License, version 2.0, the text of which
is included above.
>>> JavaPoet 0.10.0 (com.palantir.javapoet:javapoet:0.10.0):
>>> JavaPoet 1.13.0 (com.squareup:javapoet:1.13.0):
Per the LICENSE file in the JavaPoet distribution, JavaPoet 0.10.0 is licensed
under the Apache License, version 2.0, the text of which is included above.
Per the LICENSE file in the JavaPoet JAR distribution downloaded from
https://github.com/square/javapoet/archive/refs/tags/javapoet-1.13.0.zip,
JavaPoet 1.13.0 is licensed under the Apache License, version 2.0, the text of
which is included above.
>>> Objenesis 3.5 (org.objenesis:objenesis:3.5):
>>> Objenesis 3.4 (org.objenesis:objenesis:3.4):
Per the LICENSE file in the Objenesis distribution downloaded from
http://objenesis.org/download.html, Objenesis 3.5 is licensed under the
Per the LICENSE file in the Objenesis ZIP distribution downloaded from
http://objenesis.org/download.html, Objenesis 3.4 is licensed under the
Apache License, version 2.0, the text of which is included above.
Per the NOTICE file in the Objenesis distribution downloaded from
Per the NOTICE file in the Objenesis ZIP distribution downloaded from
http://objenesis.org/download.html and corresponding to section 4d of the
Apache License, Version 2.0, in this case for Objenesis:
Objenesis
Copyright 2006-2026 Joe Walnes, Henri Tremblay, Leonardo Mesquita
Copyright 2006-2019 Joe Walnes, Henri Tremblay, Leonardo Mesquita
===============================================================================
@@ -1,23 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.docs.web.webmvc.mvcservlet;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
}
@@ -1,43 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.docs.web.webmvc.mvcservlet;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletRegistration;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
// tag::snippet[]
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) {
// Load Spring web application configuration
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(AppConfig.class);
// Create and register the DispatcherServlet
DispatcherServlet servlet = new DispatcherServlet(context);
ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("/app/*");
}
}
// end::snippet[]
@@ -1,38 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.docs.web.webmvc.mvcservlet.mvcanncustomerservletcontainererrorpage;
import java.util.HashMap;
import java.util.Map;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
// tag::snippet[]
@RestController
public class ErrorController {
@RequestMapping(path = "/error")
public Map<String, Object> handle(HttpServletRequest request) {
Map<String, Object> map = new HashMap<>();
map.put("status", request.getAttribute("jakarta.servlet.error.status_code"));
map.put("reason", request.getAttribute("jakarta.servlet.error.message"));
return map;
}
}
// end::snippet[]
@@ -1,51 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.docs.web.webmvc.mvcservlet.mvccontainerconfig;
import jakarta.servlet.Filter;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.servlet.support.AbstractDispatcherServletInitializer;
// tag::snippet[]
public class MyFilterDispatcherServletInitializer extends AbstractDispatcherServletInitializer {
@Override
protected Filter[] getServletFilters() {
return new Filter[] {
new HiddenHttpMethodFilter(), new CharacterEncodingFilter() };
}
// @fold:on
@Override
protected WebApplicationContext createServletApplicationContext() {
/**/return null;
}
@Override
protected String[] getServletMappings() {
/**/return new String[] { "/" };
}
@Override
protected WebApplicationContext createRootApplicationContext() {
/**/return null;
}
// @fold:off
}
// end::snippet[]
@@ -1,41 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.docs.web.webmvc.mvcservlet.mvccontainerconfig;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
// tag::snippet[]
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { MyWebConfig.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
// end::snippet[]
class MyWebConfig {}
@@ -1,38 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.docs.web.webmvc.mvcservlet.mvccontainerconfig;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletRegistration;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.XmlWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
// tag::snippet[]
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext container) {
XmlWebApplicationContext appContext = new XmlWebApplicationContext();
appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet(appContext));
registration.setLoadOnStartup(1);
registration.addMapping("/");
}
}
// end::snippet[]
@@ -1,43 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.docs.web.webmvc.mvcservlet.mvccontainerconfig;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.XmlWebApplicationContext;
import org.springframework.web.servlet.support.AbstractDispatcherServletInitializer;
// tag::snippet[]
public class MyXmlDispatcherServletInitializer extends AbstractDispatcherServletInitializer {
@Override
protected WebApplicationContext createRootApplicationContext() {
return null;
}
@Override
protected WebApplicationContext createServletApplicationContext() {
XmlWebApplicationContext cxt = new XmlWebApplicationContext();
cxt.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
return cxt;
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
// end::snippet[]
@@ -1,50 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.docs.web.webmvc.mvcservlet.mvcloggingsensitivedata;
import jakarta.servlet.ServletRegistration;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
// tag::snippet[]
public class MyInitializer
extends AbstractAnnotationConfigDispatcherServletInitializer {
// @fold:on
@Override
protected Class<?>[] getRootConfigClasses() {
/**/throw new UnsupportedOperationException();
}
@Override
protected Class<?>[] getServletConfigClasses() {
/**/throw new UnsupportedOperationException();
}
@Override
protected String[] getServletMappings() {
/**/throw new UnsupportedOperationException();
}
// @fold:off
@Override
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
registration.setInitParameter("enableLoggingRequestDetails", "true");
}
}
// end::snippet[]
@@ -1,52 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.docs.web.webmvc.mvcservlet.mvcmultipartresolverstandard;
import jakarta.servlet.MultipartConfigElement;
import jakarta.servlet.ServletRegistration;
import org.jspecify.annotations.Nullable;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
// tag::snippet[]
public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
// @fold:on
@Override
protected String[] getServletMappings() {
/**/throw new UnsupportedOperationException();
}
@Override
protected Class<?> @Nullable [] getRootConfigClasses() {
/**/throw new UnsupportedOperationException();
}
@Override
protected Class<?> @Nullable [] getServletConfigClasses() {
/**/throw new UnsupportedOperationException();
}
// @fold:off
@Override
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
// Optionally also set maxFileSize, maxRequestSize, fileSizeThreshold
registration.setMultipartConfig(new MultipartConfigElement("/tmp"));
}
}
// end::snippet[]
@@ -1,42 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.docs.web.webmvc.mvcservlet.mvcservletcontexthierarchy;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
// tag::snippet[]
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { RootConfig.class };
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { App1Config.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/app1/*" };
}
}
// end::snippet[]
class RootConfig {}
class App1Config {}
@@ -1,22 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.docs.web.webmvc.mvcservlet
import org.springframework.context.annotation.Configuration
@Configuration
class AppConfig
@@ -1,40 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.docs.web.webmvc.mvcservlet
import jakarta.servlet.ServletContext
import org.springframework.web.WebApplicationInitializer
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext
import org.springframework.web.servlet.DispatcherServlet
// tag::snippet[]
class MyWebApplicationInitializer : WebApplicationInitializer {
override fun onStartup(servletContext: ServletContext) {
// Load Spring web application configuration
val context = AnnotationConfigWebApplicationContext()
context.register(AppConfig::class.java)
// Create and register the DispatcherServlet
val servlet = DispatcherServlet(context)
val registration = servletContext.addServlet("app", servlet)
registration.setLoadOnStartup(1)
registration.addMapping("/app/*")
}
}
// end::snippet[]
@@ -1,35 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.docs.web.webmvc.mvcservlet.mvcanncustomerservletcontainererrorpage
import jakarta.servlet.http.HttpServletRequest
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
// tag::snippet[]
@RestController
class ErrorController {
@RequestMapping(path = ["/error"])
fun handle(request: HttpServletRequest): Map<String, Any> {
val map = HashMap<String, Any>()
map["status"] = request.getAttribute("jakarta.servlet.error.status_code")!!
map["reason"] = request.getAttribute("jakarta.servlet.error.message")!!
return map
}
}
// end::snippet[]
@@ -1,46 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.docs.web.webmvc.mvcservlet.mvccontainerconfig
import jakarta.servlet.Filter
import org.springframework.web.context.WebApplicationContext
import org.springframework.web.filter.CharacterEncodingFilter
import org.springframework.web.filter.HiddenHttpMethodFilter
import org.springframework.web.servlet.support.AbstractDispatcherServletInitializer
// tag::snippet[]
class MyFilterDispatcherServletInitializer : AbstractDispatcherServletInitializer() {
override fun getServletFilters(): Array<Filter> {
return arrayOf(HiddenHttpMethodFilter(), CharacterEncodingFilter())
}
// @fold:on
override fun createServletApplicationContext(): WebApplicationContext {
/**/TODO("Not yet implemented")
}
override fun getServletMappings(): Array<String> {
/**/TODO("Not yet implemented")
}
override fun createRootApplicationContext(): WebApplicationContext? {
/**/TODO("Not yet implemented")
}
// @fold:off
}
// end::snippet[]
@@ -1,38 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.docs.web.webmvc.mvcservlet.mvccontainerconfig
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer
// tag::snippet[]
class MyWebAppInitializer : AbstractAnnotationConfigDispatcherServletInitializer() {
override fun getRootConfigClasses(): Array<Class<*>>? {
return null
}
override fun getServletConfigClasses(): Array<Class<*>>? {
return arrayOf(MyWebConfig::class.java)
}
override fun getServletMappings(): Array<String> {
return arrayOf("/")
}
}
// end::snippet[]
class MyWebConfig
@@ -1,36 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.docs.web.webmvc.mvcservlet.mvccontainerconfig
import jakarta.servlet.ServletContext
import org.springframework.web.WebApplicationInitializer
import org.springframework.web.context.support.XmlWebApplicationContext
import org.springframework.web.servlet.DispatcherServlet
// tag::snippet[]
class MyWebApplicationInitializer : WebApplicationInitializer {
override fun onStartup(container: ServletContext) {
val appContext = XmlWebApplicationContext()
appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml")
val registration = container.addServlet("dispatcher", DispatcherServlet(appContext))
registration.setLoadOnStartup(1)
registration.addMapping("/")
}
}
// end::snippet[]
@@ -1,40 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.docs.web.webmvc.mvcservlet.mvccontainerconfig
import org.springframework.web.context.WebApplicationContext
import org.springframework.web.context.support.XmlWebApplicationContext
import org.springframework.web.servlet.support.AbstractDispatcherServletInitializer
// tag::snippet[]
class MyXmlDispatcherServletInitializer : AbstractDispatcherServletInitializer() {
override fun createRootApplicationContext(): WebApplicationContext? {
return null
}
override fun createServletApplicationContext(): WebApplicationContext {
return XmlWebApplicationContext().apply {
setConfigLocation("/WEB-INF/spring/dispatcher-config.xml")
}
}
override fun getServletMappings(): Array<String> {
return arrayOf("/")
}
}
// end::snippet[]
@@ -1,44 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.docs.web.webmvc.mvcservlet.mvcloggingsensitivedata
import jakarta.servlet.ServletRegistration
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer
// tag::snippet[]
class MyInitializer : AbstractAnnotationConfigDispatcherServletInitializer() {
// @fold:on
override fun getRootConfigClasses(): Array<Class<*>>? {
/**/TODO("Not yet implemented")
}
override fun getServletConfigClasses(): Array<Class<*>>? {
/**/TODO("Not yet implemented")
}
override fun getServletMappings(): Array<String> {
/**/TODO("Not yet implemented")
}
// @fold:off
override fun customizeRegistration(registration: ServletRegistration.Dynamic) {
registration.setInitParameter("enableLoggingRequestDetails", "true")
}
}
// end::snippet[]
@@ -1,48 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.docs.web.webmvc.mvcservlet.mvcmultipartresolverstandard
import jakarta.servlet.MultipartConfigElement
import jakarta.servlet.ServletRegistration
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer
// tag::snippet[]
class AppInitializer : AbstractAnnotationConfigDispatcherServletInitializer() {
// @fold:on
override fun getServletMappings(): Array<out String> {
/**/TODO("Not yet implemented")
}
override fun getRootConfigClasses(): Array<out Class<*>>? {
/**/TODO("Not yet implemented")
}
override fun getServletConfigClasses(): Array<out Class<*>>? {
/**/TODO("Not yet implemented")
}
// @fold:off
override fun customizeRegistration(registration: ServletRegistration.Dynamic) {
// Optionally also set maxFileSize, maxRequestSize, fileSizeThreshold
registration.setMultipartConfig(MultipartConfigElement("/tmp"))
}
}
// end::snippet[]
@@ -1,39 +0,0 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.docs.web.webmvc.mvcservlet.mvcservletcontexthierarchy
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer
// tag::snippet[]
class MyWebAppInitializer : AbstractAnnotationConfigDispatcherServletInitializer() {
override fun getRootConfigClasses(): Array<Class<*>> {
return arrayOf(RootConfig::class.java)
}
override fun getServletConfigClasses(): Array<Class<*>> {
return arrayOf(App1Config::class.java)
}
override fun getServletMappings(): Array<String> {
return arrayOf("/app1/*")
}
}
// end::snippet[]
class RootConfig
class App1Config
@@ -1,29 +0,0 @@
<!-- tag::snippet[] -->
<web-app>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app-context.xml</param-value>
</context-param>
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
<!-- end::snippet[] -->
@@ -1,29 +0,0 @@
<!-- tag::snippet[] -->
<web-app>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/root-context.xml</param-value>
</context-param>
<servlet>
<servlet-name>app1</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app1-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app1</servlet-name>
<url-pattern>/app1/*</url-pattern>
</servlet-mapping>
</web-app>
<!-- end::snippet[] -->
+21 -20
View File
@@ -7,21 +7,21 @@ javaPlatform {
}
dependencies {
api(platform("com.fasterxml.jackson:jackson-bom:2.20.2"))
api(platform("io.micrometer:micrometer-bom:1.16.3"))
api(platform("io.netty:netty-bom:4.2.10.Final"))
api(platform("io.projectreactor:reactor-bom:2025.0.3"))
api(platform("com.fasterxml.jackson:jackson-bom:2.20.1"))
api(platform("io.micrometer:micrometer-bom:1.16.2"))
api(platform("io.netty:netty-bom:4.2.9.Final"))
api(platform("io.projectreactor:reactor-bom:2025.0.2"))
api(platform("io.rsocket:rsocket-bom:1.1.5"))
api(platform("org.apache.groovy:groovy-bom:5.0.4"))
api(platform("org.apache.groovy:groovy-bom:5.0.3"))
api(platform("org.apache.logging.log4j:log4j-bom:2.25.3"))
api(platform("org.assertj:assertj-bom:3.27.7"))
api(platform("org.eclipse.jetty:jetty-bom:12.1.6"))
api(platform("org.eclipse.jetty.ee11:jetty-ee11-bom:12.1.6"))
api(platform("org.assertj:assertj-bom:3.27.6"))
api(platform("org.eclipse.jetty:jetty-bom:12.1.5"))
api(platform("org.eclipse.jetty.ee11:jetty-ee11-bom:12.1.5"))
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.10.2"))
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.9.0"))
api(platform("org.junit:junit-bom:6.0.2"))
api(platform("org.mockito:mockito-bom:5.21.0"))
api(platform("tools.jackson:jackson-bom:3.0.4"))
api(platform("tools.jackson:jackson-bom:3.0.3"))
constraints {
api("com.fasterxml:aalto-xml:1.3.4")
@@ -31,7 +31,7 @@ dependencies {
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.13.2")
api("com.google.protobuf:protobuf-java-util:4.33.4")
api("com.google.protobuf:protobuf-java-util:4.33.2")
api("com.h2database:h2:2.3.232")
api("com.jayway.jsonpath:json-path:2.10.0")
api("com.networknt:json-schema-validator:1.5.3")
@@ -44,7 +44,7 @@ dependencies {
api("com.sun.xml.bind:jaxb-xjc:3.0.2")
api("com.thoughtworks.qdox:qdox:2.2.0")
api("com.thoughtworks.xstream:xstream:1.4.21")
api("commons-io:commons-io:2.21.0")
api("commons-io:commons-io:2.15.0")
api("commons-logging:commons-logging:1.3.5")
api("de.bechte.junit:junit-hierarchicalcontextrunner:4.12.2")
api("io.mockk:mockk:1.14.5")
@@ -57,6 +57,7 @@ dependencies {
api("io.vavr:vavr:0.11.0")
api("jakarta.activation:jakarta.activation-api:2.1.3")
api("jakarta.annotation:jakarta.annotation-api:3.0.0")
api("jakarta.validation:jakarta.validation-api:3.1.0")
api("jakarta.ejb:jakarta.ejb-api:4.0.1")
api("jakarta.el:jakarta.el-api:6.0.1")
api("jakarta.enterprise.concurrent:jakarta.enterprise.concurrent-api:3.1.1")
@@ -95,11 +96,11 @@ dependencies {
api("org.apache.derby:derbytools:10.16.1.1")
api("org.apache.httpcomponents.client5:httpclient5:5.6")
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.4")
api("org.apache.poi:poi-ooxml:5.5.1")
api("org.apache.tomcat.embed:tomcat-embed-core:11.0.18")
api("org.apache.tomcat.embed:tomcat-embed-websocket:11.0.18")
api("org.apache.tomcat:tomcat-util:11.0.18")
api("org.apache.tomcat:tomcat-websocket:11.0.18")
api("org.apache.poi:poi-ooxml:5.2.5")
api("org.apache.tomcat.embed:tomcat-embed-core:11.0.15")
api("org.apache.tomcat.embed:tomcat-embed-websocket:11.0.15")
api("org.apache.tomcat:tomcat-util:11.0.15")
api("org.apache.tomcat:tomcat-websocket:11.0.15")
api("org.aspectj:aspectjrt:1.9.25")
api("org.aspectj:aspectjtools:1.9.25")
api("org.aspectj:aspectjweaver:1.9.25")
@@ -120,7 +121,7 @@ dependencies {
api("org.glassfish:jakarta.el:4.0.2")
api("org.graalvm.sdk:graal-sdk:22.3.1")
api("org.hamcrest:hamcrest:3.0")
api("org.hibernate.orm:hibernate-core:7.2.4.Final")
api("org.hibernate.orm:hibernate-core:7.2.0.Final")
api("org.hibernate.validator:hibernate-validator:9.1.0.Final")
api("org.hsqldb:hsqldb:2.7.4")
api("org.htmlunit:htmlunit:4.21.0")
@@ -134,10 +135,10 @@ dependencies {
api("org.python:jython-standalone:2.7.4")
api("org.quartz-scheduler:quartz:2.3.2")
api("org.reactivestreams:reactive-streams:1.0.4")
api("org.seleniumhq.selenium:htmlunit3-driver:4.40.0")
api("org.seleniumhq.selenium:selenium-java:4.40.0")
api("org.seleniumhq.selenium:htmlunit3-driver:4.39.0")
api("org.seleniumhq.selenium:selenium-java:4.39.0")
api("org.skyscreamer:jsonassert:1.5.3")
api("org.testng:testng:7.12.0")
api("org.testng:testng:7.11.0")
api("org.webjars:underscorejs:1.8.3")
api("org.webjars:webjars-locator-lite:1.1.0")
api("org.xmlunit:xmlunit-assertj:2.10.4")
+1 -1
View File
@@ -1,4 +1,4 @@
version=7.0.4
version=7.0.3
org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
@@ -17,6 +17,7 @@
package org.springframework.aop.framework.autoproxy
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import org.aopalliance.intercept.MethodInterceptor
import org.aopalliance.intercept.MethodInvocation
import org.aspectj.lang.ProceedingJoinPoint
@@ -72,37 +73,43 @@ class AspectJAutoProxyInterceptorKotlinIntegrationTests(
}
@Test
suspend fun `Multiple interceptors with suspending function`() {
fun `Multiple interceptors with suspending function`() {
assertThat(firstAdvisor.interceptor.invocations).isEmpty()
assertThat(secondAdvisor.interceptor.invocations).isEmpty()
val value = "Hello!"
assertThat(echo.suspendingEcho(value)).isEqualTo(value)
runBlocking {
assertThat(echo.suspendingEcho(value)).isEqualTo(value)
}
assertThat(firstAdvisor.interceptor.invocations).singleElement().matches { Mono::class.java.isAssignableFrom(it) }
assertThat(secondAdvisor.interceptor.invocations).singleElement().matches { Mono::class.java.isAssignableFrom(it) }
}
@Test // gh-33095
suspend fun `Aspect and reactive transactional with suspending function`() {
fun `Aspect and reactive transactional with suspending function`() {
assertThat(countingAspect.counter).isZero()
assertThat(reactiveTransactionManager.commits).isZero()
val value = "Hello!"
assertThat(echo.suspendingTransactionalEcho(value)).isEqualTo(value)
runBlocking {
assertThat(echo.suspendingTransactionalEcho(value)).isEqualTo(value)
}
assertThat(countingAspect.counter).`as`("aspect applied").isOne()
assertThat(reactiveTransactionManager.begun).isOne()
assertThat(reactiveTransactionManager.commits).`as`("transactional applied").isOne()
}
@Test // gh-33210
suspend fun `Aspect and cacheable with suspending function`() {
fun `Aspect and cacheable with suspending function`() {
assertThat(countingAspect.counter).isZero()
val value = "Hello!"
assertThat(echo.suspendingCacheableEcho(value)).isEqualTo("$value 0")
assertThat(echo.suspendingCacheableEcho(value)).isEqualTo("$value 0")
assertThat(echo.suspendingCacheableEcho(value)).isEqualTo("$value 0")
assertThat(countingAspect.counter).`as`("aspect applied once").isOne()
runBlocking {
assertThat(echo.suspendingCacheableEcho(value)).isEqualTo("$value 0")
assertThat(echo.suspendingCacheableEcho(value)).isEqualTo("$value 0")
assertThat(echo.suspendingCacheableEcho(value)).isEqualTo("$value 0")
assertThat(countingAspect.counter).`as`("aspect applied once").isOne()
assertThat(echo.suspendingCacheableEcho("$value bis")).isEqualTo("$value bis 1")
assertThat(echo.suspendingCacheableEcho("$value bis")).isEqualTo("$value bis 1")
assertThat(echo.suspendingCacheableEcho("$value bis")).isEqualTo("$value bis 1")
assertThat(echo.suspendingCacheableEcho("$value bis")).isEqualTo("$value bis 1")
}
assertThat(countingAspect.counter).`as`("aspect applied once per key").isEqualTo(2)
}
@@ -19,6 +19,7 @@ package org.springframework.aop.framework
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.runBlocking
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import reactor.core.publisher.Flux
@@ -36,31 +37,39 @@ class CoroutinesUtilsTests {
fun awaitSingleNonNullValue() {
val value = "foo"
val continuation = Continuation<Any>(CoroutineName("test")) { }
assertThat(CoroutinesUtils.awaitSingleOrNull(value, continuation)).isEqualTo(value)
runBlocking {
assertThat(CoroutinesUtils.awaitSingleOrNull(value, continuation)).isEqualTo(value)
}
}
@Test
fun awaitSingleNullValue() {
val value = null
val continuation = Continuation<Any>(CoroutineName("test")) { }
assertThat(CoroutinesUtils.awaitSingleOrNull(value, continuation)).isNull()
runBlocking {
assertThat(CoroutinesUtils.awaitSingleOrNull(value, continuation)).isNull()
}
}
@Test
fun awaitSingleMonoValue() {
val value = "foo"
val continuation = Continuation<Any>(CoroutineName("test")) { }
assertThat(CoroutinesUtils.awaitSingleOrNull(Mono.just(value), continuation)).isEqualTo(value)
runBlocking {
assertThat(CoroutinesUtils.awaitSingleOrNull(Mono.just(value), continuation)).isEqualTo(value)
}
}
@Test
@Suppress("UNCHECKED_CAST")
suspend fun flow() {
fun flow() {
val value1 = "foo"
val value2 = "bar"
val values = Flux.just(value1, value2)
val flow = CoroutinesUtils.asFlow(values) as Flow<String>
assertThat(flow.toList()).containsExactly(value1, value2)
runBlocking {
assertThat(flow.toList()).containsExactly(value1, value2)
}
}
}
@@ -46,7 +46,7 @@ class AopUtilsKotlinTests {
@Test
fun `Invoking suspending function on bridged method should return Mono`() {
val value = "foo"
val bridgedMethod = ReflectionUtils.findMethod(WithInterface::class.java, "handle", Any::class.java, Continuation::class.java)!!
val bridgedMethod = ReflectionUtils.findMethod(WithInterface::class.java, "handle", Object::class.java, Continuation::class.java)!!
val continuation = Continuation<Any>(CoroutineName("test")) { }
val result = AopUtils.invokeJoinpointUsingReflection(WithInterface(), bridgedMethod, arrayOf(value, continuation))
assertThat(result).isInstanceOfSatisfying(Mono::class.java) {
@@ -207,13 +207,6 @@ public interface BeanRegistry {
*/
Spec<T> prototype();
/**
* Configure this bean with a custom scope.
* @since 7.0.4
* @see BeanDefinition#setScope(String)
*/
Spec<T> scope(String scope);
/**
* Set the supplier to construct a bean instance.
* @see AbstractBeanDefinition#setInstanceSupplier(Supplier)
@@ -44,10 +44,9 @@ import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
/**
* {@link AutowireCandidateResolver} implementation that matches bean definition
* qualifiers against {@link #addQualifierType(Class) qualifier annotations} on
* the field or parameter to be autowired. Also supports suggested expression
* values through a {@link #setValueAnnotationType(Class) value annotation}.
* {@link AutowireCandidateResolver} implementation that matches bean definition qualifiers
* against {@link Qualifier qualifier annotations} on the field or parameter to be autowired.
* Also supports suggested expression values through a {@link Value value} annotation.
*
* <p>Also supports JSR-330's {@link jakarta.inject.Qualifier} annotation if available.
*
@@ -69,7 +68,7 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
/**
* Create a new {@code QualifierAnnotationAutowireCandidateResolver} for Spring's
* standard {@link Qualifier @Qualifier} annotation.
* standard {@link Qualifier} annotation.
* <p>Also supports JSR-330's {@link jakarta.inject.Qualifier} annotation if available.
*/
@SuppressWarnings("unchecked")
@@ -108,11 +107,11 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
/**
* Register the given type to be used as a qualifier when autowiring.
* <p>This identifies qualifier annotations for direct use (on fields,
* method parameters and constructor parameters) as well as
* meta-annotations that in turn identify actual qualifier annotations.
* method parameters and constructor parameters) as well as meta
* annotations that in turn identify actual qualifier annotations.
* <p>This implementation only supports annotations as qualifier types.
* The default is Spring's {@link Qualifier @Qualifier} annotation which serves
* as a qualifier for direct use and also as a meta-annotation.
* The default is Spring's {@link Qualifier} annotation which serves
* as a qualifier for direct use and also as a meta annotation.
* @param qualifierType the annotation type to register
*/
public void addQualifierType(Class<? extends Annotation> qualifierType) {
@@ -123,7 +122,7 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
* Set the 'value' annotation type, to be used on fields, method parameters
* and constructor parameters.
* <p>The default value annotation type is the Spring-provided
* {@link Value @Value} annotation.
* {@link Value} annotation.
* <p>This setter property exists so that developers can provide their own
* (non-Spring-specific) annotation type to indicate a default value
* expression for a specific argument.
@@ -113,6 +113,7 @@ class BeanDefinitionPropertiesCodeGenerator {
.createValueCodeGenerator(generatedMethods, customDelegates);
}
@SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/1128
CodeBlock generateCode(RootBeanDefinition beanDefinition) {
CodeBlock.Builder code = CodeBlock.builder();
addStatementForValue(code, beanDefinition, BeanDefinition::getScope,
@@ -304,37 +304,13 @@ public abstract class YamlProcessor {
* @since 4.1.3
*/
protected final Map<String, Object> getFlattenedMap(Map<String, Object> source) {
return getFlattenedMap(source, false, null);
}
/**
* Return a flattened version of the given map, recursively following any nested Map
* or Collection values. Entries from the resulting map retain the same order as the
* source. When called with the Map from a {@link MatchCallback} the result will
* contain the same values as the {@link MatchCallback} Properties.
* @param source the source map
* @param includeEmpty whether empty entries should be included in the result
* @param emptyValue the value used to represent an empty entry &mdash; for
* example, {@code null} or an empty {@code String}
* @return a flattened map
* @since 7.0.4
*/
protected final Map<String, Object> getFlattenedMap(Map<String, Object> source, boolean includeEmpty,
@Nullable Object emptyValue) {
Map<String, Object> result = new LinkedHashMap<>();
buildFlattenedMap(result, source, null, includeEmpty, emptyValue);
buildFlattenedMap(result, source, null);
return result;
}
@SuppressWarnings({"rawtypes", "unchecked"})
private void buildFlattenedMap(Map<String, Object> result, Map<String, Object> source, @Nullable String path,
boolean includeEmpty, @Nullable Object emptyValue) {
if (includeEmpty && source.isEmpty()) {
result.put(path, emptyValue);
return;
}
private void buildFlattenedMap(Map<String, Object> result, Map<String, Object> source, @Nullable String path) {
source.forEach((key, value) -> {
if (StringUtils.hasText(path)) {
if (key.startsWith("[")) {
@@ -349,7 +325,7 @@ public abstract class YamlProcessor {
}
else if (value instanceof Map map) {
// Need a compound key
buildFlattenedMap(result, map, key, includeEmpty, emptyValue);
buildFlattenedMap(result, map, key);
}
else if (value instanceof Collection collection) {
// Need a compound key
@@ -360,12 +336,12 @@ public abstract class YamlProcessor {
int count = 0;
for (Object object : collection) {
buildFlattenedMap(result, Collections.singletonMap(
"[" + (count++) + "]", object), key, includeEmpty, emptyValue);
"[" + (count++) + "]", object), key);
}
}
}
else {
result.put(key, (value != null ? value : (includeEmpty ? emptyValue : "")));
result.put(key, (value != null ? value : ""));
}
});
}
@@ -279,12 +279,6 @@ public class BeanRegistryAdapter implements BeanRegistry {
return this;
}
@Override
public Spec<T> scope(String scope) {
this.beanDefinition.setScope(scope);
return this;
}
@Override
public Spec<T> supplier(Function<SupplierContext, T> supplier) {
this.beanDefinition.setInstanceSupplier(() ->
@@ -38,7 +38,7 @@ import org.springframework.util.ClassUtils;
*
* <p>This is the base class for
* {@link org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver},
* providing an implementation for all non-annotation-based resolution steps at this level.
* providing an implementation all non-annotation-based resolution steps at this level.
*
* @author Juergen Hoeller
* @since 4.0
@@ -44,7 +44,8 @@ import static org.assertj.core.api.InstanceOfAssertFactories.set;
*/
class YamlProcessorTests {
private final TestYamlProcessor processor = new TestYamlProcessor();
private final YamlProcessor processor = new YamlProcessor() {
};
@Test
@@ -181,51 +182,8 @@ class YamlProcessorTests {
.withMessageContaining("Global tag is not allowed: tag:yaml.org,2002:java.net.URL");
}
@Test
void processAndFlattenWithoutIncludedNulls() {
setYaml("avalue: value\nanull: null\natilde: ~\nparent:\n anempty: {}\n");
Map<String, Object> flattened = this.processor.processAndFlatten(false, null);
assertThat(flattened).containsOnly(
entry("avalue", "value"),
entry("anull", ""),
entry("atilde", ""));
}
@Test
void processAndFlattenWithIncludedNulls() {
setYaml("avalue: value\nanull: null\natilde: ~\nparent:\n anempty: {}\n");
Map<String, Object> flattened = this.processor.processAndFlatten(true, null);
assertThat(flattened).containsOnly(
entry("avalue", "value"),
entry("anull", null),
entry("atilde", null),
entry("parent.anempty", null));
}
@Test
void processAndFlattenWithIncludedBlankString() {
setYaml("avalue: value\nanull: null\natilde: ~\nparent:\n anempty: {}\n");
Map<String, Object> flattened = this.processor.processAndFlatten(true, "");
assertThat(flattened).containsOnly(
entry("avalue", "value"),
entry("anull", ""),
entry("atilde", ""),
entry("parent.anempty", ""));
}
private void setYaml(String yaml) {
this.processor.setResources(new ByteArrayResource(yaml.getBytes()));
}
private static class TestYamlProcessor extends YamlProcessor {
Map<String, Object> processAndFlatten(boolean includeEmpty, Object emptyValue) {
Map<String, Object> flattened = new LinkedHashMap<>();
process((properties, map) -> flattened.putAll(getFlattenedMap(map, includeEmpty, emptyValue)));
return flattened;
}
}
}
@@ -186,14 +186,6 @@ public class BeanRegistryAdapterTests {
assertThat(beanDefinition.getScope()).isEqualTo(AbstractBeanDefinition.SCOPE_PROTOTYPE);
}
@Test
void customScope() {
BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, this.env, ScopeBeanRegistrar.class);
new ScopeBeanRegistrar().register(adapter, env);
BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition("foo");
assertThat(beanDefinition.getScope()).isEqualTo("custom");
}
@Test
void defaultSupplier() {
BeanRegistryAdapter adapter = new BeanRegistryAdapter(this.beanFactory, this.beanFactory, this.env, DefaultBeanRegistrar.class);
@@ -316,14 +308,6 @@ public class BeanRegistryAdapterTests {
}
}
private static class ScopeBeanRegistrar implements BeanRegistrar {
@Override
public void register(BeanRegistry registry, Environment env) {
registry.registerBean("foo", Foo.class, spec -> spec.scope("custom"));
}
}
private static class SupplierBeanRegistrar implements BeanRegistrar {
@Override
@@ -46,6 +46,7 @@ public abstract class AbstractJCacheConfiguration extends AbstractCachingConfigu
@Override
@SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1290
protected void useCachingConfigurer(CachingConfigurerSupplier cachingConfigurerSupplier) {
super.useCachingConfigurer(cachingConfigurerSupplier);
this.exceptionCacheResolver = cachingConfigurerSupplier.adapt(config -> {
@@ -20,11 +20,8 @@ import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicLong;
import org.junit.jupiter.api.AutoClose;
import org.junit.jupiter.api.Named;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedClass;
import org.junit.jupiter.params.provider.FieldSource;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -37,38 +34,21 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Named.named;
/**
* Tests for annotation-based caching methods that use reactive operators.
*
* @author Juergen Hoeller
* @author Sam Brannen
* @since 6.1
*/
@ParameterizedClass
@FieldSource("configClasses")
class CaffeineReactiveCachingTests {
static List<Named<Class<?>>> configClasses = List.of(
named(AsyncCacheModeConfig.class.getSimpleName(), AsyncCacheModeConfig.class),
named(AsyncCacheModeWithoutNullValuesConfig.class.getSimpleName(), AsyncCacheModeWithoutNullValuesConfig.class));
@ParameterizedTest
@ValueSource(classes = {AsyncCacheModeConfig.class, AsyncCacheModeConfig.class})
void cacheHitDetermination(Class<?> configClass) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(configClass, ReactiveCacheableService.class);
ReactiveCacheableService service = ctx.getBean(ReactiveCacheableService.class);
@AutoClose
private final AnnotationConfigApplicationContext ctx;
private final ReactiveCacheableService service;
CaffeineReactiveCachingTests(Class<?> configClass) {
this.ctx = new AnnotationConfigApplicationContext(configClass, ReactiveCacheableService.class);
this.service = ctx.getBean(ReactiveCacheableService.class);
}
@Test
void cacheHitDetermination() {
Object key = new Object();
Long r1 = service.cacheFuture(key).join();
@@ -122,10 +102,17 @@ class CaffeineReactiveCachingTests {
assertThat(r1).isNotNull();
assertThat(r1).isSameAs(r2).isSameAs(r3);
ctx.close();
}
@Test
void fluxCacheDoesntDependOnFirstRequest() {
@ParameterizedTest
@ValueSource(classes = {AsyncCacheModeConfig.class, AsyncCacheModeConfig.class})
void fluxCacheDoesntDependOnFirstRequest(Class<?> configClass) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(configClass, ReactiveCacheableService.class);
ReactiveCacheableService service = ctx.getBean(ReactiveCacheableService.class);
Object key = new Object();
List<Long> l1 = service.cacheFlux(key).take(1L, true).collectList().block();
@@ -137,6 +124,8 @@ class CaffeineReactiveCachingTests {
assertThat(l1).as("l1").containsExactly(first);
assertThat(l2).as("l2").containsExactly(first, 0L, -1L);
assertThat(l3).as("l3").containsExactly(first, 0L, -1L, -2L, -3L);
ctx.close();
}
@@ -94,6 +94,7 @@ public abstract class AbstractCachingConfiguration implements ImportAware {
/**
* Extract the configuration from the nominated {@link CachingConfigurer}.
*/
@SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/1128
protected void useCachingConfigurer(CachingConfigurerSupplier cachingConfigurerSupplier) {
this.cacheManager = cachingConfigurerSupplier.adapt(CachingConfigurer::cacheManager);
this.cacheResolver = cachingConfigurerSupplier.adapt(CachingConfigurer::cacheResolver);
@@ -999,7 +999,6 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
.addStatement("throw new $T(\"Failed to read metadata for '$L'\", ex)",
IllegalStateException.class, beanRegistrarEntry.getKey())
.endControlFlow();
generationContext.getRuntimeHints().resources().registerType(TypeReference.of(beanRegistrarEntry.getKey()));
}
code.addStatement("$L.register(new $T(($T)$L, $L, $L, $L.getClass(), $L), $L)", beanRegistrarName,
BeanRegistryAdapter.class, BeanDefinitionRegistry.class, BeanFactoryInitializationCode.BEAN_FACTORY_VARIABLE,
@@ -44,7 +44,6 @@ import org.springframework.core.annotation.AnnotationUtils;
* driven by the {@link Lazy} annotation in the {@code context.annotation} package.
*
* @author Juergen Hoeller
* @author Sam Brannen
* @since 4.0
*/
public class ContextAnnotationAutowireCandidateResolver extends QualifierAnnotationAutowireCandidateResolver {
@@ -61,12 +60,7 @@ public class ContextAnnotationAutowireCandidateResolver extends QualifierAnnotat
protected boolean isLazy(DependencyDescriptor descriptor) {
for (Annotation ann : descriptor.getAnnotations()) {
// Directly present?
if (ann instanceof Lazy lazy && lazy.value()) {
return true;
}
// Meta-present?
Lazy lazy = AnnotationUtils.findAnnotation(ann.annotationType(), Lazy.class);
Lazy lazy = AnnotationUtils.getAnnotation(ann, Lazy.class);
if (lazy != null && lazy.value()) {
return true;
}
@@ -75,7 +69,7 @@ public class ContextAnnotationAutowireCandidateResolver extends QualifierAnnotat
if (methodParam != null) {
Method method = methodParam.getMethod();
if (method == null || void.class == method.getReturnType()) {
Lazy lazy = AnnotationUtils.findAnnotation(methodParam.getAnnotatedElement(), Lazy.class);
Lazy lazy = AnnotationUtils.getAnnotation(methodParam.getAnnotatedElement(), Lazy.class);
if (lazy != null && lazy.value()) {
return true;
}
@@ -54,9 +54,6 @@ import java.lang.annotation.Target;
* for optional dependencies. For a programmatic equivalent, allowing for lazy references
* with more sophistication, consider {@link org.springframework.beans.factory.ObjectProvider}.
*
* <p>This annotation may be used as a <em>meta-annotation</em> to create custom
* <em>composed annotations</em>.
*
* @author Chris Beams
* @author Juergen Hoeller
* @since 3.0
@@ -27,7 +27,6 @@ import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@@ -1071,9 +1070,11 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
this.shutdownHook = new Thread(SHUTDOWN_HOOK_THREAD_NAME) {
@Override
public void run() {
if (!tryLockForShutdown()) {
if (isStartupShutdownThreadStuck()) {
active.set(false);
return;
}
startupShutdownLock.lock();
try {
doClose();
}
@@ -1086,30 +1087,6 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
}
}
/**
* Try to acquire the common startup/shutdown lock, backing out if
* the main startup/shutdown thread is stuck or on interruption.
* @see #isStartupShutdownThreadStuck()
*/
private boolean tryLockForShutdown() {
try {
while (!this.startupShutdownLock.tryLock(100, TimeUnit.MILLISECONDS)) {
if (!this.active.get() || this.closed.get()) {
return false;
}
if (isStartupShutdownThreadStuck()) {
this.active.set(false);
return false;
}
}
return true;
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
return false;
}
}
/**
* Determine whether an active startup/shutdown thread is currently stuck,
* for example, through a {@code System.exit} call in a user component.
@@ -1121,7 +1098,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
activeThread.interrupt();
try {
// Leave just a little bit of time for the interruption to show effect
Thread.sleep(10);
Thread.sleep(1);
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
@@ -1143,10 +1120,12 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
*/
@Override
public void close() {
if (!tryLockForShutdown()) {
if (isStartupShutdownThreadStuck()) {
this.active.set(false);
return;
}
this.startupShutdownLock.lock();
try {
this.startupShutdownThread = Thread.currentThread();
@@ -101,14 +101,14 @@ public class ConcurrencyLimitBeanPostProcessor extends AbstractBeanFactoryAwareA
interceptor = holder.methodInterceptors.get(method);
if (interceptor == null) {
boolean perMethod = false;
ConcurrencyLimit annotation = AnnotatedElementUtils.findMergedAnnotation(method, ConcurrencyLimit.class);
ConcurrencyLimit annotation = AnnotatedElementUtils.getMergedAnnotation(method, ConcurrencyLimit.class);
if (annotation != null) {
perMethod = true;
}
else {
interceptor = holder.classInterceptor;
if (interceptor == null) {
annotation = AnnotatedElementUtils.findMergedAnnotation(targetClass, ConcurrencyLimit.class);
annotation = AnnotatedElementUtils.getMergedAnnotation(targetClass, ConcurrencyLimit.class);
}
}
if (interceptor == null) {
@@ -117,10 +117,11 @@ public class ConcurrencyLimitBeanPostProcessor extends AbstractBeanFactoryAwareA
if (concurrencyLimit < -1) {
throw new IllegalStateException(annotation + " must be configured with a valid limit");
}
String name = (perMethod ? ClassUtils.getQualifiedMethodName(method) : targetClass.getName());
interceptor = (annotation.policy() == ConcurrencyLimit.ThrottlePolicy.REJECT ?
new RejectingConcurrencyThrottleInterceptor(concurrencyLimit, name, instance) :
new ResilienceConcurrencyThrottleInterceptor(concurrencyLimit, name, instance));
new RejectingConcurrencyThrottleInterceptor(concurrencyLimit,
(perMethod ? ClassUtils.getQualifiedMethodName(method) : targetClass.getName()),
instance) :
new ConcurrencyThrottleInterceptor(concurrencyLimit));
if (!perMethod) {
holder.classInterceptor = interceptor;
}
@@ -154,34 +155,22 @@ public class ConcurrencyLimitBeanPostProcessor extends AbstractBeanFactoryAwareA
}
private static class ResilienceConcurrencyThrottleInterceptor extends ConcurrencyThrottleInterceptor {
private static class RejectingConcurrencyThrottleInterceptor extends ConcurrencyThrottleInterceptor {
private final String identifier;
private final Object target;
public ResilienceConcurrencyThrottleInterceptor(int concurrencyLimit, String identifier, Object target) {
public RejectingConcurrencyThrottleInterceptor(int concurrencyLimit, String identifier, Object target) {
super(concurrencyLimit);
this.identifier = identifier;
this.target = target;
}
@Override
protected void onAccessRejected(String msg) {
throw new InvocationRejectedException(msg + " " + this.identifier, this.target);
}
}
private static class RejectingConcurrencyThrottleInterceptor extends ResilienceConcurrencyThrottleInterceptor {
public RejectingConcurrencyThrottleInterceptor(int concurrencyLimit, String identifier, Object target) {
super(concurrencyLimit, identifier, target);
}
@Override
protected void onLimitReached() {
onAccessRejected("Concurrency limit reached: " + getConcurrencyLimit() + " - not allowed to enter");
throw new InvocationRejectedException(
"Concurrency limit reached for " + this.identifier + ": " + getConcurrencyLimit(), this.target);
}
}
@@ -93,9 +93,9 @@ public class RetryAnnotationBeanPostProcessor extends AbstractBeanFactoryAwareAd
return retrySpec;
}
Retryable retryable = AnnotatedElementUtils.findMergedAnnotation(method, Retryable.class);
Retryable retryable = AnnotatedElementUtils.getMergedAnnotation(method, Retryable.class);
if (retryable == null) {
retryable = AnnotatedElementUtils.findMergedAnnotation(targetClass, Retryable.class);
retryable = AnnotatedElementUtils.getMergedAnnotation(targetClass, Retryable.class);
if (retryable == null) {
return null;
}
@@ -67,7 +67,7 @@ public abstract class AbstractAsyncConfiguration implements ImportAware {
* Collect any {@link AsyncConfigurer} beans through autowiring.
*/
@Autowired
@SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/1290
@SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/1128
void setConfigurers(ObjectProvider<AsyncConfigurer> configurers) {
SingletonSupplier<AsyncConfigurer> configurer = SingletonSupplier.ofNullable(() -> {
List<AsyncConfigurer> candidates = configurers.stream().toList();
@@ -41,9 +41,6 @@ import java.lang.annotation.Target;
* for a specific bean to begin with. Can also be used as a meta-annotation on a
* custom stereotype annotation or a custom group-specific validated annotation.
*
* <p>This annotation may be used as a <em>meta-annotation</em> to create custom
* <em>composed annotations</em>.
*
* @author Juergen Hoeller
* @since 3.1
* @see jakarta.validation.Validator#validate(Object, Class[])
@@ -17,18 +17,14 @@
package org.springframework.validation.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import org.jspecify.annotations.Nullable;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.annotation.AnnotationUtils;
/**
* Utility class for handling validation annotations.
*
* <p>Mainly for internal use within the framework.
* Mainly for internal use within the framework.
*
* @author Christoph Dreis
* @author Juergen Hoeller
@@ -38,11 +34,9 @@ public abstract class ValidationAnnotationUtils {
private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0];
/**
* Determine any validation hints for the given annotation.
* Determine any validation hints by the given annotation.
* <p>This implementation checks for Spring's
* {@link org.springframework.validation.annotation.Validated},
* {@code @jakarta.validation.Valid}, and custom annotations whose
@@ -63,7 +57,7 @@ public abstract class ValidationAnnotationUtils {
return EMPTY_OBJECT_ARRAY;
}
// Meta presence of @Validated ?
Validated validatedAnn = AnnotationUtils.findAnnotation(annotationType, Validated.class);
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
if (validatedAnn != null) {
return validatedAnn.value();
}
@@ -82,30 +76,4 @@ public abstract class ValidationAnnotationUtils {
return (hints instanceof Object[] objectHints ? objectHints : new Object[] {hints});
}
/**
* Determine the applicable validation groups from an
* {@link org.springframework.validation.annotation.Validated @Validated}
* annotation either on the method, or on the containing target class of
* the method, or for an AOP proxy without a target (with all behavior in
* advisors), also check on proxied interfaces.
* @since 7.0.4
*/
public static Class<?>[] determineValidationGroups(Object target, Method method) {
Validated validatedAnn = AnnotationUtils.findAnnotation(method, Validated.class);
if (validatedAnn == null) {
if (AopUtils.isAopProxy(target)) {
for (Class<?> type : AopProxyUtils.proxiedUserInterfaces(target)) {
validatedAnn = AnnotationUtils.findAnnotation(type, Validated.class);
if (validatedAnn != null) {
break;
}
}
}
else {
validatedAnn = AnnotationUtils.findAnnotation(target.getClass(), Validated.class);
}
}
return (validatedAnn != null ? validatedAnn.value() : EMPTY_CLASS_ARRAY);
}
}
@@ -261,7 +261,7 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
configuration = bootstrap.configure();
}
// Try Hibernate Validator's externalClassLoader(ClassLoader) method
// Try Hibernate Validator 5.2's externalClassLoader(ClassLoader) method
if (this.applicationContext != null) {
try {
Method eclMethod = configuration.getClass().getMethod("externalClassLoader", ClassLoader.class);
@@ -269,7 +269,7 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
ReflectionUtils.invokeMethod(eclMethod, configuration, this.applicationContext.getClassLoader());
}
catch (NoSuchMethodException ignored) {
// no Hibernate Validator or similar provider
// no Hibernate Validator 5.2+ or similar provider
}
}
@@ -370,45 +370,47 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
protected void postProcessConfiguration(Configuration<?> configuration) {
}
private ValidatorFactory obtainValidatorFactory() {
Assert.state(this.validatorFactory != null, "No target ValidatorFactory set");
return this.validatorFactory;
}
@Override
public Validator getValidator() {
return obtainValidatorFactory().getValidator();
Assert.state(this.validatorFactory != null, "No target ValidatorFactory set");
return this.validatorFactory.getValidator();
}
@Override
public ValidatorContext usingContext() {
return obtainValidatorFactory().usingContext();
Assert.state(this.validatorFactory != null, "No target ValidatorFactory set");
return this.validatorFactory.usingContext();
}
@Override
public MessageInterpolator getMessageInterpolator() {
return obtainValidatorFactory().getMessageInterpolator();
Assert.state(this.validatorFactory != null, "No target ValidatorFactory set");
return this.validatorFactory.getMessageInterpolator();
}
@Override
public TraversableResolver getTraversableResolver() {
return obtainValidatorFactory().getTraversableResolver();
Assert.state(this.validatorFactory != null, "No target ValidatorFactory set");
return this.validatorFactory.getTraversableResolver();
}
@Override
public ConstraintValidatorFactory getConstraintValidatorFactory() {
return obtainValidatorFactory().getConstraintValidatorFactory();
Assert.state(this.validatorFactory != null, "No target ValidatorFactory set");
return this.validatorFactory.getConstraintValidatorFactory();
}
@Override
public ParameterNameProvider getParameterNameProvider() {
return obtainValidatorFactory().getParameterNameProvider();
Assert.state(this.validatorFactory != null, "No target ValidatorFactory set");
return this.validatorFactory.getParameterNameProvider();
}
@Override
public ClockProvider getClockProvider() {
return obtainValidatorFactory().getClockProvider();
Assert.state(this.validatorFactory != null, "No target ValidatorFactory set");
return this.validatorFactory.getClockProvider();
}
@Override
@@ -419,7 +421,7 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
return super.unwrap(type);
}
catch (ValidationException ignored) {
// Trying ValidatorFactory unwrapping next
// we'll try ValidatorFactory unwrapping next
}
}
if (this.validatorFactory != null) {
@@ -26,7 +26,7 @@ import org.springframework.context.support.MessageSourceResourceBundle;
import org.springframework.util.Assert;
/**
* Implementation of Hibernate Validator's {@link ResourceBundleLocator} interface,
* Implementation of Hibernate Validator 4.3/5.x's {@link ResourceBundleLocator} interface,
* exposing a Spring {@link MessageSource} as localized {@link MessageSourceResourceBundle}.
*
* @author Juergen Hoeller
@@ -39,7 +39,6 @@ public class MessageSourceResourceBundleLocator implements ResourceBundleLocator
private final MessageSource messageSource;
/**
* Build a MessageSourceResourceBundleLocator for the given MessageSource.
* @param messageSource the Spring MessageSource to wrap
@@ -49,7 +48,6 @@ public class MessageSourceResourceBundleLocator implements ResourceBundleLocator
this.messageSource = messageSource;
}
@Override
public ResourceBundle getResourceBundle(Locale locale) {
return new MessageSourceResourceBundle(this.messageSource, locale);
@@ -40,6 +40,8 @@ import jakarta.validation.executable.ExecutableValidator;
import jakarta.validation.metadata.ConstraintDescriptor;
import org.jspecify.annotations.Nullable;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.core.BridgeMethodResolver;
@@ -48,6 +50,7 @@ import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.util.function.SingletonSupplier;
import org.springframework.validation.BeanPropertyBindingResult;
@@ -56,7 +59,6 @@ import org.springframework.validation.DefaultMessageCodesResolver;
import org.springframework.validation.Errors;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.annotation.Validated;
import org.springframework.validation.annotation.ValidationAnnotationUtils;
import org.springframework.validation.method.MethodValidationResult;
import org.springframework.validation.method.MethodValidator;
import org.springframework.validation.method.ParameterErrors;
@@ -211,14 +213,24 @@ public class MethodValidationAdapter implements MethodValidator {
* annotation on the method, or on the containing target class of the method,
* or for an AOP proxy without a target (with all behavior in advisors), also
* check on proxied interfaces.
* @deprecated in favor of
* {@link org.springframework.validation.annotation.ValidationAnnotationUtils#determineValidationGroups(Object, Method)}
*/
@SuppressWarnings("removal")
@Deprecated(since = "7.0.4", forRemoval = true)
@Override
public Class<?>[] determineValidationGroups(Object target, Method method) {
return ValidationAnnotationUtils.determineValidationGroups(target, method);
Validated validatedAnn = AnnotationUtils.findAnnotation(method, Validated.class);
if (validatedAnn == null) {
if (AopUtils.isAopProxy(target)) {
for (Class<?> type : AopProxyUtils.proxiedUserInterfaces(target)) {
validatedAnn = AnnotationUtils.findAnnotation(type, Validated.class);
if (validatedAnn != null) {
break;
}
}
}
else {
validatedAnn = AnnotationUtils.findAnnotation(target.getClass(), Validated.class);
}
}
return (validatedAnn != null ? validatedAnn.value() : new Class<?>[0]);
}
@Override
@@ -46,7 +46,6 @@ import org.springframework.util.ClassUtils;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.annotation.Validated;
import org.springframework.validation.annotation.ValidationAnnotationUtils;
import org.springframework.validation.method.MethodValidationException;
import org.springframework.validation.method.MethodValidationResult;
import org.springframework.validation.method.ParameterErrors;
@@ -225,7 +224,7 @@ public class MethodValidationInterceptor implements MethodInterceptor {
*/
protected Class<?>[] determineValidationGroups(MethodInvocation invocation) {
Object target = getTarget(invocation);
return ValidationAnnotationUtils.determineValidationGroups(target, invocation.getMethod());
return this.validationAdapter.determineValidationGroups(target, invocation.getMethod());
}
@@ -39,10 +39,7 @@ public interface MethodValidator {
* @param target the target Object
* @param method the target method
* @return the applicable validation groups as a {@code Class} array
* @deprecated in favor of
* {@link org.springframework.validation.annotation.ValidationAnnotationUtils#determineValidationGroups(Object, Method)}
*/
@Deprecated(since = "7.0.4", forRemoval = true)
Class<?>[] determineValidationGroups(Object target, Method method);
/**
@@ -51,8 +48,7 @@ public interface MethodValidator {
* @param method the target method
* @param parameters the parameters, if already created and available
* @param arguments the candidate argument values to validate
* @param groups validation groups from
* {@link org.springframework.validation.annotation.ValidationAnnotationUtils#determineValidationGroups(Object, Method)}
* @param groups validation groups from {@link #determineValidationGroups}
* @return the result of validation
*/
MethodValidationResult validateArguments(
@@ -82,8 +78,7 @@ public interface MethodValidator {
* @param method the target method
* @param returnType the return parameter, if already created and available
* @param returnValue the return value to validate
* @param groups validation groups from
* {@link org.springframework.validation.annotation.ValidationAnnotationUtils#determineValidationGroups(Object, Method)}
* @param groups validation groups from {@link #determineValidationGroups}
* @return the result of validation
*/
MethodValidationResult validateReturnValue(
@@ -176,24 +176,6 @@ public class ConfigurationClassPostProcessorAotContributionTests {
));
}
@Test
void applyToWhenHasImportAwareBeanRegistrarRegistersHints() {
BeanFactoryInitializationAotContribution contribution = getContribution(BeanRegistrarTests.ImportAwareConfiguration.class);
contribution.applyTo(generationContext, beanFactoryInitializationCode);
assertThat(generationContext.getRuntimeHints().resources().resourcePatternHints())
.singleElement()
.satisfies(resourceHint -> assertThat(resourceHint.getIncludes())
.map(ResourcePatternHint::getPattern)
.containsExactlyInAnyOrder(
"/",
"org",
"org/springframework",
"org/springframework/context",
"org/springframework/context/annotation",
"org/springframework/context/annotation/ConfigurationClassPostProcessorAotContributionTests$BeanRegistrarTests$ImportAwareConfiguration.class"
));
}
@SuppressWarnings("unchecked")
private void compile(BiConsumer<Consumer<DefaultListableBeanFactory>, Compiled> result) {
MethodReference methodReference = beanFactoryInitializationCode.getInitializers().get(0);
@@ -1,134 +0,0 @@
/*
* Copyright 2002-present 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.context.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.core.MethodParameter;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Unit tests for {@link ContextAnnotationAutowireCandidateResolver}.
*
* @author Sam Brannen
* @since 7.0.4
*/
class ContextAnnotationAutowireCandidateResolverTests {
final ContextAnnotationAutowireCandidateResolver resolver = new ContextAnnotationAutowireCandidateResolver();
Method testMethod;
@RegisterExtension
BeforeTestExecutionCallback extension = context -> this.testMethod = context.getRequiredTestMethod();
@Test
void isNotLazy() {
assertNotLazy();
}
@Test
void isLazy() {
assertLazy();
}
@Test
void isMetaLazy() {
assertLazy();
}
@Test // gh-36306
void isMetaMetaLazy() {
assertLazy();
}
private void assertLazy() {
assertThat(this.resolver.isLazy(getMethodDescriptor()))
.as("%sMethod() is @Lazy", this.testMethod.getName()).isTrue();
assertThat(this.resolver.isLazy(getParameterDescriptor()))
.as("parameter in %sParameter() is @Lazy", this.testMethod.getName()).isTrue();
}
private void assertNotLazy() {
assertThat(this.resolver.isLazy(getMethodDescriptor()))
.as("%sMethod() is not @Lazy", this.testMethod.getName()).isFalse();
assertThat(this.resolver.isLazy(getParameterDescriptor()))
.as("parameter in %sParameter() is not @Lazy", this.testMethod.getName()).isFalse();
}
private DependencyDescriptor getMethodDescriptor() {
var method = ReflectionUtils.findMethod(getClass(), this.testMethod.getName() + "Method");
var methodParameter = MethodParameter.forExecutable(method, -1);
return new DependencyDescriptor(methodParameter, true);
}
private DependencyDescriptor getParameterDescriptor() {
var method = ReflectionUtils.findMethod(getClass(), this.testMethod.getName() + "Parameter", String.class);
var methodParameter = MethodParameter.forExecutable(method, 0);
return new DependencyDescriptor(methodParameter, true);
}
void isNotLazyMethod() {
}
@Lazy
void isLazyMethod() {
}
@MetaLazy
void isMetaLazyMethod() {
}
@MetaMetaLazy
void isMetaMetaLazyMethod() {
}
void isNotLazyParameter(String enigma) {
}
void isLazyParameter(@Lazy String enigma) {
}
void isMetaLazyParameter(@MetaLazy String enigma) {
}
void isMetaMetaLazyParameter(@MetaMetaLazy String enigma) {
}
@Lazy
@Retention(RetentionPolicy.RUNTIME)
@interface MetaLazy {
}
@MetaLazy
@Retention(RetentionPolicy.RUNTIME)
@interface MetaMetaLazy {
}
}
@@ -23,11 +23,9 @@ import java.util.concurrent.atomic.AtomicInteger;
import org.junit.jupiter.api.Test;
import org.springframework.aop.config.AopConfigUtils;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.interceptor.ConcurrencyThrottleInterceptor;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
@@ -79,32 +77,6 @@ class ConcurrencyLimitTests {
assertThat(target.current).hasValue(0);
}
@Test
void withPostProcessorForMethodWithInterface() {
AnnotatedInterface proxy = createProxy(AnnotatedMethodBeanWithInterface.class, AnnotatedInterface.class, false);
AnnotatedMethodBeanWithInterface target = (AnnotatedMethodBeanWithInterface) AopProxyUtils.getSingletonTarget(proxy);
List<CompletableFuture<?>> futures = new ArrayList<>(10);
for (int i = 0; i < 10; i++) {
futures.add(CompletableFuture.runAsync(proxy::concurrentOperation));
}
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
assertThat(target.current).hasValue(0);
}
@Test
void withPostProcessorForMethodWithInterfaceAndDefaultTargetClass() {
AnnotatedInterface proxy = createProxy(AnnotatedMethodBeanWithInterface.class, AnnotatedInterface.class, true);
AnnotatedMethodBeanWithInterface target = (AnnotatedMethodBeanWithInterface) AopProxyUtils.getSingletonTarget(proxy);
List<CompletableFuture<?>> futures = new ArrayList<>(10);
for (int i = 0; i < 10; i++) {
futures.add(CompletableFuture.runAsync(proxy::concurrentOperation));
}
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
assertThat(target.current).hasValue(0);
}
@Test
void withPostProcessorForMethodWithUnboundedConcurrency() {
AnnotatedMethodBean proxy = createProxy(AnnotatedMethodBean.class);
@@ -229,21 +201,12 @@ class ConcurrencyLimitTests {
private static <T> T createProxy(Class<T> beanClass) {
return createProxy(beanClass, beanClass, true);
}
private static <T> T createProxy(Class<?> beanClass, Class<T> exposedClass, boolean proxyTargetClass) {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
if (exposedClass.isInterface() && proxyTargetClass) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(bf);
}
bf.registerBeanDefinition("bean", new RootBeanDefinition(beanClass));
ConcurrencyLimitBeanPostProcessor bpp = new ConcurrencyLimitBeanPostProcessor();
bpp.setBeanFactory(bf);
bf.addBeanPostProcessor(bpp);
T proxy = bf.getBean(exposedClass);
assertThat(proxyTargetClass ? AopUtils.isCglibProxy(proxy) : AopUtils.isJdkDynamicProxy(proxy)).isTrue();
return proxy;
return bf.getBean(beanClass);
}
@@ -311,33 +274,6 @@ class ConcurrencyLimitTests {
}
static class AnnotatedMethodBeanWithInterface implements AnnotatedInterface {
final AtomicInteger current = new AtomicInteger();
@Override
public void concurrentOperation() {
if (current.incrementAndGet() > 2) {
throw new IllegalStateException();
}
try {
Thread.sleep(100);
}
catch (InterruptedException ex) {
throw new IllegalStateException(ex);
}
current.decrementAndGet();
}
}
interface AnnotatedInterface {
@ConcurrencyLimit(2)
void concurrentOperation();
}
@ConcurrencyLimit(2)
static class AnnotatedClassBean {
@@ -523,6 +523,7 @@ class RetryInterceptorTests {
int counter = 0;
@Retryable(maxRetries = 5, delay = 10)
@Override
public void retryOperation() throws IOException {
counter++;
@@ -31,7 +31,6 @@ import org.springframework.scheduling.TriggerContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.junit.jupiter.api.Named.named;
import static org.junit.jupiter.params.provider.Arguments.arguments;
/**
@@ -48,8 +47,8 @@ import static org.junit.jupiter.params.provider.Arguments.arguments;
class CronTriggerTests {
static List<Arguments> parameters = List.of(
arguments(new Date(), named("PST", TimeZone.getTimeZone("PST"))),
arguments(new Date(), named("CET", TimeZone.getTimeZone("CET"))));
arguments(new Date(), TimeZone.getTimeZone("PST")),
arguments(new Date(), TimeZone.getTimeZone("CET")));
private final Calendar calendar = new GregorianCalendar();
@@ -1,196 +0,0 @@
/*
* Copyright 2002-present 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.validation.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
import jakarta.validation.Valid;
import jakarta.validation.groups.Default;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
import org.junit.jupiter.api.extension.RegisterExtension;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Unit tests for {@link ValidationAnnotationUtils}.
*
* @author Sam Brannen
* @since 7.0.4
*/
class ValidationAnnotationUtilsTests {
@Nested
class DetermineValidationHintsTests {
Method method;
@RegisterExtension
BeforeTestExecutionCallback extension = context -> this.method = context.getRequiredTestMethod();
@Test
void nonValidatedMethod(TestInfo testInfo) {
var annotation = this.method.getAnnotation(Test.class);
var hints = ValidationAnnotationUtils.determineValidationHints(annotation);
assertThat(hints).isNull();
}
@Test
@Validated({ GroupA.class, Default.class })
void springValidated(TestInfo testInfo) {
var annotation = this.method.getAnnotation(Validated.class);
var hints = ValidationAnnotationUtils.determineValidationHints(annotation);
assertThat(hints).containsExactly(GroupA.class, Default.class);
}
@Test
@MetaValidated
void springMetaValidated(TestInfo testInfo) {
var annotation = this.method.getAnnotation(MetaValidated.class);
var hints = ValidationAnnotationUtils.determineValidationHints(annotation);
assertThat(hints).containsExactly(GroupB.class, Default.class);
}
@Test // gh-36274
@MetaMetaValidated
void springMetaMetaValidated(TestInfo testInfo) {
var annotation = this.method.getAnnotation(MetaMetaValidated.class);
var hints = ValidationAnnotationUtils.determineValidationHints(annotation);
assertThat(hints).containsExactly(GroupB.class, Default.class);
}
@Test
@Valid
void jakartaValid(TestInfo testInfo) {
var annotation = this.method.getAnnotation(Valid.class);
var hints = ValidationAnnotationUtils.determineValidationHints(annotation);
assertThat(hints).isEmpty();
}
@Test
@ValidPlain
void plainCustomValidAnnotation(TestInfo testInfo) {
var annotation = this.method.getAnnotation(ValidPlain.class);
var hints = ValidationAnnotationUtils.determineValidationHints(annotation);
assertThat(hints).isEmpty();
}
@Test
@ValidParameterized
void parameterizedCustomValidAnnotationWithEmptyGroups(TestInfo testInfo) {
var annotation = this.method.getAnnotation(ValidParameterized.class);
var hints = ValidationAnnotationUtils.determineValidationHints(annotation);
assertThat(hints).isEmpty();
}
@Test
@ValidParameterized({ GroupA.class, GroupB.class })
void parameterizedCustomValidAnnotationWithNonEmptyGroups(TestInfo testInfo) {
var annotation = this.method.getAnnotation(ValidParameterized.class);
var hints = ValidationAnnotationUtils.determineValidationHints(annotation);
assertThat(hints).containsExactly(GroupA.class, GroupB.class);
}
@Retention(RetentionPolicy.RUNTIME)
@interface ValidPlain {
}
@Retention(RetentionPolicy.RUNTIME)
@interface ValidParameterized {
Class<?>[] value() default {};
}
}
@Nested
class DetermineValidationGroupsTests {
Method method;
@RegisterExtension
BeforeTestExecutionCallback extension = context -> this.method = context.getRequiredTestMethod();
@Test
void nonValidatedMethod(TestInfo testInfo) {
var hints = ValidationAnnotationUtils.determineValidationGroups(this, this.method);
assertThat(hints).isEmpty();
}
@Test
@Validated({ GroupA.class, Default.class })
void springValidated(TestInfo testInfo) {
var hints = ValidationAnnotationUtils.determineValidationGroups(this, this.method);
assertThat(hints).containsExactly(GroupA.class, Default.class);
}
@Test
@MetaValidated
void springMetaValidated(TestInfo testInfo) {
var hints = ValidationAnnotationUtils.determineValidationGroups(this, this.method);
assertThat(hints).containsExactly(GroupB.class, Default.class);
}
@Test
@MetaMetaValidated
void springMetaMetaValidated(TestInfo testInfo) {
var hints = ValidationAnnotationUtils.determineValidationGroups(this, this.method);
assertThat(hints).containsExactly(GroupB.class, Default.class);
}
@Test
@Valid
void jakartaValid(TestInfo testInfo) {
var hints = ValidationAnnotationUtils.determineValidationGroups(this, this.method);
assertThat(hints).isEmpty();
}
}
interface GroupA {
}
interface GroupB {
}
@Validated({ GroupB.class, Default.class })
@Retention(RetentionPolicy.RUNTIME)
@interface MetaValidated {
}
@MetaValidated
@Retention(RetentionPolicy.RUNTIME)
@interface MetaMetaValidated {
}
}
@@ -16,6 +16,7 @@
package org.springframework.cache
import kotlinx.coroutines.runBlocking
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.testfixture.beans.TestBean
@@ -32,47 +33,51 @@ import org.springframework.context.annotation.Configuration
class KotlinCacheReproTests {
@Test
suspend fun spr14235AdaptsToSuspendingFunction() {
val context = AnnotationConfigApplicationContext(
Spr14235Config::class.java,
Spr14235SuspendingService::class.java
)
val bean = context.getBean(Spr14235SuspendingService::class.java)
val cache = context.getBean(CacheManager::class.java).getCache("itemCache")!!
val tb: TestBean = bean.findById("tb1")
assertThat(bean.findById("tb1")).isSameAs(tb)
assertThat(cache["tb1"]!!.get()).isSameAs(tb)
bean.clear()
val tb2: TestBean = bean.findById("tb1")
assertThat(tb2).isNotSameAs(tb)
assertThat(cache["tb1"]!!.get()).isSameAs(tb2)
bean.clear()
bean.insertItem(tb)
assertThat(bean.findById("tb1")).isSameAs(tb)
assertThat(cache["tb1"]!!.get()).isSameAs(tb)
context.close()
fun spr14235AdaptsToSuspendingFunction() {
runBlocking {
val context = AnnotationConfigApplicationContext(
Spr14235Config::class.java,
Spr14235SuspendingService::class.java
)
val bean = context.getBean(Spr14235SuspendingService::class.java)
val cache = context.getBean(CacheManager::class.java).getCache("itemCache")!!
val tb: TestBean = bean.findById("tb1")
assertThat(bean.findById("tb1")).isSameAs(tb)
assertThat(cache["tb1"]!!.get()).isSameAs(tb)
bean.clear()
val tb2: TestBean = bean.findById("tb1")
assertThat(tb2).isNotSameAs(tb)
assertThat(cache["tb1"]!!.get()).isSameAs(tb2)
bean.clear()
bean.insertItem(tb)
assertThat(bean.findById("tb1")).isSameAs(tb)
assertThat(cache["tb1"]!!.get()).isSameAs(tb)
context.close()
}
}
@Test
suspend fun spr14235AdaptsToSuspendingFunctionWithSync() {
val context = AnnotationConfigApplicationContext(
Spr14235Config::class.java,
Spr14235SuspendingServiceSync::class.java
)
val bean = context.getBean(Spr14235SuspendingServiceSync::class.java)
val cache = context.getBean(CacheManager::class.java).getCache("itemCache")!!
val tb = bean.findById("tb1")
assertThat(bean.findById("tb1")).isSameAs(tb)
assertThat(cache["tb1"]!!.get()).isSameAs(tb)
cache.clear()
val tb2 = bean.findById("tb1")
assertThat(tb2).isNotSameAs(tb)
assertThat(cache["tb1"]!!.get()).isSameAs(tb2)
cache.clear()
bean.insertItem(tb)
assertThat(bean.findById("tb1")).isSameAs(tb)
assertThat(cache["tb1"]!!.get()).isSameAs(tb)
context.close()
fun spr14235AdaptsToSuspendingFunctionWithSync() {
runBlocking {
val context = AnnotationConfigApplicationContext(
Spr14235Config::class.java,
Spr14235SuspendingServiceSync::class.java
)
val bean = context.getBean(Spr14235SuspendingServiceSync::class.java)
val cache = context.getBean(CacheManager::class.java).getCache("itemCache")!!
val tb = bean.findById("tb1")
assertThat(bean.findById("tb1")).isSameAs(tb)
assertThat(cache["tb1"]!!.get()).isSameAs(tb)
cache.clear()
val tb2 = bean.findById("tb1")
assertThat(tb2).isNotSameAs(tb)
assertThat(cache["tb1"]!!.get()).isSameAs(tb2)
cache.clear()
bean.insertItem(tb)
assertThat(bean.findById("tb1")).isSameAs(tb)
assertThat(cache["tb1"]!!.get()).isSameAs(tb)
context.close()
}
}
@Test
@@ -49,7 +49,7 @@ class MethodValidationKotlinTests {
}
@Test
suspend fun coroutinesParameterValidation() {
fun coroutinesParameterValidation() = runBlocking<Unit> {
val bean = MyValidCoroutinesBean()
val proxyFactory = ProxyFactory(bean)
val validator = LocalValidatorFactoryBean()
@@ -44,7 +44,6 @@ class InvocationsRecorderClassTransformer implements ClassFileTransformer {
private final String[] ignoredPackages;
public InvocationsRecorderClassTransformer(String[] instrumentedPackages, String[] ignoredPackages) {
Assert.notNull(instrumentedPackages, "instrumentedPackages must not be null");
Assert.notNull(ignoredPackages, "ignoredPackages must not be null");
@@ -57,7 +56,6 @@ class InvocationsRecorderClassTransformer implements ClassFileTransformer {
.toArray(String[]::new);
}
@Override
public byte[] transform(@Nullable ClassLoader classLoader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
@@ -104,7 +102,7 @@ class InvocationsRecorderClassTransformer implements ClassFileTransformer {
fileReader.accept(classVisitor, 0);
}
catch (Exception ex) {
System.err.println("Failed to transform class: " + ex);
ex.printStackTrace();
return classfileBuffer;
}
if (classVisitor.isTransformed()) {
@@ -112,5 +110,4 @@ class InvocationsRecorderClassTransformer implements ClassFileTransformer {
}
return classfileBuffer;
}
}
@@ -49,6 +49,7 @@ public final class RuntimeHintsAgent {
private static boolean loaded = false;
private RuntimeHintsAgent() {
}
public static void premain(@Nullable String agentArgs, Instrumentation inst) {
@@ -67,7 +68,6 @@ public final class RuntimeHintsAgent {
return loaded;
}
private static final class ParsedArguments {
List<String> instrumentedPackages;
@@ -108,6 +108,6 @@ public final class RuntimeHintsAgent {
}
return new ParsedArguments(included, excluded);
}
}
}
}
+2 -2
View File
@@ -14,8 +14,8 @@ multiRelease {
releaseVersions 21, 24
}
def javapoetVersion = "0.10.0"
def objenesisVersion = "3.5"
def javapoetVersion = "0.7.0"
def objenesisVersion = "3.4"
configurations {
java21Api.extendsFrom(api)
@@ -0,0 +1,38 @@
/*
* Copyright 2002-present 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.aot.nativex.substitution;
import com.oracle.svm.core.annotate.Alias;
import com.oracle.svm.core.annotate.TargetClass;
/**
* Allow to reference {@code com.sun.beans.finder.ClassFinder} from
* {@link Target_Introspector}.
*
* TODO Remove once Spring Framework requires GraalVM 23.0+, see <a href="https://github.com/oracle/graal/pull/5224">graal#5224</a>.
*
* @author Sebastien Deleuze
* @since 6.0
*/
@TargetClass(className = "com.sun.beans.finder.ClassFinder")
final class Target_ClassFinder {
@Alias
public static Class<?> findClass(String name, ClassLoader loader) throws ClassNotFoundException {
return null;
}
}
@@ -0,0 +1,56 @@
/*
* Copyright 2002-present 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.aot.nativex.substitution;
import java.beans.Customizer;
import com.oracle.svm.core.annotate.Substitute;
import com.oracle.svm.core.annotate.TargetClass;
/**
* {@link java.beans.Introspector} substitution with a refined {@code findCustomizerClass} implementation
* designed to avoid thousands of AWT classes to be included in the native image.
*
* TODO Remove once Spring Framework requires GraalVM 23.0+, see <a href="https://github.com/oracle/graal/pull/5224">graal#5224</a>.
*
* @author Sebastien Deleuze
* @since 6.0
*/
@TargetClass(className = "java.beans.Introspector")
final class Target_Introspector {
@Substitute
private static Class<?> findCustomizerClass(Class<?> type) {
String name = type.getName() + "Customizer";
try {
type = Target_ClassFinder.findClass(name, type.getClassLoader());
if (Customizer.class.isAssignableFrom(type)) {
Class<?> c = type;
do {
c = c.getSuperclass();
if (c.getName().equals("java.awt.Component")) {
return type;
}
} while (!c.getName().equals("java.lang.Object"));
}
}
catch (Exception ignored) {
}
return null;
}
}
@@ -0,0 +1,7 @@
/**
* GraalVM native image substitutions, not part of Spring Framework public API.
*/
@NullUnmarked
package org.springframework.aot.nativex.substitution;
import org.jspecify.annotations.NullUnmarked;
@@ -95,7 +95,7 @@ public class Attribute {
* a Code attribute that contains labels.
* @deprecated no longer used by ASM.
*/
@Deprecated(forRemoval = false)
@Deprecated
protected Label[] getLabels() {
return new Label[0];
}
@@ -174,7 +174,6 @@ public class Attribute {
* ClassReader overrides {@link ClassReader#readLabel}. Hence {@link #read(ClassReader, int, int,
* char[], int, Label[])} must not manually create {@link Label} instances.
*
* @param classReader the class that contains the attribute to be read.
* @param bytecodeOffset a bytecode offset in a method.
* @param labels the already created labels, indexed by their offset. If a label already exists
* for bytecodeOffset this method does not create a new one. Otherwise it stores the new label

Some files were not shown because too many files have changed in this diff Show More