mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-19 19:29:03 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5356a1b1ac | ||
|
|
f7307c9e07 | ||
|
|
098c4b1dd7 | ||
|
|
65dbfd09b4 | ||
|
|
203fa75196 | ||
|
|
9a56a8877f | ||
|
|
e79a9a5bff | ||
|
|
2c3c3831c1 | ||
|
|
c38e9896c7 | ||
|
|
3e0849a566 | ||
|
|
f3a605b92c | ||
|
|
6fdff201e9 | ||
|
|
c42778a05b | ||
|
|
f140df881d | ||
|
|
12cf654c98 | ||
|
|
089e4e69f1 | ||
|
|
77755ff2fa | ||
|
|
d0aa7ad524 | ||
|
|
072fc80cdb | ||
|
|
6f32ff489a | ||
|
|
24c8dfea1f | ||
|
|
cdfe5816c8 | ||
|
|
0ff200b2f1 | ||
|
|
261dac87cc | ||
|
|
fce2f49e46 | ||
|
|
eca2b9657e | ||
|
|
e48f37d956 | ||
|
|
ddc397dd05 | ||
|
|
6b7f0bd4b6 | ||
|
|
c97a895f09 | ||
|
|
3b13f2ed38 | ||
|
|
a0eebca0cf | ||
|
|
c6c64e6fe7 | ||
|
|
0ca393c0dc | ||
|
|
6681394886 | ||
|
|
172987c874 | ||
|
|
47a5ebfde6 | ||
|
|
43a113f067 | ||
|
|
dc250e1cc1 | ||
|
|
fe74fcfded | ||
|
|
2451bd62b0 | ||
|
|
624d6dd167 | ||
|
|
0ea96b4806 | ||
|
|
c28a0d5627 | ||
|
|
61d045ce52 | ||
|
|
c0bef2c693 | ||
|
|
c3a0eaa95e | ||
|
|
e12d1259d1 | ||
|
|
404c4d9d92 | ||
|
|
7785f94c4c | ||
|
|
4f6f2c0d41 | ||
|
|
e6da2a86fc | ||
|
|
301087e510 | ||
|
|
6c054f88ea | ||
|
|
f58c7d80cc | ||
|
|
e5be10d53d | ||
|
|
524da905db | ||
|
|
4323c60513 | ||
|
|
f6b608eecb | ||
|
|
4da1511ed3 | ||
|
|
7102c33661 | ||
|
|
43409b00d0 | ||
|
|
d55abc6cf9 | ||
|
|
31806f3a6b | ||
|
|
8a84241c1e | ||
|
|
624be6d4e6 | ||
|
|
b08883b65c | ||
|
|
542ba3517f | ||
|
|
8c6a7799be | ||
|
|
557dbba585 | ||
|
|
e9de426eb5 | ||
|
|
cda577d1aa | ||
|
|
8feb842df5 | ||
|
|
ea2931f24a | ||
|
|
26d1c38d84 | ||
|
|
345daaabbc | ||
|
|
6c08d93992 | ||
|
|
6d7cd9c7dc | ||
|
|
73eb6f0660 | ||
|
|
31f298b929 | ||
|
|
c01aab5850 | ||
|
|
61ef5a8930 | ||
|
|
3b53ee7038 |
@@ -1,17 +0,0 @@
|
||||
name: Print JVM thread dumps
|
||||
description: Prints a thread dump for all running JVMs
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- if: ${{ runner.os == 'Linux' }}
|
||||
shell: bash
|
||||
run: |
|
||||
for jvm_pid in $(jps -q -J-XX:+PerfDisableSharedMem); do
|
||||
jcmd $java_pid Thread.print
|
||||
done
|
||||
- if: ${{ runner.os == 'Windows' }}
|
||||
shell: powershell
|
||||
run: |
|
||||
foreach ($jvm_pid in $(jps -q -J-XX:+PerfDisableSharedMem)) {
|
||||
jcmd $jvm_pid Thread.print
|
||||
}
|
||||
@@ -2,7 +2,7 @@ name: Build and deploy snapshot
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- 6.1.x
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
name: Build Pull Request
|
||||
on: pull_request
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build pull request
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'spring-projects/spring-framework' }}
|
||||
steps:
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'liberica'
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Validate Gradle wrapper
|
||||
uses: gradle/wrapper-validation-action@699bb18358f12c5b78b37bb0111d3a0e2276e0e2
|
||||
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
CI: 'true'
|
||||
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
|
||||
run: ./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false --no-daemon --no-parallel --continue build
|
||||
|
||||
- name: Print JVM thread dumps when cancelled
|
||||
uses: ./.github/actions/print-jvm-thread-dumps
|
||||
if: cancelled()
|
||||
|
||||
- name: Upload build reports
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: build-reports
|
||||
path: '**/build/reports/'
|
||||
@@ -1,7 +1,8 @@
|
||||
name: CI
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 9 * * *'
|
||||
push:
|
||||
branches:
|
||||
- 6.1.x
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
= Contributor Code of Conduct
|
||||
|
||||
As contributors and maintainers of this project, and in the interest of fostering an open
|
||||
and welcoming community, we pledge to respect all people who contribute through reporting
|
||||
issues, posting feature requests, updating documentation, submitting pull requests or
|
||||
patches, and other activities.
|
||||
|
||||
We are committed to making participation in this project a harassment-free experience for
|
||||
everyone, regardless of level of experience, gender, gender identity and expression,
|
||||
sexual orientation, disability, personal appearance, body size, race, ethnicity, age,
|
||||
religion, or nationality.
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery
|
||||
* Personal attacks
|
||||
* Trolling or insulting/derogatory comments
|
||||
* Public or private harassment
|
||||
* Publishing other's private information, such as physical or electronic addresses,
|
||||
without explicit permission
|
||||
* Other unethical or unprofessional conduct
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments,
|
||||
commits, code, wiki edits, issues, and other contributions that are not aligned to this
|
||||
Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors
|
||||
that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
By adopting this Code of Conduct, project maintainers commit themselves to fairly and
|
||||
consistently applying these principles to every aspect of managing this project. Project
|
||||
maintainers who do not follow or enforce the Code of Conduct may be permanently removed
|
||||
from the project team.
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an
|
||||
individual is representing the project or its community.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by
|
||||
contacting a project maintainer at spring-code-of-conduct@pivotal.io . All complaints will
|
||||
be reviewed and investigated and will result in a response that is deemed necessary and
|
||||
appropriate to the circumstances. Maintainers are obligated to maintain confidentiality
|
||||
with regard to the reporter of an incident.
|
||||
|
||||
This Code of Conduct is adapted from the
|
||||
https://contributor-covenant.org[Contributor Covenant], version 1.3.0, available at
|
||||
https://contributor-covenant.org/version/1/3/0/[contributor-covenant.org/version/1/3/0/]
|
||||
+1
-1
@@ -18,7 +18,7 @@ First off, thank you for taking the time to contribute! :+1: :tada:
|
||||
|
||||
This project is governed by the [Spring Code of Conduct](CODE_OF_CONDUCT.adoc).
|
||||
By participating you are expected to uphold this code.
|
||||
Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
|
||||
Please report unacceptable behavior to spring-code-of-conduct@spring.io.
|
||||
|
||||
### How to Contribute
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [](https://github.com/spring-projects/spring-framework/actions/workflows/build-and-deploy-snapshot.yml?query=branch%3Amain) [](https://ge.spring.io/scans?search.rootProjectNames=spring)
|
||||
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [](https://github.com/spring-projects/spring-framework/actions/workflows/build-and-deploy-snapshot.yml?query=branch%3A6.1.x) [](https://ge.spring.io/scans?search.rootProjectNames=spring)
|
||||
|
||||
This is the home of the Spring Framework: the foundation for all [Spring projects](https://spring.io/projects). Collectively the Spring Framework and the family of Spring projects are often referred to simply as "Spring".
|
||||
|
||||
@@ -6,7 +6,7 @@ Spring provides everything required beyond the Java programming language for cre
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project is governed by the [Spring Code of Conduct](CODE_OF_CONDUCT.adoc). By participating, you are expected to uphold this code of conduct. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
|
||||
This project is governed by the [Spring Code of Conduct](CODE_OF_CONDUCT.adoc). By participating, you are expected to uphold this code of conduct. Please report unacceptable behavior to spring-code-of-conduct@spring.io.
|
||||
|
||||
## Access to Binaries
|
||||
|
||||
|
||||
+1
-2
@@ -2,14 +2,13 @@ plugins {
|
||||
id 'io.freefair.aspectj' version '8.4' apply false
|
||||
// kotlinVersion is managed in gradle.properties
|
||||
id 'org.jetbrains.kotlin.plugin.serialization' version "${kotlinVersion}" apply false
|
||||
id 'org.jetbrains.dokka' version '1.9.20'
|
||||
id 'org.jetbrains.dokka' version '1.8.20'
|
||||
id 'org.unbroken-dome.xjc' version '2.0.0' apply false
|
||||
id 'com.github.ben-manes.versions' version '0.51.0'
|
||||
id 'com.github.johnrengelman.shadow' version '8.1.1' apply false
|
||||
id 'de.undercouch.download' version '5.4.0'
|
||||
id 'me.champeau.jmh' version '0.7.2' apply false
|
||||
id 'me.champeau.mrjar' version '0.1.1'
|
||||
id "net.ltgt.errorprone" version "3.1.0" apply false
|
||||
}
|
||||
|
||||
ext {
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
org.gradle.caching=true
|
||||
javaFormatVersion=0.0.41
|
||||
javaFormatVersion=0.0.42
|
||||
|
||||
@@ -50,7 +50,7 @@ public class CheckstyleConventions {
|
||||
project.getPlugins().apply(CheckstylePlugin.class);
|
||||
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g"));
|
||||
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
|
||||
checkstyle.setToolVersion("10.16.0");
|
||||
checkstyle.setToolVersion("10.17.0");
|
||||
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
|
||||
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
|
||||
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
|
||||
@@ -64,7 +64,7 @@ public class CheckstyleConventions {
|
||||
NoHttpExtension noHttp = project.getExtensions().getByType(NoHttpExtension.class);
|
||||
noHttp.setAllowlistFile(project.file("src/nohttp/allowlist.lines"));
|
||||
noHttp.getSource().exclude("**/test-output/**", "**/.settings/**",
|
||||
"**/.classpath", "**/.project", "**/.gradle/**");
|
||||
"**/.classpath", "**/.project", "**/.gradle/**", "**/node_modules/**");
|
||||
List<String> buildFolders = List.of("bin", "build", "out");
|
||||
project.allprojects(subproject -> {
|
||||
Path rootPath = project.getRootDir().toPath();
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@ NOTE: CI is being migrated to GitHub Actions.
|
||||
|
||||
The Spring Framework uses https://concourse-ci.org/[Concourse] for its CI build and other automated tasks.
|
||||
The Spring team has a dedicated Concourse instance available at https://ci.spring.io with a build pipeline
|
||||
for https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-6.2.x[Spring Framework 6.2.x].
|
||||
for https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-6.1.x[Spring Framework 6.1.x].
|
||||
|
||||
=== Setting up your development environment
|
||||
|
||||
@@ -53,7 +53,7 @@ The pipeline can be deployed using the following command:
|
||||
|
||||
[source]
|
||||
----
|
||||
$ fly -t spring set-pipeline -p spring-framework-6.2.x -c ci/pipeline.yml -l ci/parameters.yml
|
||||
$ fly -t spring set-pipeline -p spring-framework-6.1.x -c ci/pipeline.yml -l ci/parameters.yml
|
||||
----
|
||||
|
||||
NOTE: This assumes that you have credhub integration configured with the appropriate secrets.
|
||||
|
||||
+2
-2
@@ -3,8 +3,8 @@ github-repo-name: "spring-projects/spring-framework"
|
||||
sonatype-staging-profile: "org.springframework"
|
||||
docker-hub-organization: "springci"
|
||||
artifactory-server: "https://repo.spring.io"
|
||||
branch: "main"
|
||||
milestone: "6.2.x"
|
||||
branch: "6.1.x"
|
||||
milestone: "6.1.x"
|
||||
build-name: "spring-framework"
|
||||
pipeline-name: "spring-framework"
|
||||
concourse-url: "https://ci.spring.io"
|
||||
|
||||
+2
-2
@@ -7,8 +7,8 @@ anchors:
|
||||
gradle-enterprise-task-params: &gradle-enterprise-task-params
|
||||
DEVELOCITY_ACCESS_KEY: ((gradle_enterprise_secret_access_key))
|
||||
sonatype-task-params: &sonatype-task-params
|
||||
SONATYPE_USERNAME: ((sonatype-username))
|
||||
SONATYPE_PASSWORD: ((sonatype-password))
|
||||
SONATYPE_USERNAME: ((s01-user-token))
|
||||
SONATYPE_PASSWORD: ((s01-user-token-password))
|
||||
SONATYPE_URL: ((sonatype-url))
|
||||
SONATYPE_STAGING_PROFILE: ((sonatype-staging-profile))
|
||||
artifactory-task-params: &artifactory-task-params
|
||||
|
||||
@@ -36,4 +36,4 @@ runtime:
|
||||
failure_level: warn
|
||||
ui:
|
||||
bundle:
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.15/ui-bundle.zip
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.16/ui-bundle.zip
|
||||
|
||||
@@ -20,8 +20,6 @@ asciidoc:
|
||||
fold: 'all'
|
||||
table-stripes: 'odd'
|
||||
include-java: 'example$docs-src/main/java/org/springframework/docs'
|
||||
include-kotlin: 'example$docs-src/main/kotlin/org/springframework/docs'
|
||||
include-xml: 'example$docs-src/main/resources/org/springframework/docs'
|
||||
spring-site: 'https://spring.io'
|
||||
spring-site-blog: '{spring-site}/blog'
|
||||
spring-site-cve: "{spring-site}/security"
|
||||
|
||||
@@ -43,28 +43,10 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
api(project(":spring-context"))
|
||||
api(project(":spring-jdbc"))
|
||||
api(project(":spring-jms"))
|
||||
api(project(":spring-web"))
|
||||
api(project(":spring-webmvc"))
|
||||
api(project(":spring-context-support"))
|
||||
api(project(":spring-aspects"))
|
||||
api(project(":spring-websocket"))
|
||||
|
||||
api("org.jetbrains.kotlin:kotlin-stdlib")
|
||||
api("jakarta.jms:jakarta.jms-api")
|
||||
api("jakarta.servlet:jakarta.servlet-api")
|
||||
api("org.apache.commons:commons-dbcp2:2.11.0")
|
||||
api("com.mchange:c3p0:0.9.5.5")
|
||||
api("com.fasterxml.jackson.core:jackson-databind")
|
||||
api("com.fasterxml.jackson.module:jackson-module-parameter-names")
|
||||
api("jakarta.validation:jakarta.validation-api")
|
||||
api("org.aspectj:aspectjweaver")
|
||||
api("io.projectreactor.netty:reactor-netty-http")
|
||||
api("org.eclipse.jetty.websocket:jetty-websocket-jetty-api")
|
||||
api("javax.cache:cache-api")
|
||||
api("jakarta.resource:jakarta.resource-api")
|
||||
api("org.apache.activemq:activemq-ra:6.1.2")
|
||||
|
||||
implementation(project(":spring-core-test"))
|
||||
implementation("org.assertj:assertj-core")
|
||||
|
||||
@@ -134,7 +134,6 @@
|
||||
**** xref:testing/testcontext-framework/ctx-management/failure-threshold.adoc[]
|
||||
**** xref:testing/testcontext-framework/ctx-management/hierarchies.adoc[]
|
||||
*** xref:testing/testcontext-framework/fixture-di.adoc[]
|
||||
*** xref:testing/testcontext-framework/bean-overriding.adoc[]
|
||||
*** xref:testing/testcontext-framework/web-scoped-beans.adoc[]
|
||||
*** xref:testing/testcontext-framework/tx.adoc[]
|
||||
*** xref:testing/testcontext-framework/executing-sql.adoc[]
|
||||
@@ -172,8 +171,6 @@
|
||||
***** xref:testing/annotations/integration-spring/annotation-activeprofiles.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-testpropertysource.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-dynamicpropertysource.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-testbean.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-mockitobean.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-dirtiescontext.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-testexecutionlisteners.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-recordapplicationevents.adoc[]
|
||||
|
||||
@@ -205,7 +205,7 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val person = factory.getBean("person") as Person
|
||||
val person = factory.getBean("person") as Person;
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -128,7 +128,18 @@ the resulting pointcut is effectively the union of the specified patterns.)
|
||||
|
||||
The following example shows how to use `JdkRegexpMethodPointcut`:
|
||||
|
||||
include-code::./JdkRegexpConfiguration[tag=snippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<bean id="settersAndAbsquatulatePointcut"
|
||||
class="org.springframework.aop.support.JdkRegexpMethodPointcut">
|
||||
<property name="patterns">
|
||||
<list>
|
||||
<value>.*set.*</value>
|
||||
<value>.*absquatulate</value>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
Spring provides a convenience class named `RegexpMethodPointcutAdvisor`, which lets us
|
||||
also reference an `Advice` (remember that an `Advice` can be an interceptor, before advice,
|
||||
@@ -136,7 +147,21 @@ throws advice, and others). Behind the scenes, Spring uses a `JdkRegexpMethodPoi
|
||||
Using `RegexpMethodPointcutAdvisor` simplifies wiring, as the one bean encapsulates both
|
||||
pointcut and advice, as the following example shows:
|
||||
|
||||
include-code::./RegexpConfiguration[tag=snippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<bean id="settersAndAbsquatulateAdvisor"
|
||||
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
|
||||
<property name="advice">
|
||||
<ref bean="beanNameOfAopAllianceInterceptor"/>
|
||||
</property>
|
||||
<property name="patterns">
|
||||
<list>
|
||||
<value>.*set.*</value>
|
||||
<value>.*absquatulate</value>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
You can use `RegexpMethodPointcutAdvisor` with any `Advice` type.
|
||||
|
||||
|
||||
@@ -221,8 +221,8 @@ NOTE: `ThreadLocal` instances come with serious issues (potentially resulting in
|
||||
incorrectly using them in multi-threaded and multi-classloader environments. You
|
||||
should always consider wrapping a `ThreadLocal` in some other class and never directly use
|
||||
the `ThreadLocal` itself (except in the wrapper class). Also, you should
|
||||
always remember to correctly set and unset (where the latter simply involves a call to
|
||||
`ThreadLocal.set(null)`) the resource local to the thread. Unsetting should be done in
|
||||
always remember to correctly set and unset (where the latter involves a call to
|
||||
`ThreadLocal.remove()`) the resource local to the thread. Unsetting should be done in
|
||||
any case, since not unsetting it might result in problematic behavior. Spring's
|
||||
`ThreadLocal` support does this for you and should always be considered in favor of using
|
||||
`ThreadLocal` instances without other proper handling code.
|
||||
|
||||
@@ -8,8 +8,54 @@ determines that a bean is advised by one or more aspects, it automatically gener
|
||||
a proxy for that bean to intercept method invocations and ensures that advice is run
|
||||
as needed.
|
||||
|
||||
The @AspectJ support can be enabled with programmatic or XML configuration. In either
|
||||
case, you also need to ensure that AspectJ's `org.aspectj:aspectjweaver` library is on the
|
||||
classpath of your application (version 1.9 or later).
|
||||
The @AspectJ support can be enabled with XML- or Java-style configuration. In either
|
||||
case, you also need to ensure that AspectJ's `aspectjweaver.jar` library is on the
|
||||
classpath of your application (version 1.9 or later). This library is available in the
|
||||
`lib` directory of an AspectJ distribution or from the Maven Central repository.
|
||||
|
||||
|
||||
[[aop-enable-aspectj-java]]
|
||||
== Enabling @AspectJ Support with Java Configuration
|
||||
|
||||
To enable @AspectJ support with Java `@Configuration`, add the `@EnableAspectJAutoProxy`
|
||||
annotation, as the following example shows:
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableAspectJAutoProxy
|
||||
public class AppConfig {
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableAspectJAutoProxy
|
||||
class AppConfig
|
||||
----
|
||||
======
|
||||
|
||||
[[aop-enable-aspectj-xml]]
|
||||
== Enabling @AspectJ Support with XML Configuration
|
||||
|
||||
To enable @AspectJ support with XML-based configuration, use the `aop:aspectj-autoproxy`
|
||||
element, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:aspectj-autoproxy/>
|
||||
----
|
||||
|
||||
This assumes that you use schema support as described in
|
||||
xref:core/appendix/xsd-schemas.adoc[XML Schema-based configuration].
|
||||
See xref:core/appendix/xsd-schemas.adoc#aop[the AOP schema] for how to
|
||||
import the tags in the `aop` namespace.
|
||||
|
||||
|
||||
|
||||
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
|
||||
|
||||
@@ -9,12 +9,43 @@ minimal steps required for a not-very-useful aspect.
|
||||
The first of the two examples shows a regular bean definition in the application context
|
||||
that points to a bean class that is annotated with `@Aspect`:
|
||||
|
||||
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<bean id="myAspect" class="com.xyz.NotVeryUsefulAspect">
|
||||
<!-- configure properties of the aspect here -->
|
||||
</bean>
|
||||
----
|
||||
|
||||
The second of the two examples shows the `NotVeryUsefulAspect` class definition, which is
|
||||
annotated with `@Aspect`:
|
||||
|
||||
include-code::./NotVeryUsefulAspect[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary",chomp="-packages",fold="none"]
|
||||
----
|
||||
package com.xyz;
|
||||
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
|
||||
@Aspect
|
||||
public class NotVeryUsefulAspect {
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages",fold="none"]
|
||||
----
|
||||
package com.xyz
|
||||
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
|
||||
@Aspect
|
||||
class NotVeryUsefulAspect
|
||||
----
|
||||
======
|
||||
|
||||
Aspects (classes annotated with `@Aspect`) can have methods and fields, the same as any
|
||||
other class. They can also contain pointcut, advice, and introduction (inter-type)
|
||||
|
||||
@@ -16,9 +16,93 @@ aspect.
|
||||
Because we want to retry the operation, we need to use around advice so that we can
|
||||
call `proceed` multiple times. The following listing shows the basic aspect implementation:
|
||||
|
||||
include-code::./ConcurrentOperationExecutor[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Aspect
|
||||
public class ConcurrentOperationExecutor implements Ordered {
|
||||
|
||||
`@Around("com.xyz.CommonPointcuts.businessService()")` references the `businessService` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[Sharing Named Pointcut Definitions].
|
||||
private static final int DEFAULT_MAX_RETRIES = 2;
|
||||
|
||||
private int maxRetries = DEFAULT_MAX_RETRIES;
|
||||
private int order = 1;
|
||||
|
||||
public void setMaxRetries(int maxRetries) {
|
||||
this.maxRetries = maxRetries;
|
||||
}
|
||||
|
||||
public int getOrder() {
|
||||
return this.order;
|
||||
}
|
||||
|
||||
public void setOrder(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
@Around("com.xyz.CommonPointcuts.businessService()") // <1>
|
||||
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
|
||||
int numAttempts = 0;
|
||||
PessimisticLockingFailureException lockFailureException;
|
||||
do {
|
||||
numAttempts++;
|
||||
try {
|
||||
return pjp.proceed();
|
||||
}
|
||||
catch(PessimisticLockingFailureException ex) {
|
||||
lockFailureException = ex;
|
||||
}
|
||||
} while(numAttempts <= this.maxRetries);
|
||||
throw lockFailureException;
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> References the `businessService` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[Sharing Named Pointcut Definitions].
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Aspect
|
||||
class ConcurrentOperationExecutor : Ordered {
|
||||
|
||||
private val DEFAULT_MAX_RETRIES = 2
|
||||
private var maxRetries = DEFAULT_MAX_RETRIES
|
||||
private var order = 1
|
||||
|
||||
fun setMaxRetries(maxRetries: Int) {
|
||||
this.maxRetries = maxRetries
|
||||
}
|
||||
|
||||
override fun getOrder(): Int {
|
||||
return this.order
|
||||
}
|
||||
|
||||
fun setOrder(order: Int) {
|
||||
this.order = order
|
||||
}
|
||||
|
||||
@Around("com.xyz.CommonPointcuts.businessService()") // <1>
|
||||
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any? {
|
||||
var numAttempts = 0
|
||||
var lockFailureException: PessimisticLockingFailureException
|
||||
do {
|
||||
numAttempts++
|
||||
try {
|
||||
return pjp.proceed()
|
||||
} catch (ex: PessimisticLockingFailureException) {
|
||||
lockFailureException = ex
|
||||
}
|
||||
|
||||
} while (numAttempts <= this.maxRetries)
|
||||
throw lockFailureException
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> References the `businessService` named pointcut defined in xref:core/aop/ataspectj/pointcuts.adoc#aop-common-pointcuts[Sharing Named Pointcut Definitions].
|
||||
======
|
||||
|
||||
Note that the aspect implements the `Ordered` interface so that we can set the precedence of
|
||||
the aspect higher than the transaction advice (we want a fresh transaction each time we
|
||||
@@ -30,15 +114,70 @@ we have exhausted all of our retry attempts.
|
||||
|
||||
The corresponding Spring configuration follows:
|
||||
|
||||
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:aspectj-autoproxy/>
|
||||
|
||||
<bean id="concurrentOperationExecutor"
|
||||
class="com.xyz.service.impl.ConcurrentOperationExecutor">
|
||||
<property name="maxRetries" value="3"/>
|
||||
<property name="order" value="100"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
To refine the aspect so that it retries only idempotent operations, we might define the following
|
||||
`Idempotent` annotation:
|
||||
|
||||
include-code::./service/Idempotent[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
// marker annotation
|
||||
public @interface Idempotent {
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
// marker annotation
|
||||
annotation class Idempotent
|
||||
----
|
||||
======
|
||||
|
||||
We can then use the annotation to annotate the implementation of service operations. The change
|
||||
to the aspect to retry only idempotent operations involves refining the pointcut
|
||||
expression so that only `@Idempotent` operations match, as follows:
|
||||
|
||||
include-code::./service/SampleService[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Around("execution(* com.xyz..service.*.*(..)) && " +
|
||||
"@annotation(com.xyz.service.Idempotent)")
|
||||
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Around("execution(* com.xyz..service.*.*(..)) && " +
|
||||
"@annotation(com.xyz.service.Idempotent)")
|
||||
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any? {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
val usageTracked = context.getBean<UsageTracked>("myService")
|
||||
val usageTracked = context.getBean("myService", UsageTracked.class)
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ Kotlin::
|
||||
When used as a marker interface in this way, Spring configures new instances of the
|
||||
annotated type (`Account`, in this case) by using a bean definition (typically
|
||||
prototype-scoped) with the same name as the fully-qualified type name
|
||||
(`com.xyz.domain.Account`). Since the default name for a bean defined via XML is the
|
||||
(`com.xyz.domain.Account`). Since the default name for a bean is the
|
||||
fully-qualified name of its type, a convenient way to declare the prototype definition
|
||||
is to omit the `id` attribute, as the following example shows:
|
||||
|
||||
@@ -177,10 +177,41 @@ either use a build-time Ant or Maven task to do this (see, for example, the
|
||||
{aspectj-docs-devguide}/antTasks.html[AspectJ Development
|
||||
Environment Guide]) or load-time weaving (see xref:core/aop/using-aspectj.adoc#aop-aj-ltw[Load-time Weaving with AspectJ in the Spring Framework]). The
|
||||
`AnnotationBeanConfigurerAspect` itself needs to be configured by Spring (in order to obtain
|
||||
a reference to the bean factory that is to be used to configure new objects). You can define
|
||||
the related configuration as follows:
|
||||
a reference to the bean factory that is to be used to configure new objects). If you
|
||||
use Java-based configuration, you can add `@EnableSpringConfigured` to any
|
||||
`@Configuration` class, as follows:
|
||||
|
||||
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableSpringConfigured
|
||||
public class AppConfig {
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableSpringConfigured
|
||||
class AppConfig {
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
If you prefer XML based configuration, the Spring
|
||||
xref:core/appendix/xsd-schemas.adoc#context[`context` namespace]
|
||||
defines a convenient `context:spring-configured` element, which you can use as follows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<context:spring-configured/>
|
||||
----
|
||||
|
||||
Instances of `@Configurable` objects created before the aspect has been configured
|
||||
result in a message being issued to the debug log and no configuration of the
|
||||
@@ -752,9 +783,52 @@ adding one line. (Note that you almost certainly need to use an
|
||||
`ApplicationContext` as your Spring container -- typically, a `BeanFactory` is not
|
||||
enough because the LTW support uses `BeanFactoryPostProcessors`.)
|
||||
|
||||
To enable the Spring Framework's LTW support, you need to configure a `LoadTimeWeaver` as follows:
|
||||
To enable the Spring Framework's LTW support, you need to configure a `LoadTimeWeaver`,
|
||||
which typically is done by using the `@EnableLoadTimeWeaving` annotation, as follows:
|
||||
|
||||
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableLoadTimeWeaving
|
||||
public class AppConfig {
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableLoadTimeWeaving
|
||||
class AppConfig {
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Alternatively, if you prefer XML-based configuration, use the
|
||||
`<context:load-time-weaver/>` element. Note that the element is defined in the
|
||||
`context` namespace. The following example shows how to use `<context:load-time-weaver/>`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
https://www.springframework.org/schema/context/spring-context.xsd">
|
||||
|
||||
<context:load-time-weaver/>
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
The preceding configuration automatically defines and registers a number of LTW-specific
|
||||
infrastructure beans, such as a `LoadTimeWeaver` and an `AspectJWeavingEnabler`, for you.
|
||||
@@ -790,12 +864,63 @@ Note that the table lists only the `LoadTimeWeavers` that are autodetected when
|
||||
use the `DefaultContextLoadTimeWeaver`. You can specify exactly which `LoadTimeWeaver`
|
||||
implementation to use.
|
||||
|
||||
To configure a specific `LoadTimeWeaver`, implement the
|
||||
`LoadTimeWeavingConfigurer` interface and override the `getLoadTimeWeaver()` method
|
||||
(or use the XML equivalent).
|
||||
To specify a specific `LoadTimeWeaver` with Java configuration, implement the
|
||||
`LoadTimeWeavingConfigurer` interface and override the `getLoadTimeWeaver()` method.
|
||||
The following example specifies a `ReflectiveLoadTimeWeaver`:
|
||||
|
||||
include-code::./CustomWeaverConfiguration[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableLoadTimeWeaving
|
||||
public class AppConfig implements LoadTimeWeavingConfigurer {
|
||||
|
||||
@Override
|
||||
public LoadTimeWeaver getLoadTimeWeaver() {
|
||||
return new ReflectiveLoadTimeWeaver();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableLoadTimeWeaving
|
||||
class AppConfig : LoadTimeWeavingConfigurer {
|
||||
|
||||
override fun getLoadTimeWeaver(): LoadTimeWeaver {
|
||||
return ReflectiveLoadTimeWeaver()
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
If you use XML-based configuration, you can specify the fully qualified class name
|
||||
as the value of the `weaver-class` attribute on the `<context:load-time-weaver/>`
|
||||
element. Again, the following example specifies a `ReflectiveLoadTimeWeaver`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
https://www.springframework.org/schema/context/spring-context.xsd">
|
||||
|
||||
<context:load-time-weaver
|
||||
weaver-class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
The `LoadTimeWeaver` that is defined and registered by the configuration can be later
|
||||
retrieved from the Spring container by using the well known name, `loadTimeWeaver`.
|
||||
|
||||
@@ -326,6 +326,19 @@ However, this is not a best practice and flagging the preferred constructor with
|
||||
|
||||
In case you are working on a code base that you cannot modify, you can set the {spring-framework-api}/beans/factory/support/AbstractBeanDefinition.html#PREFERRED_CONSTRUCTORS_ATTRIBUTE[`preferredConstructors` attribute] on the related bean definition to indicate which constructor should be used.
|
||||
|
||||
[[aot.bestpractices.comlext-data-structure]]
|
||||
=== Avoid Complex Data Structure for Constructor Parameters and Properties
|
||||
|
||||
When crafting a `RootBeanDefinition` programmatically, you are not constrained in terms of types that you can use.
|
||||
For instance, you may have a custom `record` with several properties that your bean takes as a constructor argument.
|
||||
|
||||
While this works fine with the regular runtime, AOT does not know how to generate the code of your custom data structure.
|
||||
A good rule of thumb is to keep in mind that bean definitions are an abstraction on top of several models.
|
||||
Rather than using such structure, decomposing to simple types or referring to a bean that is built as such is recommended.
|
||||
|
||||
As a last resort, you can implement your own `org.springframework.aot.generate.ValueCodeGenerator$Delegate`.
|
||||
To use it, register its fully qualified name in `META-INF/spring/aot.factories` using the `Delegate` as the key.
|
||||
|
||||
[[aot.bestpractices.custom-arguments]]
|
||||
=== Avoid Creating Bean with Custom Arguments
|
||||
|
||||
|
||||
+3
-46
@@ -1,5 +1,5 @@
|
||||
[[beans-autowired-annotation-primary]]
|
||||
= Fine-tuning Annotation-based Autowiring with `@Primary` or `@Fallback`
|
||||
= Fine-tuning Annotation-based Autowiring with `@Primary`
|
||||
|
||||
Because autowiring by type may lead to multiple candidates, it is often necessary to have
|
||||
more control over the selection process. One way to accomplish this is with Spring's
|
||||
@@ -50,51 +50,8 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
Alternatively, as of 6.2, there is a `@Fallback` annotation for demarcating
|
||||
any beans other than the regular ones to be injected. If only one regular
|
||||
bean is left, it is effectively primary as well:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class MovieConfiguration {
|
||||
|
||||
@Bean
|
||||
public MovieCatalog firstMovieCatalog() { ... }
|
||||
|
||||
@Bean
|
||||
@Fallback
|
||||
public MovieCatalog secondMovieCatalog() { ... }
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class MovieConfiguration {
|
||||
|
||||
@Bean
|
||||
fun firstMovieCatalog(): MovieCatalog { ... }
|
||||
|
||||
@Bean
|
||||
@Fallback
|
||||
fun secondMovieCatalog(): MovieCatalog { ... }
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
With both variants of the preceding configuration, the following
|
||||
`MovieRecommender` is autowired with the `firstMovieCatalog`:
|
||||
With the preceding configuration, the following `MovieRecommender` is autowired with the
|
||||
`firstMovieCatalog`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
||||
+11
-14
@@ -1,13 +1,12 @@
|
||||
[[beans-autowired-annotation-qualifiers]]
|
||||
= Fine-tuning Annotation-based Autowiring with Qualifiers
|
||||
|
||||
`@Primary` and `@Fallback` are effective ways to use autowiring by type with several
|
||||
instances when one primary (or non-fallback) candidate can be determined.
|
||||
|
||||
When you need more control over the selection process, you can use Spring's `@Qualifier`
|
||||
annotation. You can associate qualifier values with specific arguments, narrowing the set
|
||||
of type matches so that a specific bean is chosen for each argument. In the simplest case,
|
||||
this can be a plain descriptive value, as shown in the following example:
|
||||
`@Primary` is an effective way to use autowiring by type with several instances when one
|
||||
primary candidate can be determined. When you need more control over the selection process,
|
||||
you can use Spring's `@Qualifier` annotation. You can associate qualifier values
|
||||
with specific arguments, narrowing the set of type matches so that a specific bean is
|
||||
chosen for each argument. In the simplest case, this can be a plain descriptive value, as
|
||||
shown in the following example:
|
||||
|
||||
--
|
||||
[tabs]
|
||||
@@ -154,17 +153,15 @@ Letting qualifier values select against target bean names, within the type-match
|
||||
candidates, does not require a `@Qualifier` annotation at the injection point.
|
||||
If there is no other resolution indicator (such as a qualifier or a primary marker),
|
||||
for a non-unique dependency situation, Spring matches the injection point name
|
||||
(that is, the field name or parameter name) against the target bean names and chooses the
|
||||
same-named candidate, if any.
|
||||
(that is, the field name or parameter name) against the target bean names and chooses
|
||||
the same-named candidate, if any (either by bean name or by associated alias).
|
||||
|
||||
Since version 6.1, this requires the `-parameters` Java compiler flag to be present.
|
||||
====
|
||||
|
||||
That said, if you intend to express annotation-driven injection by name, do not
|
||||
primarily use `@Autowired`, even if it is capable of selecting by bean name among
|
||||
type-matching candidates. Instead, use the JSR-250 `@Resource` annotation, which is
|
||||
semantically defined to identify a specific target component by its unique name, with
|
||||
the declared type being irrelevant for the matching process. `@Autowired` has rather
|
||||
As an alternative for injection by name, consider the JSR-250 `@Resource` annotation
|
||||
which is semantically defined to identify a specific target component by its unique name,
|
||||
with the declared type being irrelevant for the matching process. `@Autowired` has rather
|
||||
different semantics: After selecting candidate beans by type, the specified `String`
|
||||
qualifier value is considered within those type-selected candidates only (for example,
|
||||
matching an `account` qualifier against beans marked with the same qualifier label).
|
||||
|
||||
+2
-2
@@ -101,8 +101,8 @@ NOTE: When configuring a `PropertySourcesPlaceholderConfigurer` using JavaConfig
|
||||
|
||||
Using the above configuration ensures Spring initialization failure if any `${}`
|
||||
placeholder could not be resolved. It is also possible to use methods like
|
||||
`setPlaceholderPrefix`, `setPlaceholderSuffix`, `setValueSeparator`, or
|
||||
`setEscapeCharacter` to customize placeholders.
|
||||
`setPlaceholderPrefix`, `setPlaceholderSuffix`, or `setValueSeparator` to customize
|
||||
placeholders.
|
||||
|
||||
NOTE: Spring Boot configures by default a `PropertySourcesPlaceholderConfigurer` bean that
|
||||
will get properties from `application.properties` and `application.yml` files.
|
||||
|
||||
@@ -73,6 +73,8 @@ runtime (concurrently with live access to the factory) is not officially support
|
||||
lead to concurrent access exceptions, inconsistent state in the bean container, or both.
|
||||
====
|
||||
|
||||
|
||||
|
||||
[[beans-definition-overriding]]
|
||||
== Overriding Beans
|
||||
|
||||
@@ -81,15 +83,22 @@ already allocated. While bean overriding is possible, it makes the configuration
|
||||
to read and this feature will be deprecated in a future release.
|
||||
|
||||
To disable bean overriding altogether, you can set the `allowBeanDefinitionOverriding`
|
||||
to `false` on the `ApplicationContext` before it is refreshed. In such setup, an
|
||||
flag to `false` on the `ApplicationContext` before it is refreshed. In such setup, an
|
||||
exception is thrown if bean overriding is used.
|
||||
|
||||
By default, the container logs every bean overriding at `INFO` level so that you can
|
||||
adapt your configuration accordingly. While not recommended, you can silence those logs
|
||||
by setting the `allowBeanDefinitionOverriding` flag to `true`.
|
||||
|
||||
NOTE: We acknowledge that overriding beans in a test is convenient, and there is
|
||||
explicit support for this. For more details please refer to xref:testing/testcontext-framework/bean-overriding.adoc[this section].
|
||||
.Java-configuration
|
||||
****
|
||||
If you use Java Configuration, a corresponding `@Bean` method always silently overrides
|
||||
a scanned bean class with the same component name as long as the return type of the
|
||||
`@Bean` method matches that bean class. This simply means that the container will call
|
||||
the `@Bean` factory method in favor of any pre-declared constructor on the bean class.
|
||||
****
|
||||
|
||||
|
||||
|
||||
[[beans-beanname]]
|
||||
== Naming Beans
|
||||
|
||||
@@ -10,25 +10,33 @@ pre-instantiation of a singleton bean by marking the bean definition as being
|
||||
lazy-initialized. A lazy-initialized bean tells the IoC container to create a bean
|
||||
instance when it is first requested, rather than at startup.
|
||||
|
||||
This behavior is controlled by the `@Lazy` annotation or in XML the `lazy-init` attribute on the `<bean/>` element, as
|
||||
the following example shows:
|
||||
In XML, this behavior is controlled by the `lazy-init` attribute on the `<bean/>`
|
||||
element, as the following example shows:
|
||||
|
||||
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
|
||||
<bean name="not.lazy" class="com.something.AnotherBean"/>
|
||||
----
|
||||
|
||||
When the preceding configuration is consumed by an `ApplicationContext`, the `lazy` bean
|
||||
is not eagerly pre-instantiated when the `ApplicationContext` starts,
|
||||
whereas the `notLazy` one is eagerly pre-instantiated.
|
||||
whereas the `not.lazy` bean is eagerly pre-instantiated.
|
||||
|
||||
However, when a lazy-initialized bean is a dependency of a singleton bean that is
|
||||
not lazy-initialized, the `ApplicationContext` creates the lazy-initialized bean at
|
||||
startup, because it must satisfy the singleton's dependencies. The lazy-initialized bean
|
||||
is injected into a singleton bean elsewhere that is not lazy-initialized.
|
||||
|
||||
You can also control lazy-initialization for a set of beans by using the `@Lazy` annotation on your `@Configuration`
|
||||
annotated class or in XML using the `default-lazy-init` attribute on the `<beans/>` element, as the following example
|
||||
shows:
|
||||
You can also control lazy-initialization at the container level by using the
|
||||
`default-lazy-init` attribute on the `<beans/>` element, as the following example shows:
|
||||
|
||||
include-code::./LazyConfiguration[tag=snippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans default-lazy-init="true">
|
||||
<!-- no beans will be pre-instantiated... -->
|
||||
</beans>
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -439,7 +439,7 @@ dataSource.url=jdbc:mysql:mydb
|
||||
----
|
||||
|
||||
This example file can be used with a container definition that contains a bean called
|
||||
`dataSource` that has `driver` and `url` properties.
|
||||
`dataSource` that has `driverClassName` and `url` properties.
|
||||
|
||||
Compound property names are also supported, as long as every component of the path
|
||||
except the final property being overridden is already non-null (presumably initialized
|
||||
|
||||
+4
-41
@@ -503,47 +503,10 @@ way, navigating `@Configuration` classes and their dependencies becomes no diffe
|
||||
than the usual process of navigating interface-based code.
|
||||
--
|
||||
|
||||
|
||||
[[beans-java-startup]]
|
||||
== Influencing the Startup of `@Bean`-defined Singletons
|
||||
|
||||
If you want to influence the startup creation order of certain singleton beans, consider
|
||||
declaring some of them as `@Lazy` for creation on first access instead of on startup.
|
||||
|
||||
`@DependsOn` forces certain other beans to be initialized first, making sure that
|
||||
the specified beans are created before the current bean, beyond what the latter's
|
||||
direct dependencies imply.
|
||||
|
||||
[[beans-java-startup-background]]
|
||||
=== Background Initialization
|
||||
|
||||
As of 6.2, there is a background initialization option: `@Bean(bootstrap=BACKGROUND)`
|
||||
allows for singling out specific beans for background initialization, covering the
|
||||
entire bean creation step for each such bean on context startup.
|
||||
|
||||
Dependent beans with non-lazy injection points automatically wait for the bean instance
|
||||
to be completed. All regular background initializations are forced to complete at the end
|
||||
of context startup. Only beans additionally marked as `@Lazy` are allowed to be completed
|
||||
later (up until the first actual access).
|
||||
|
||||
Background initialization typically goes together with `@Lazy` (or `ObjectProvider`)
|
||||
injection points in dependent beans. Otherwise, the main bootstrap thread is going to
|
||||
block when an actual background-initialized bean instance needs to be injected early.
|
||||
|
||||
This form of concurrent startup applies to individual beans: if such a bean depends on
|
||||
other beans, they need to have been initialized already, either simply through being
|
||||
declared earlier or through `@DependsOn` which enforces initialization in the main
|
||||
bootstrap thread before background initialization for the affected bean is triggered.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
A `bootstrapExecutor` bean of type `Executor` must be declared for background
|
||||
bootstrapping to be actually active. Otherwise, the background markers will be ignored at
|
||||
runtime.
|
||||
|
||||
The bootstrap executor may be a bounded executor just for startup purposes or a shared
|
||||
thread pool which serves for other purposes as well.
|
||||
====
|
||||
TIP: If you want to influence the startup creation order of certain beans, consider
|
||||
declaring some of them as `@Lazy` (for creation on first access instead of on startup)
|
||||
or as `@DependsOn` certain other beans (making sure that specific other beans are
|
||||
created before the current bean, beyond what the latter's direct dependencies imply).
|
||||
|
||||
|
||||
[[beans-java-conditional]]
|
||||
|
||||
@@ -1,34 +1,219 @@
|
||||
[[expressions-beandef]]
|
||||
= Expressions in Bean Definitions
|
||||
|
||||
You can use SpEL expressions with configuration metadata for defining bean instances. In both
|
||||
cases, the syntax to define the expression is of the form `#{ <expression string> }`.
|
||||
You can use SpEL expressions with XML-based or annotation-based configuration metadata for
|
||||
defining `BeanDefinition` instances. In both cases, the syntax to define the expression is of the
|
||||
form `#{ <expression string> }`.
|
||||
|
||||
|
||||
|
||||
[[expressions-beandef-xml-based]]
|
||||
== XML Configuration
|
||||
|
||||
A property or constructor argument value can be set by using expressions, as the following
|
||||
example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<bean id="numberGuess" class="org.spring.samples.NumberGuess">
|
||||
<property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
|
||||
|
||||
<!-- other properties -->
|
||||
</bean>
|
||||
----
|
||||
|
||||
All beans in the application context are available as predefined variables with their
|
||||
common bean name. This includes standard context beans such as `environment` (of type
|
||||
`org.springframework.core.env.Environment`) as well as `systemProperties` and
|
||||
`systemEnvironment` (of type `Map<String, Object>`) for access to the runtime environment.
|
||||
|
||||
To specify a default value, you can place the `@Value` annotation on fields, methods,
|
||||
and method or constructor parameters (or XML equivalent).
|
||||
The following example shows access to the `systemProperties` bean as a SpEL variable:
|
||||
|
||||
The following example sets the default value of a field:
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<bean id="taxCalculator" class="org.spring.samples.TaxCalculator">
|
||||
<property name="defaultLocale" value="#{ systemProperties['user.region'] }"/>
|
||||
|
||||
include-code::./FieldValueTestBean[tag=snippet,indent=0]
|
||||
<!-- other properties -->
|
||||
</bean>
|
||||
----
|
||||
|
||||
Note that you do not have to prefix the predefined variable with the `#` symbol here.
|
||||
|
||||
You can also refer to other bean properties by name, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<bean id="numberGuess" class="org.spring.samples.NumberGuess">
|
||||
<property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
|
||||
|
||||
<!-- other properties -->
|
||||
</bean>
|
||||
|
||||
<bean id="shapeGuess" class="org.spring.samples.ShapeGuess">
|
||||
<property name="initialShapeSeed" value="#{ numberGuess.randomNumber }"/>
|
||||
|
||||
<!-- other properties -->
|
||||
</bean>
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[expressions-beandef-annotation-based]]
|
||||
== Annotation Configuration
|
||||
|
||||
To specify a default value, you can place the `@Value` annotation on fields, methods,
|
||||
and method or constructor parameters.
|
||||
|
||||
The following example sets the default value of a field:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class FieldValueTestBean {
|
||||
|
||||
@Value("#{ systemProperties['user.region'] }")
|
||||
private String defaultLocale;
|
||||
|
||||
public void setDefaultLocale(String defaultLocale) {
|
||||
this.defaultLocale = defaultLocale;
|
||||
}
|
||||
|
||||
public String getDefaultLocale() {
|
||||
return this.defaultLocale;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class FieldValueTestBean {
|
||||
|
||||
@Value("#{ systemProperties['user.region'] }")
|
||||
var defaultLocale: String? = null
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows the equivalent but on a property setter method:
|
||||
|
||||
include-code::./PropertyValueTestBean[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class PropertyValueTestBean {
|
||||
|
||||
private String defaultLocale;
|
||||
|
||||
@Value("#{ systemProperties['user.region'] }")
|
||||
public void setDefaultLocale(String defaultLocale) {
|
||||
this.defaultLocale = defaultLocale;
|
||||
}
|
||||
|
||||
public String getDefaultLocale() {
|
||||
return this.defaultLocale;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class PropertyValueTestBean {
|
||||
|
||||
@Value("#{ systemProperties['user.region'] }")
|
||||
var defaultLocale: String? = null
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Autowired methods and constructors can also use the `@Value` annotation, as the following
|
||||
examples show:
|
||||
|
||||
include-code::./SimpleMovieLister[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class SimpleMovieLister {
|
||||
|
||||
private MovieFinder movieFinder;
|
||||
private String defaultLocale;
|
||||
|
||||
@Autowired
|
||||
public void configure(MovieFinder movieFinder,
|
||||
@Value("#{ systemProperties['user.region'] }") String defaultLocale) {
|
||||
this.movieFinder = movieFinder;
|
||||
this.defaultLocale = defaultLocale;
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class SimpleMovieLister {
|
||||
|
||||
private lateinit var movieFinder: MovieFinder
|
||||
private lateinit var defaultLocale: String
|
||||
|
||||
@Autowired
|
||||
fun configure(movieFinder: MovieFinder,
|
||||
@Value("#{ systemProperties['user.region'] }") defaultLocale: String) {
|
||||
this.movieFinder = movieFinder
|
||||
this.defaultLocale = defaultLocale
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class MovieRecommender {
|
||||
|
||||
private String defaultLocale;
|
||||
|
||||
private CustomerPreferenceDao customerPreferenceDao;
|
||||
|
||||
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao,
|
||||
@Value("#{systemProperties['user.country']}") String defaultLocale) {
|
||||
this.customerPreferenceDao = customerPreferenceDao;
|
||||
this.defaultLocale = defaultLocale;
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class MovieRecommender(private val customerPreferenceDao: CustomerPreferenceDao,
|
||||
@Value("#{systemProperties['user.country']}") private val defaultLocale: String) {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
include-code::./MovieRecommender[tag=snippet,indent=0]
|
||||
|
||||
You can also refer to other bean properties by name, as the following example shows:
|
||||
|
||||
include-code::./ShapeGuess[tag=snippet,indent=0]
|
||||
|
||||
@@ -516,7 +516,7 @@ following kinds of expressions cannot be compiled.
|
||||
|
||||
* Expressions involving assignment
|
||||
* Expressions relying on the conversion service
|
||||
* Expressions using custom resolvers
|
||||
* Expressions using custom resolvers or accessors
|
||||
* Expressions using overloaded operators
|
||||
* Expressions using array construction syntax
|
||||
* Expressions using selection or projection
|
||||
|
||||
+1
-60
@@ -1,7 +1,7 @@
|
||||
[[expressions-operator-safe-navigation]]
|
||||
= Safe Navigation Operator
|
||||
|
||||
The safe navigation operator (`?.`) is used to avoid a `NullPointerException` and comes
|
||||
The safe navigation operator (`?`) is used to avoid a `NullPointerException` and comes
|
||||
from the https://www.groovy-lang.org/operators.html#_safe_navigation_operator[Groovy]
|
||||
language. Typically, when you have a reference to an object, you might need to verify
|
||||
that it is not `null` before accessing methods or properties of the object. To avoid
|
||||
@@ -81,65 +81,6 @@ For example, the expression `#calculator?.max(4, 2)` evaluates to `null` if the
|
||||
`max(int, int)` method will be invoked on the `#calculator`.
|
||||
====
|
||||
|
||||
[[expressions-operator-safe-navigation-indexing]]
|
||||
== Safe Index Access
|
||||
|
||||
Since Spring Framework 6.2, the Spring Expression Language supports safe navigation for
|
||||
indexing into the following types of structures.
|
||||
|
||||
* xref:core/expressions/language-ref/properties-arrays.adoc#expressions-indexing-arrays-and-collections[arrays and collections]
|
||||
* xref:core/expressions/language-ref/properties-arrays.adoc#expressions-indexing-strings[strings]
|
||||
* xref:core/expressions/language-ref/properties-arrays.adoc#expressions-indexing-maps[maps]
|
||||
* xref:core/expressions/language-ref/properties-arrays.adoc#expressions-indexing-objects[objects]
|
||||
* xref:core/expressions/language-ref/properties-arrays.adoc#expressions-indexing-custom[custom]
|
||||
|
||||
The following example shows how to use the safe navigation operator for indexing into
|
||||
a list (`?.[]`).
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
IEEE society = new IEEE();
|
||||
EvaluationContext context = new StandardEvaluationContext(society);
|
||||
|
||||
// evaluates to Inventor("Nikola Tesla")
|
||||
Inventor inventor = parser.parseExpression("members?.[0]") // <1>
|
||||
.getValue(context, Inventor.class);
|
||||
|
||||
society.members = null;
|
||||
|
||||
// evaluates to null - does not throw an exception
|
||||
inventor = parser.parseExpression("members?.[0]") // <2>
|
||||
.getValue(context, Inventor.class);
|
||||
----
|
||||
<1> Use null-safe index operator on a non-null `members` list
|
||||
<2> Use null-safe index operator on a null `members` list
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val society = IEEE()
|
||||
val context = StandardEvaluationContext(society)
|
||||
|
||||
// evaluates to Inventor("Nikola Tesla")
|
||||
var inventor = parser.parseExpression("members?.[0]") // <1>
|
||||
.getValue(context, Inventor::class.java)
|
||||
|
||||
society.members = null
|
||||
|
||||
// evaluates to null - does not throw an exception
|
||||
inventor = parser.parseExpression("members?.[0]") // <2>
|
||||
.getValue(context, Inventor::class.java)
|
||||
----
|
||||
<1> Use null-safe index operator on a non-null `members` list
|
||||
<2> Use null-safe index operator on a null `members` list
|
||||
======
|
||||
|
||||
[[expressions-operator-safe-navigation-selection-and-projection]]
|
||||
== Safe Collection Selection and Projection
|
||||
|
||||
+26
-248
@@ -1,25 +1,11 @@
|
||||
[[expressions-properties-arrays]]
|
||||
= Properties, Arrays, Lists, Maps, and Indexers
|
||||
|
||||
The Spring Expression Language provides support for navigating object graphs and indexing
|
||||
into various structures.
|
||||
|
||||
NOTE: Numerical index values are zero-based, such as when accessing the n^th^ element of
|
||||
an array in Java.
|
||||
|
||||
TIP: See the xref:core/expressions/language-ref/operator-safe-navigation.adoc[Safe Navigation Operator]
|
||||
section for details on how to navigate object graphs and index into various structures
|
||||
using the null-safe operator.
|
||||
|
||||
[[expressions-property-navigation]]
|
||||
== Property Navigation
|
||||
|
||||
You can navigate property references within an object graph by using a period to indicate
|
||||
a nested property value. The instances of the `Inventor` class, `pupin` and `tesla`, were
|
||||
populated with data listed in the
|
||||
xref:core/expressions/example-classes.adoc[Classes used in the examples] section. To
|
||||
navigate _down_ the object graph and get Tesla's year of birth and Pupin's city of birth,
|
||||
we use the following expressions:
|
||||
Navigating with property references is easy. To do so, use a period to indicate a nested
|
||||
property value. The instances of the `Inventor` class, `pupin` and `tesla`, were
|
||||
populated with data listed in the xref:core/expressions/example-classes.adoc[Classes used in the examples]
|
||||
section. To navigate "down" the object graph and get Tesla's year of birth and
|
||||
Pupin's city of birth, we use the following expressions:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -30,7 +16,6 @@ Java::
|
||||
// evaluates to 1856
|
||||
int year = (Integer) parser.parseExpression("birthdate.year + 1900").getValue(context);
|
||||
|
||||
// evaluates to "Smiljan"
|
||||
String city = (String) parser.parseExpression("placeOfBirth.city").getValue(context);
|
||||
----
|
||||
|
||||
@@ -41,7 +26,6 @@ Kotlin::
|
||||
// evaluates to 1856
|
||||
val year = parser.parseExpression("birthdate.year + 1900").getValue(context) as Int
|
||||
|
||||
// evaluates to "Smiljan"
|
||||
val city = parser.parseExpression("placeOfBirth.city").getValue(context) as String
|
||||
----
|
||||
======
|
||||
@@ -55,20 +39,8 @@ method invocations -- for example, `getPlaceOfBirth().getCity()` instead of
|
||||
`placeOfBirth.city`.
|
||||
====
|
||||
|
||||
[[expressions-indexing-arrays-and-collections]]
|
||||
== Indexing into Arrays and Collections
|
||||
|
||||
The n^th^ element of an array or collection (for example, a `Set` or `List`) can be
|
||||
obtained by using square bracket notation, as the following example shows.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
If the indexed collection is a `java.util.List`, the n^th^ element will be accessed
|
||||
directly via `list.get(n)`.
|
||||
|
||||
For any other type of `Collection`, the n^th^ element will be accessed by iterating over
|
||||
the collection using its `Iterator` and returning the n^th^ element encountered.
|
||||
====
|
||||
The contents of arrays and lists are obtained by using square bracket notation, as the
|
||||
following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -91,8 +63,7 @@ Java::
|
||||
String name = parser.parseExpression("members[0].name").getValue(
|
||||
context, ieee, String.class);
|
||||
|
||||
// List and Array Indexing
|
||||
|
||||
// List and Array navigation
|
||||
// evaluates to "Wireless communication"
|
||||
String invention = parser.parseExpression("members[0].inventions[6]").getValue(
|
||||
context, ieee, String.class);
|
||||
@@ -117,22 +88,16 @@ Kotlin::
|
||||
val name = parser.parseExpression("members[0].name").getValue(
|
||||
context, ieee, String::class.java)
|
||||
|
||||
// List and Array Indexing
|
||||
|
||||
// List and Array navigation
|
||||
// evaluates to "Wireless communication"
|
||||
val invention = parser.parseExpression("members[0].inventions[6]").getValue(
|
||||
context, ieee, String::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
[[expressions-indexing-strings]]
|
||||
== Indexing into Strings
|
||||
|
||||
The n^th^ character of a string can be obtained by specifying the index within square
|
||||
brackets, as demonstrated in the following example.
|
||||
|
||||
NOTE: The n^th^ character of a string will evaluate to a `java.lang.String`, not a
|
||||
`java.lang.Character`.
|
||||
The contents of maps are obtained by specifying the literal key value within the
|
||||
brackets. In the following example, because keys for the `officers` map are strings, we can specify
|
||||
string literals:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -140,225 +105,38 @@ Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// evaluates to "T" (8th letter of "Nikola Tesla")
|
||||
String character = parser.parseExpression("members[0].name[7]")
|
||||
.getValue(societyContext, String.class);
|
||||
----
|
||||
// Officer's Dictionary
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// evaluates to "T" (8th letter of "Nikola Tesla")
|
||||
val character = parser.parseExpression("members[0].name[7]")
|
||||
.getValue(societyContext, String::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
[[expressions-indexing-maps]]
|
||||
== Indexing into Maps
|
||||
|
||||
The contents of maps are obtained by specifying the key value within square brackets. In
|
||||
the following example, because keys for the `officers` map are strings, we can specify
|
||||
string literals such as `'president'`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// Officer's Map
|
||||
|
||||
// evaluates to Inventor("Pupin")
|
||||
Inventor pupin = parser.parseExpression("officers['president']")
|
||||
.getValue(societyContext, Inventor.class);
|
||||
Inventor pupin = parser.parseExpression("officers['president']").getValue(
|
||||
societyContext, Inventor.class);
|
||||
|
||||
// evaluates to "Idvor"
|
||||
String city = parser.parseExpression("officers['president'].placeOfBirth.city")
|
||||
.getValue(societyContext, String.class);
|
||||
|
||||
String countryExpression = "officers['advisors'][0].placeOfBirth.country";
|
||||
String city = parser.parseExpression("officers['president'].placeOfBirth.city").getValue(
|
||||
societyContext, String.class);
|
||||
|
||||
// setting values
|
||||
parser.parseExpression(countryExpression)
|
||||
.setValue(societyContext, "Croatia");
|
||||
|
||||
// evaluates to "Croatia"
|
||||
String country = parser.parseExpression(countryExpression)
|
||||
.getValue(societyContext, String.class);
|
||||
parser.parseExpression("officers['advisors'][0].placeOfBirth.country").setValue(
|
||||
societyContext, "Croatia");
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// Officer's Map
|
||||
// Officer's Dictionary
|
||||
|
||||
// evaluates to Inventor("Pupin")
|
||||
val pupin = parser.parseExpression("officers['president']")
|
||||
.getValue(societyContext, Inventor::class.java)
|
||||
val pupin = parser.parseExpression("officers['president']").getValue(
|
||||
societyContext, Inventor::class.java)
|
||||
|
||||
// evaluates to "Idvor"
|
||||
val city = parser.parseExpression("officers['president'].placeOfBirth.city")
|
||||
.getValue(societyContext, String::class.java)
|
||||
|
||||
val countryExpression = "officers['advisors'][0].placeOfBirth.country"
|
||||
val city = parser.parseExpression("officers['president'].placeOfBirth.city").getValue(
|
||||
societyContext, String::class.java)
|
||||
|
||||
// setting values
|
||||
parser.parseExpression(countryExpression)
|
||||
.setValue(societyContext, "Croatia")
|
||||
|
||||
// evaluates to "Croatia"
|
||||
val country = parser.parseExpression(countryExpression)
|
||||
.getValue(societyContext, String::class.java)
|
||||
parser.parseExpression("officers['advisors'][0].placeOfBirth.country").setValue(
|
||||
societyContext, "Croatia")
|
||||
----
|
||||
======
|
||||
|
||||
[[expressions-indexing-objects]]
|
||||
== Indexing into Objects
|
||||
|
||||
A property of an object can be obtained by specifying the name of the property within
|
||||
square brackets. This is analogous to accessing the value of a map based on its key. The
|
||||
following example demonstrates how to _index_ into an object to retrieve a specific
|
||||
property.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// Create an inventor to use as the root context object.
|
||||
Inventor tesla = new Inventor("Nikola Tesla");
|
||||
|
||||
// evaluates to "Nikola Tesla"
|
||||
String name = parser.parseExpression("#root['name']")
|
||||
.getValue(context, tesla, String.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// Create an inventor to use as the root context object.
|
||||
val tesla = Inventor("Nikola Tesla")
|
||||
|
||||
// evaluates to "Nikola Tesla"
|
||||
val name = parser.parseExpression("#root['name']")
|
||||
.getValue(context, tesla, String::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
[[expressions-indexing-custom]]
|
||||
== Indexing into Custom Structures
|
||||
|
||||
Since Spring Framework 6.2, the Spring Expression Language supports indexing into custom
|
||||
structures by allowing developers to implement and register an `IndexAccessor` with the
|
||||
`EvaluationContext`. If you would like to support
|
||||
xref:core/expressions/evaluation.adoc#expressions-spel-compilation[compilation] of
|
||||
expressions that rely on a custom index accessor, that index accessor must implement the
|
||||
`CompilableIndexAccessor` SPI.
|
||||
|
||||
To support common use cases, Spring provides a built-in `ReflectiveIndexAccessor` which
|
||||
is a flexible `IndexAccessor` that uses reflection to read from and optionally write to
|
||||
an indexed structure of a target object. The indexed structure can be accessed through a
|
||||
`public` read-method (when being read) or a `public` write-method (when being written).
|
||||
The relationship between the read-method and write-method is based on a convention that
|
||||
is applicable for typical implementations of indexed structures.
|
||||
|
||||
NOTE: `ReflectiveIndexAccessor` also implements `CompilableIndexAccessor` in order to
|
||||
support xref:core/expressions/evaluation.adoc#expressions-spel-compilation[compilation]
|
||||
to bytecode for read access. Note, however, that the configured read-method must be
|
||||
invokable via a `public` class or `public` interface for compilation to succeed.
|
||||
|
||||
The following code listings define a `Color` enum and `FruitMap` type that behaves like a
|
||||
map but does not implement the `java.util.Map` interface. Thus, if you want to index into
|
||||
a `FruitMap` within a SpEL expression, you will need to register an `IndexAccessor`.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
package example;
|
||||
|
||||
public enum Color {
|
||||
RED, ORANGE, YELLOW
|
||||
}
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public class FruitMap {
|
||||
|
||||
private final Map<Color, String> map = new HashMap<>();
|
||||
|
||||
public FruitMap() {
|
||||
this.map.put(Color.RED, "cherry");
|
||||
this.map.put(Color.ORANGE, "orange");
|
||||
this.map.put(Color.YELLOW, "banana");
|
||||
}
|
||||
|
||||
public String getFruit(Color color) {
|
||||
return this.map.get(color);
|
||||
}
|
||||
|
||||
public void setFruit(Color color, String fruit) {
|
||||
this.map.put(color, fruit);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
A read-only `IndexAccessor` for `FruitMap` can be created via `new
|
||||
ReflectiveIndexAccessor(FruitMap.class, Color.class, "getFruit")`. With that accessor
|
||||
registered and a `FruitMap` registered as a variable named `#fruitMap`, the SpEL
|
||||
expression `#fruitMap[T(example.Color).RED]` will evaluate to `"cherry"`.
|
||||
|
||||
A read-write `IndexAccessor` for `FruitMap` can be created via `new
|
||||
ReflectiveIndexAccessor(FruitMap.class, Color.class, "getFruit", "setFruit")`. With that
|
||||
accessor registered and a `FruitMap` registered as a variable named `#fruitMap`, the SpEL
|
||||
expression `#fruitMap[T(example.Color).RED] = 'strawberry'` can be used to change the
|
||||
fruit mapping for the color red from `"cherry"` to `"strawberry"`.
|
||||
|
||||
The following example demonstrates how to register a `ReflectiveIndexAccessor` to index
|
||||
into a `FruitMap` and then index into the `FruitMap` within a SpEL expression.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
// Create a ReflectiveIndexAccessor for FruitMap
|
||||
IndexAccessor fruitMapAccessor = new ReflectiveIndexAccessor(
|
||||
FruitMap.class, Color.class, "getFruit", "setFruit");
|
||||
|
||||
// Register the IndexAccessor for FruitMap
|
||||
context.addIndexAccessor(fruitMapAccessor);
|
||||
|
||||
// Register the fruitMap variable
|
||||
context.setVariable("fruitMap", new FruitMap());
|
||||
|
||||
// evaluates to "cherry"
|
||||
String fruit = parser.parseExpression("#fruitMap[T(example.Color).RED]")
|
||||
.getValue(context, String.class);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
// Create a ReflectiveIndexAccessor for FruitMap
|
||||
val fruitMapAccessor = ReflectiveIndexAccessor(
|
||||
FruitMap::class.java, Color::class.java, "getFruit", "setFruit")
|
||||
|
||||
// Register the IndexAccessor for FruitMap
|
||||
context.addIndexAccessor(fruitMapAccessor)
|
||||
|
||||
// Register the fruitMap variable
|
||||
context.setVariable("fruitMap", FruitMap())
|
||||
|
||||
// evaluates to "cherry"
|
||||
val fruit = parser.parseExpression("#fruitMap[T(example.Color).RED]")
|
||||
.getValue(context, String::class.java)
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
Expression templates allow mixing literal text with one or more evaluation blocks.
|
||||
Each evaluation block is delimited with prefix and suffix characters that you can
|
||||
define. A common choice is to use `+#{ }+` as the delimiters, as the following example
|
||||
define. A common choice is to use `#{ }` as the delimiters, as the following example
|
||||
shows:
|
||||
|
||||
[tabs]
|
||||
@@ -32,7 +32,7 @@ Kotlin::
|
||||
======
|
||||
|
||||
The string is evaluated by concatenating the literal text `'random number is '` with the
|
||||
result of evaluating the expression inside the `+#{ }+` delimiters (in this case, the
|
||||
result of evaluating the expression inside the `#{ }` delimiters (in this case, the
|
||||
result of calling that `random()` method). The second argument to the `parseExpression()`
|
||||
method is of the type `ParserContext`. The `ParserContext` interface is used to influence
|
||||
how the expression is parsed in order to support the expression templating functionality.
|
||||
|
||||
@@ -299,7 +299,7 @@ Java::
|
||||
public class AppConfig {
|
||||
|
||||
@Bean
|
||||
public MethodValidationPostProcessor validationPostProcessor() {
|
||||
public static MethodValidationPostProcessor validationPostProcessor() {
|
||||
return new MethodValidationPostProcessor();
|
||||
}
|
||||
}
|
||||
@@ -341,7 +341,7 @@ xref:web/webflux/ann-rest-exceptions.adoc[Error Responses] sections.
|
||||
=== Method Validation Exceptions
|
||||
|
||||
By default, `jakarta.validation.ConstraintViolationException` is raised with the set of
|
||||
``ConstraintViolation``s returned by `jakarata.validation.Validator`. As an alternative,
|
||||
``ConstraintViolation``s returned by `jakarta.validation.Validator`. As an alternative,
|
||||
you can have `MethodValidationException` raised instead with ``ConstraintViolation``s
|
||||
adapted to `MessageSourceResolvable` errors. To enable set the following flag:
|
||||
|
||||
@@ -357,7 +357,7 @@ Java::
|
||||
public class AppConfig {
|
||||
|
||||
@Bean
|
||||
public MethodValidationPostProcessor validationPostProcessor() {
|
||||
public static MethodValidationPostProcessor validationPostProcessor() {
|
||||
MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
|
||||
processor.setAdaptConstraintViolations(true);
|
||||
return processor;
|
||||
@@ -438,7 +438,7 @@ A `ConstraintViolation` on `Person.name()` is adapted to a `FieldError` with the
|
||||
To customize the default message, you can add properties to
|
||||
xref:core/beans/context-introduction.adoc#context-functionality-messagesource[MessageSource]
|
||||
resource bundles using any of the above errors codes and message arguments. Note also that the
|
||||
message argument `"name"` is itself a `MessageSourceResolvable` with error codes
|
||||
message argument `"name"` is itself a `MessagreSourceResolvable` with error codes
|
||||
`"student.name"` and `"name"` and can customized too. For example:
|
||||
|
||||
Properties::
|
||||
|
||||
+99
-2
@@ -11,9 +11,106 @@ formatters manually with the help of:
|
||||
* `org.springframework.format.datetime.standard.DateTimeFormatterRegistrar`
|
||||
* `org.springframework.format.datetime.DateFormatterRegistrar`
|
||||
|
||||
For example, the following configuration registers a global `yyyyMMdd` format:
|
||||
For example, the following Java configuration registers a global `yyyyMMdd` format:
|
||||
|
||||
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
|
||||
@Bean
|
||||
public FormattingConversionService conversionService() {
|
||||
|
||||
// Use the DefaultFormattingConversionService but do not register defaults
|
||||
DefaultFormattingConversionService conversionService =
|
||||
new DefaultFormattingConversionService(false);
|
||||
|
||||
// Ensure @NumberFormat is still supported
|
||||
conversionService.addFormatterForFieldAnnotation(
|
||||
new NumberFormatAnnotationFormatterFactory());
|
||||
|
||||
// Register JSR-310 date conversion with a specific global format
|
||||
DateTimeFormatterRegistrar dateTimeRegistrar = new DateTimeFormatterRegistrar();
|
||||
dateTimeRegistrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"));
|
||||
dateTimeRegistrar.registerFormatters(conversionService);
|
||||
|
||||
// Register date conversion with a specific global format
|
||||
DateFormatterRegistrar dateRegistrar = new DateFormatterRegistrar();
|
||||
dateRegistrar.setFormatter(new DateFormatter("yyyyMMdd"));
|
||||
dateRegistrar.registerFormatters(conversionService);
|
||||
|
||||
return conversionService;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class AppConfig {
|
||||
|
||||
@Bean
|
||||
fun conversionService(): FormattingConversionService {
|
||||
// Use the DefaultFormattingConversionService but do not register defaults
|
||||
return DefaultFormattingConversionService(false).apply {
|
||||
|
||||
// Ensure @NumberFormat is still supported
|
||||
addFormatterForFieldAnnotation(NumberFormatAnnotationFormatterFactory())
|
||||
|
||||
// Register JSR-310 date conversion with a specific global format
|
||||
val dateTimeRegistrar = DateTimeFormatterRegistrar()
|
||||
dateTimeRegistrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"))
|
||||
dateTimeRegistrar.registerFormatters(this)
|
||||
|
||||
// Register date conversion with a specific global format
|
||||
val dateRegistrar = DateFormatterRegistrar()
|
||||
dateRegistrar.setFormatter(DateFormatter("yyyyMMdd"))
|
||||
dateRegistrar.registerFormatters(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
If you prefer XML-based configuration, you can use a
|
||||
`FormattingConversionServiceFactoryBean`. The following example shows how to do so:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
|
||||
<property name="registerDefaultFormatters" value="false" />
|
||||
<property name="formatters">
|
||||
<set>
|
||||
<bean class="org.springframework.format.number.NumberFormatAnnotationFormatterFactory" />
|
||||
</set>
|
||||
</property>
|
||||
<property name="formatterRegistrars">
|
||||
<set>
|
||||
<bean class="org.springframework.format.datetime.standard.DateTimeFormatterRegistrar">
|
||||
<property name="dateFormatter">
|
||||
<bean class="org.springframework.format.datetime.standard.DateTimeFormatterFactoryBean">
|
||||
<property name="pattern" value="yyyyMMdd"/>
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
</set>
|
||||
</property>
|
||||
</bean>
|
||||
</beans>
|
||||
----
|
||||
|
||||
Note there are extra considerations when configuring date and time formats in web
|
||||
applications. Please see
|
||||
|
||||
@@ -46,9 +46,47 @@ To configure a `DriverManagerDataSource`:
|
||||
for the correct value.)
|
||||
. Provide a username and a password to connect to the database.
|
||||
|
||||
The following example shows how to configure a `DriverManagerDataSource`:
|
||||
The following example shows how to configure a `DriverManagerDataSource` in Java:
|
||||
|
||||
include-code::./DriverManagerDataSourceConfiguration[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
DriverManagerDataSource dataSource = new DriverManagerDataSource();
|
||||
dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
|
||||
dataSource.setUrl("jdbc:hsqldb:hsql://localhost:");
|
||||
dataSource.setUsername("sa");
|
||||
dataSource.setPassword("");
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val dataSource = DriverManagerDataSource().apply {
|
||||
setDriverClassName("org.hsqldb.jdbcDriver")
|
||||
url = "jdbc:hsqldb:hsql://localhost:"
|
||||
username = "sa"
|
||||
password = ""
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows the corresponding XML configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
|
||||
<property name="driverClassName" value="${jdbc.driverClassName}"/>
|
||||
<property name="url" value="${jdbc.url}"/>
|
||||
<property name="username" value="${jdbc.username}"/>
|
||||
<property name="password" value="${jdbc.password}"/>
|
||||
</bean>
|
||||
|
||||
<context:property-placeholder location="jdbc.properties"/>
|
||||
----
|
||||
|
||||
The next two examples show the basic connectivity and configuration for DBCP and C3P0.
|
||||
To learn about more options that help control the pooling features, see the product
|
||||
@@ -56,11 +94,32 @@ documentation for the respective connection pooling implementations.
|
||||
|
||||
The following example shows DBCP configuration:
|
||||
|
||||
include-code::./BasicDataSourceConfiguration[tag=snippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
|
||||
<property name="driverClassName" value="${jdbc.driverClassName}"/>
|
||||
<property name="url" value="${jdbc.url}"/>
|
||||
<property name="username" value="${jdbc.username}"/>
|
||||
<property name="password" value="${jdbc.password}"/>
|
||||
</bean>
|
||||
|
||||
<context:property-placeholder location="jdbc.properties"/>
|
||||
----
|
||||
|
||||
The following example shows C3P0 configuration:
|
||||
|
||||
include-code::./ComboPooledDataSourceConfiguration[tag=snippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
|
||||
<property name="driverClass" value="${jdbc.driverClassName}"/>
|
||||
<property name="jdbcUrl" value="${jdbc.url}"/>
|
||||
<property name="user" value="${jdbc.username}"/>
|
||||
<property name="password" value="${jdbc.password}"/>
|
||||
</bean>
|
||||
|
||||
<context:property-placeholder location="jdbc.properties"/>
|
||||
----
|
||||
|
||||
|
||||
[[jdbc-DataSourceUtils]]
|
||||
== Using `DataSourceUtils`
|
||||
|
||||
@@ -339,7 +339,7 @@ Kotlin::
|
||||
|
||||
More sophisticated stored procedure support is xref:data-access/jdbc/object.adoc#jdbc-StoredProcedure[covered later].
|
||||
|
||||
[[jdbc-jdbctemplate-idioms]]
|
||||
[[jdbc-JdbcTemplate-idioms]]
|
||||
=== `JdbcTemplate` Best Practices
|
||||
|
||||
Instances of the `JdbcTemplate` class are thread-safe, once configured. This is
|
||||
@@ -352,23 +352,147 @@ A common practice when using the `JdbcTemplate` class (and the associated
|
||||
xref:data-access/jdbc/core.adoc#jdbc-NamedParameterJdbcTemplate[`NamedParameterJdbcTemplate`] class) is to
|
||||
configure a `DataSource` in your Spring configuration file and then dependency-inject
|
||||
that shared `DataSource` bean into your DAO classes. The `JdbcTemplate` is created in
|
||||
the setter for the `DataSource` or in the constructor. This leads to DAOs that resemble the following:
|
||||
the setter for the `DataSource`. This leads to DAOs that resemble the following:
|
||||
|
||||
include-code::./JdbcCorporateEventDao[tag=snippet,indent=0]
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
public class JdbcCorporateEventDao implements CorporateEventDao {
|
||||
|
||||
The following example shows the corresponding configuration:
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
include-code::./JdbcCorporateEventDaoConfiguration[tag=snippet,indent=0]
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
// JDBC-backed implementations of the methods on the CorporateEventDao follow...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
class JdbcCorporateEventDao(dataSource: DataSource) : CorporateEventDao {
|
||||
|
||||
private val jdbcTemplate = JdbcTemplate(dataSource)
|
||||
|
||||
// JDBC-backed implementations of the methods on the CorporateEventDao follow...
|
||||
}
|
||||
----
|
||||
======
|
||||
--
|
||||
|
||||
The following example shows the corresponding XML configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
https://www.springframework.org/schema/context/spring-context.xsd">
|
||||
|
||||
<bean id="corporateEventDao" class="com.example.JdbcCorporateEventDao">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
</bean>
|
||||
|
||||
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
|
||||
<property name="driverClassName" value="${jdbc.driverClassName}"/>
|
||||
<property name="url" value="${jdbc.url}"/>
|
||||
<property name="username" value="${jdbc.username}"/>
|
||||
<property name="password" value="${jdbc.password}"/>
|
||||
</bean>
|
||||
|
||||
<context:property-placeholder location="jdbc.properties"/>
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
An alternative to explicit configuration is to use component-scanning and annotation
|
||||
support for dependency injection. In this case, you can annotate the class with `@Repository`
|
||||
(which makes it a candidate for component-scanning). The following example shows how to do so:
|
||||
(which makes it a candidate for component-scanning) and annotate the `DataSource` setter
|
||||
method with `@Autowired`. The following example shows how to do so:
|
||||
|
||||
include-code::./JdbcCorporateEventRepository[tag=snippet,indent=0]
|
||||
--
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Repository // <1>
|
||||
public class JdbcCorporateEventDao implements CorporateEventDao {
|
||||
|
||||
The following example shows the corresponding configuration:
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
include-code::./JdbcCorporateEventRepositoryConfiguration[tag=snippet,indent=0]
|
||||
@Autowired // <2>
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.jdbcTemplate = new JdbcTemplate(dataSource); // <3>
|
||||
}
|
||||
|
||||
// JDBC-backed implementations of the methods on the CorporateEventDao follow...
|
||||
}
|
||||
----
|
||||
<1> Annotate the class with `@Repository`.
|
||||
<2> Annotate the `DataSource` setter method with `@Autowired`.
|
||||
<3> Create a new `JdbcTemplate` with the `DataSource`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Repository // <1>
|
||||
class JdbcCorporateEventDao(dataSource: DataSource) : CorporateEventDao { // <2>
|
||||
|
||||
private val jdbcTemplate = JdbcTemplate(dataSource) // <3>
|
||||
|
||||
// JDBC-backed implementations of the methods on the CorporateEventDao follow...
|
||||
}
|
||||
----
|
||||
<1> Annotate the class with `@Repository`.
|
||||
<2> Constructor injection of the `DataSource`.
|
||||
<3> Create a new `JdbcTemplate` with the `DataSource`.
|
||||
======
|
||||
--
|
||||
|
||||
|
||||
The following example shows the corresponding XML configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
https://www.springframework.org/schema/context/spring-context.xsd">
|
||||
|
||||
<!-- Scans within the base package of the application for @Component classes to configure as beans -->
|
||||
<context:component-scan base-package="org.springframework.docs.test" />
|
||||
|
||||
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
|
||||
<property name="driverClassName" value="${jdbc.driverClassName}"/>
|
||||
<property name="url" value="${jdbc.url}"/>
|
||||
<property name="username" value="${jdbc.username}"/>
|
||||
<property name="password" value="${jdbc.password}"/>
|
||||
</bean>
|
||||
|
||||
<context:property-placeholder location="jdbc.properties"/>
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
If you use Spring's `JdbcDaoSupport` class and your various JDBC-backed DAO classes
|
||||
extend from it, your sub-class inherits a `setDataSource(..)` method from the
|
||||
@@ -574,7 +698,7 @@ functionality that is present only in the `JdbcTemplate` class, you can use the
|
||||
`getJdbcOperations()` method to access the wrapped `JdbcTemplate` through the
|
||||
`JdbcOperations` interface.
|
||||
|
||||
See also xref:data-access/jdbc/core.adoc#jdbc-jdbctemplate-idioms[`JdbcTemplate` Best Practices]
|
||||
See also xref:data-access/jdbc/core.adoc#jdbc-JdbcTemplate-idioms[`JdbcTemplate` Best Practices]
|
||||
for guidelines on using the `NamedParameterJdbcTemplate` class in the context of an application.
|
||||
|
||||
|
||||
|
||||
+109
-75
@@ -16,22 +16,124 @@ lightweight nature. Benefits include ease of configuration, quick startup time,
|
||||
testability, and the ability to rapidly evolve your SQL during development.
|
||||
|
||||
|
||||
[[jdbc-embedded-database]]
|
||||
== Creating an Embedded Database
|
||||
[[jdbc-embedded-database-xml]]
|
||||
== Creating an Embedded Database by Using Spring XML
|
||||
|
||||
You can expose an embedded database instance as a bean as the following example shows:
|
||||
If you want to expose an embedded database instance as a bean in a Spring
|
||||
`ApplicationContext`, you can use the `embedded-database` tag in the `spring-jdbc` namespace:
|
||||
|
||||
include-code::./JdbcEmbeddedDatabaseConfiguration[tag=snippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<jdbc:embedded-database id="dataSource" generate-name="true">
|
||||
<jdbc:script location="classpath:schema.sql"/>
|
||||
<jdbc:script location="classpath:test-data.sql"/>
|
||||
</jdbc:embedded-database>
|
||||
----
|
||||
|
||||
The preceding configuration creates an embedded H2 database that is populated with SQL from
|
||||
The preceding configuration creates an embedded HSQL database that is populated with SQL from
|
||||
the `schema.sql` and `test-data.sql` resources in the root of the classpath. In addition, as
|
||||
a best practice, the embedded database is assigned a uniquely generated name. The
|
||||
embedded database is made available to the Spring container as a bean of type
|
||||
`javax.sql.DataSource` that can then be injected into data access objects as needed.
|
||||
|
||||
|
||||
[[jdbc-embedded-database-java]]
|
||||
== Creating an Embedded Database Programmatically
|
||||
|
||||
The `EmbeddedDatabaseBuilder` class provides a fluent API for constructing an embedded
|
||||
database programmatically. You can use this when you need to create an embedded database in a
|
||||
stand-alone environment or in a stand-alone integration test, as in the following example:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
EmbeddedDatabase db = new EmbeddedDatabaseBuilder()
|
||||
.generateUniqueName(true)
|
||||
.setType(H2)
|
||||
.setScriptEncoding("UTF-8")
|
||||
.ignoreFailedDrops(true)
|
||||
.addScript("schema.sql")
|
||||
.addScripts("user_data.sql", "country_data.sql")
|
||||
.build();
|
||||
|
||||
// perform actions against the db (EmbeddedDatabase extends javax.sql.DataSource)
|
||||
|
||||
db.shutdown()
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val db = EmbeddedDatabaseBuilder()
|
||||
.generateUniqueName(true)
|
||||
.setType(H2)
|
||||
.setScriptEncoding("UTF-8")
|
||||
.ignoreFailedDrops(true)
|
||||
.addScript("schema.sql")
|
||||
.addScripts("user_data.sql", "country_data.sql")
|
||||
.build()
|
||||
|
||||
// perform actions against the db (EmbeddedDatabase extends javax.sql.DataSource)
|
||||
|
||||
db.shutdown()
|
||||
----
|
||||
======
|
||||
|
||||
See the {spring-framework-api}/jdbc/datasource/embedded/EmbeddedDatabaseBuilder.html[javadoc for `EmbeddedDatabaseBuilder`]
|
||||
for further details on all supported options.
|
||||
|
||||
You can also use the `EmbeddedDatabaseBuilder` to create an embedded database by using Java
|
||||
configuration, as the following example shows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class DataSourceConfig {
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
return new EmbeddedDatabaseBuilder()
|
||||
.generateUniqueName(true)
|
||||
.setType(H2)
|
||||
.setScriptEncoding("UTF-8")
|
||||
.ignoreFailedDrops(true)
|
||||
.addScript("schema.sql")
|
||||
.addScripts("user_data.sql", "country_data.sql")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class DataSourceConfig {
|
||||
|
||||
@Bean
|
||||
fun dataSource(): DataSource {
|
||||
return EmbeddedDatabaseBuilder()
|
||||
.generateUniqueName(true)
|
||||
.setType(H2)
|
||||
.setScriptEncoding("UTF-8")
|
||||
.ignoreFailedDrops(true)
|
||||
.addScript("schema.sql")
|
||||
.addScripts("user_data.sql", "country_data.sql")
|
||||
.build()
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[jdbc-embedded-database-types]]
|
||||
== Selecting the Embedded Database Type
|
||||
@@ -66,74 +168,6 @@ attribute of the `embedded-database` tag to `DERBY`. If you use the builder API,
|
||||
call the `setType(EmbeddedDatabaseType)` method with `EmbeddedDatabaseType.DERBY`.
|
||||
|
||||
|
||||
[[jdbc-embedded-database-types-custom]]
|
||||
== Customizing the Embedded Database Type
|
||||
|
||||
While each supported type comes with default connection settings, it is possible
|
||||
to customize them if necessary. The following example uses H2 with a custom driver:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class DataSourceConfig {
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
return new EmbeddedDatabaseBuilder()
|
||||
.setDatabaseConfigurer(EmbeddedDatabaseConfigurers
|
||||
.customizeConfigurer(H2, this::customize))
|
||||
.addScript("schema.sql")
|
||||
.build();
|
||||
}
|
||||
|
||||
private EmbeddedDatabaseConfigurer customize(EmbeddedDatabaseConfigurer defaultConfigurer) {
|
||||
return new EmbeddedDatabaseConfigurerDelegate(defaultConfigurer) {
|
||||
@Override
|
||||
public void configureConnectionProperties(ConnectionProperties properties, String databaseName) {
|
||||
super.configureConnectionProperties(properties, databaseName);
|
||||
properties.setDriverClass(CustomDriver.class);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class DataSourceConfig {
|
||||
|
||||
@Bean
|
||||
fun dataSource(): DataSource {
|
||||
return EmbeddedDatabaseBuilder()
|
||||
.setDatabaseConfigurer(EmbeddedDatabaseConfigurers
|
||||
.customizeConfigurer(EmbeddedDatabaseType.H2) { this.customize(it) })
|
||||
.addScript("schema.sql")
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun customize(defaultConfigurer: EmbeddedDatabaseConfigurer): EmbeddedDatabaseConfigurer {
|
||||
return object : EmbeddedDatabaseConfigurerDelegate(defaultConfigurer) {
|
||||
override fun configureConnectionProperties(
|
||||
properties: ConnectionProperties,
|
||||
databaseName: String
|
||||
) {
|
||||
super.configureConnectionProperties(properties, databaseName)
|
||||
properties.setDriverClass(CustomDriver::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
[[jdbc-embedded-database-dao-testing]]
|
||||
== Testing Data Access Logic with an Embedded Database
|
||||
|
||||
@@ -143,8 +177,8 @@ can be useful for one-offs when the embedded database does not need to be reused
|
||||
classes. However, if you wish to create an embedded database that is shared within a test suite,
|
||||
consider using the xref:testing/testcontext-framework.adoc[Spring TestContext Framework] and
|
||||
configuring the embedded database as a bean in the Spring `ApplicationContext` as described
|
||||
in xref:data-access/jdbc/embedded-database-support.adoc#jdbc-embedded-database[Creating an Embedded Database].
|
||||
The following listing shows the test template:
|
||||
in xref:data-access/jdbc/embedded-database-support.adoc#jdbc-embedded-database-xml[Creating an Embedded Database by Using Spring XML] and xref:data-access/jdbc/embedded-database-support.adoc#jdbc-embedded-database-java[Creating an Embedded Database Programmatically]. The following listing
|
||||
shows the test template:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
||||
@@ -112,7 +112,7 @@ Java::
|
||||
this.actorMappingQuery = new ActorMappingQuery(dataSource);
|
||||
}
|
||||
|
||||
public Customer getCustomer(Long id) {
|
||||
public Actor getActor(Long id) {
|
||||
return actorMappingQuery.findObject(id);
|
||||
}
|
||||
----
|
||||
@@ -123,11 +123,11 @@ Kotlin::
|
||||
----
|
||||
private val actorMappingQuery = ActorMappingQuery(dataSource)
|
||||
|
||||
fun getCustomer(id: Long) = actorMappingQuery.findObject(id)
|
||||
fun getActor(id: Long) = actorMappingQuery.findObject(id)
|
||||
----
|
||||
======
|
||||
|
||||
The method in the preceding example retrieves the customer with the `id` that is passed in as the
|
||||
The method in the preceding example retrieves the actor with the `id` that is passed in as the
|
||||
only parameter. Since we want only one object to be returned, we call the `findObject` convenience
|
||||
method with the `id` as the parameter. If we had instead a query that returned a
|
||||
list of objects and took additional parameters, we would use one of the `execute`
|
||||
|
||||
@@ -272,13 +272,6 @@ is being accessed by other components (for example, calling `createEntityManager
|
||||
calls block until the background bootstrapping has completed. In particular, when you use
|
||||
Spring Data JPA, make sure to set up deferred bootstrapping for its repositories as well.
|
||||
|
||||
As of 6.2, JPA initialization is enforced before context refresh completion, waiting for
|
||||
asynchronous bootstrapping to complete by then. This makes the availability of the fully
|
||||
initialized database infrastructure predictable and allows for custom post-initialization
|
||||
logic in `ContextRefreshedEvent` listeners etc. Putting such application-level database
|
||||
initialization into `@PostConstruct` methods or the like is not recommended; this is
|
||||
better placed in `Lifecycle.start` (if applicable) or a `ContextRefreshedEvent` listener.
|
||||
|
||||
|
||||
[[orm-jpa-dao]]
|
||||
== Implementing DAOs Based on JPA: `EntityManagerFactory` and `EntityManager`
|
||||
|
||||
+5
-45
@@ -442,32 +442,12 @@ xref:data-access/transaction/declarative/rolling-back.adoc#transaction-declarati
|
||||
for further details on rollback rule semantics, patterns, and warnings
|
||||
regarding possible unintentional matches for pattern-based rollback rules.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
As of 6.2, you can globally change the default rollback behavior – for example, through
|
||||
`@EnableTransactionManagement(rollbackOn=ALL_EXCEPTIONS)`, leading to a rollback
|
||||
for all exceptions raised within a transaction, including any checked exception.
|
||||
For further customizations, `AnnotationTransactionAttributeSource` provides an
|
||||
`addDefaultRollbackRule(RollbackRuleAttribute)` method for custom default rules.
|
||||
|
||||
Note that transaction-specific rollback rules override the default behavior but
|
||||
retain the chosen default for unspecified exceptions. This is the case for
|
||||
Spring's `@Transactional` as well as JTA's `jakarta.transaction.Transactional`
|
||||
annotation.
|
||||
|
||||
Unless you rely on EJB-style business exceptions with commit behavior, it is
|
||||
advisable to switch to `ALL_EXCEPTIONS` for consistent rollback semantics even
|
||||
in case of a (potentially accidental) checked exception. Also, it is advisable
|
||||
to make that switch for Kotlin-based applications where there is no enforcement
|
||||
of checked exceptions at all.
|
||||
====
|
||||
|
||||
Currently, you cannot have explicit control over the name of a transaction, where 'name'
|
||||
means the transaction name that appears in a transaction monitor and in logging output.
|
||||
For declarative transactions, the transaction name is always the fully-qualified class
|
||||
name of the transactionally advised class + `.` + the method name. For example, if the
|
||||
name + `.` + the method name of the transactionally advised class. For example, if the
|
||||
`handlePayment(..)` method of the `BusinessService` class started a transaction, the
|
||||
name of the transaction would be `com.example.BusinessService.handlePayment`.
|
||||
name of the transaction would be: `com.example.BusinessService.handlePayment`.
|
||||
|
||||
[[tx-multiple-tx-mgrs-with-attransactional]]
|
||||
== Multiple Transaction Managers with `@Transactional`
|
||||
@@ -551,32 +531,12 @@ transaction managers, differentiated by the `order`, `account`, and `reactive-ac
|
||||
qualifiers. The default `<tx:annotation-driven>` target bean name, `transactionManager`,
|
||||
is still used if no specifically qualified `TransactionManager` bean is found.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
If all transactional methods on the same class share the same qualifier, consider
|
||||
declaring a type-level `org.springframework.beans.factory.annotation.Qualifier`
|
||||
annotation instead. If its value matches the qualifier value (or bean name) of a
|
||||
specific transaction manager, that transaction manager is going to be used for
|
||||
transaction definitions without a specific qualifier on `@Transactional` itself.
|
||||
|
||||
Such a type-level qualifier can be declared on the concrete class, applying to
|
||||
transaction definitions from a base class as well. This effectively overrides
|
||||
the default transaction manager choice for any unqualified base class methods.
|
||||
|
||||
Last but not least, such a type-level bean qualifier can serve multiple purposes,
|
||||
e.g. with a value of "order" it can be used for autowiring purposes (identifying
|
||||
the order repository) as well as transaction manager selection, as long as the
|
||||
target beans for autowiring as well as the associated transaction manager
|
||||
definitions declare the same qualifier value. Such a qualifier value only needs
|
||||
to be unique within a set of type-matching beans, not having to serve as an ID.
|
||||
====
|
||||
|
||||
[[tx-custom-attributes]]
|
||||
== Custom Composed Annotations
|
||||
|
||||
If you find you repeatedly use the same attributes with `@Transactional` on many different methods,
|
||||
xref:core/beans/classpath-scanning.adoc#beans-meta-annotations[Spring's meta-annotation support]
|
||||
lets you define custom composed annotations for your specific use cases. For example, consider the
|
||||
If you find you repeatedly use the same attributes with `@Transactional` on many different
|
||||
methods, xref:core/beans/classpath-scanning.adoc#beans-meta-annotations[Spring's meta-annotation support] lets you
|
||||
define custom composed annotations for your specific use cases. For example, consider the
|
||||
following annotation definitions:
|
||||
|
||||
[tabs]
|
||||
|
||||
@@ -29,8 +29,6 @@ Brannen, Ramnivas Laddad, Arjen Poutsma, Chris Beams, Tareq Abedrabbo, Andy Clem
|
||||
Syer, Oliver Gierke, Rossen Stoyanchev, Phillip Webb, Rob Winch, Brian Clozel, Stephane
|
||||
Nicoll, Sebastien Deleuze, Jay Bryant, Mark Paluch
|
||||
|
||||
Copyright © 2002 - 2024 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Copies of this document may be made for your own use and for distribution to others,
|
||||
provided that you do not charge any fee for such copies and further provided that each
|
||||
copy contains this Copyright Notice, whether distributed in print or electronically.
|
||||
copy contains the Copyright Notice, whether distributed in print or electronically.
|
||||
|
||||
@@ -518,9 +518,41 @@ disable it by removing only one configuration line rather than all the annotatio
|
||||
your code).
|
||||
|
||||
To enable caching annotations add the annotation `@EnableCaching` to one of your
|
||||
`@Configuration` classes or use the `cache:annotation-driven` element with XML:
|
||||
`@Configuration` classes:
|
||||
|
||||
include-code::./CacheConfiguration[tag=snippet,indent=0]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
public class AppConfig {
|
||||
|
||||
@Bean
|
||||
CacheManager cacheManager() {
|
||||
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
|
||||
cacheManager.setCacheSpecification(...);
|
||||
return cacheManager;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Alternatively, for XML configuration you can use the `cache:annotation-driven` element:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:cache="http://www.springframework.org/schema/cache"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/cache https://www.springframework.org/schema/cache/spring-cache.xsd">
|
||||
|
||||
<cache:annotation-driven/>
|
||||
|
||||
<bean id="cacheManager" class="org.springframework.cache.caffeine.CaffeineCacheManager">
|
||||
<property name="cacheSpecification" value="..."/>
|
||||
</bean>
|
||||
</beans>
|
||||
----
|
||||
|
||||
Both the `cache:annotation-driven` element and the `@EnableCaching` annotation let you
|
||||
specify various options that influence the way the caching behavior is added to the
|
||||
|
||||
+49
-5
@@ -13,7 +13,18 @@ The JDK-based `Cache` implementation resides under
|
||||
`org.springframework.cache.concurrent` package. It lets you use `ConcurrentHashMap`
|
||||
as a backing `Cache` store. The following example shows how to configure two caches:
|
||||
|
||||
include-code::./CacheConfiguration[tag=snippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<!-- simple cache manager -->
|
||||
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
|
||||
<property name="caches">
|
||||
<set>
|
||||
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="default"/>
|
||||
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="books"/>
|
||||
</set>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
The preceding snippet uses the `SimpleCacheManager` to create a `CacheManager` for the
|
||||
two nested `ConcurrentMapCache` instances named `default` and `books`. Note that the
|
||||
@@ -41,12 +52,26 @@ of Caffeine.
|
||||
|
||||
The following example configures a `CacheManager` that creates the cache on demand:
|
||||
|
||||
include-code::./CacheConfiguration[tag=snippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="cacheManager"
|
||||
class="org.springframework.cache.caffeine.CaffeineCacheManager"/>
|
||||
----
|
||||
|
||||
You can also provide the caches to use explicitly. In that case, only those
|
||||
are made available by the manager. The following example shows how to do so:
|
||||
|
||||
include-code::./CustomCacheConfiguration[tag=snippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="cacheManager" class="org.springframework.cache.caffeine.CaffeineCacheManager">
|
||||
<property name="cacheNames">
|
||||
<set>
|
||||
<value>default</value>
|
||||
<value>books</value>
|
||||
</set>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
The Caffeine `CacheManager` also supports custom `Caffeine` and `CacheLoader`.
|
||||
See the https://github.com/ben-manes/caffeine/wiki[Caffeine documentation]
|
||||
@@ -72,7 +97,15 @@ implementation is located in the `org.springframework.cache.jcache` package.
|
||||
Again, to use it, you need to declare the appropriate `CacheManager`.
|
||||
The following example shows how to do so:
|
||||
|
||||
include-code::./CacheConfiguration[tag=snippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="cacheManager"
|
||||
class="org.springframework.cache.jcache.JCacheCacheManager"
|
||||
p:cache-manager-ref="jCacheManager"/>
|
||||
|
||||
<!-- JSR-107 cache manager setup -->
|
||||
<bean id="jCacheManager" .../>
|
||||
----
|
||||
|
||||
|
||||
[[cache-store-configuration-noop]]
|
||||
@@ -86,7 +119,18 @@ cache declarations (which can prove tedious), you can wire in a simple dummy cac
|
||||
performs no caching -- that is, it forces the cached methods to be invoked every time.
|
||||
The following example shows how to do so:
|
||||
|
||||
include-code::./CacheConfiguration[tag=snippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="cacheManager" class="org.springframework.cache.support.CompositeCacheManager">
|
||||
<property name="cacheManagers">
|
||||
<list>
|
||||
<ref bean="jdkCache"/>
|
||||
<ref bean="gemfireCache"/>
|
||||
</list>
|
||||
</property>
|
||||
<property name="fallbackToNoOpCache" value="true"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
The `CompositeCacheManager` in the preceding chains multiple `CacheManager` instances and,
|
||||
through the `fallbackToNoOpCache` flag, adds a no-op cache for all the definitions not
|
||||
|
||||
@@ -41,7 +41,14 @@ JavaMail features, such as MIME message support to the `MailSender` interface
|
||||
|
||||
Assume that we have a business interface called `OrderManager`, as the following example shows:
|
||||
|
||||
include-code::./OrderManager[tag=snippet,indent=0]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface OrderManager {
|
||||
|
||||
void placeOrder(Order order);
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Further assume that we have a requirement stating that an email message with an
|
||||
order number needs to be generated and sent to a customer who placed the relevant order.
|
||||
@@ -53,11 +60,70 @@ order number needs to be generated and sent to a customer who placed the relevan
|
||||
The following example shows how to use `MailSender` and `SimpleMailMessage` to send an
|
||||
email when someone places an order:
|
||||
|
||||
include-code::./SimpleOrderManager[tag=snippet,indent=0]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import org.springframework.mail.MailException;
|
||||
import org.springframework.mail.MailSender;
|
||||
import org.springframework.mail.SimpleMailMessage;
|
||||
|
||||
public class SimpleOrderManager implements OrderManager {
|
||||
|
||||
private MailSender mailSender;
|
||||
private SimpleMailMessage templateMessage;
|
||||
|
||||
public void setMailSender(MailSender mailSender) {
|
||||
this.mailSender = mailSender;
|
||||
}
|
||||
|
||||
public void setTemplateMessage(SimpleMailMessage templateMessage) {
|
||||
this.templateMessage = templateMessage;
|
||||
}
|
||||
|
||||
public void placeOrder(Order order) {
|
||||
|
||||
// Do the business calculations...
|
||||
|
||||
// Call the collaborators to persist the order...
|
||||
|
||||
// Create a thread-safe "copy" of the template message and customize it
|
||||
SimpleMailMessage msg = new SimpleMailMessage(this.templateMessage);
|
||||
msg.setTo(order.getCustomer().getEmailAddress());
|
||||
msg.setText(
|
||||
"Dear " + order.getCustomer().getFirstName()
|
||||
+ order.getCustomer().getLastName()
|
||||
+ ", thank you for placing order. Your order number is "
|
||||
+ order.getOrderNumber());
|
||||
try {
|
||||
this.mailSender.send(msg);
|
||||
}
|
||||
catch (MailException ex) {
|
||||
// simply log it and go on...
|
||||
System.err.println(ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
The following example shows the bean definitions for the preceding code:
|
||||
|
||||
include-code::./MailConfiguration[tag=snippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
|
||||
<property name="host" value="mail.mycompany.example"/>
|
||||
</bean>
|
||||
|
||||
<!-- this is a template message that we can pre-load with default state -->
|
||||
<bean id="templateMessage" class="org.springframework.mail.SimpleMailMessage">
|
||||
<property name="from" value="customerservice@mycompany.example"/>
|
||||
<property name="subject" value="Your order"/>
|
||||
</bean>
|
||||
|
||||
<bean id="orderManager" class="com.mycompany.businessapp.support.SimpleOrderManager">
|
||||
<property name="mailSender" ref="mailSender"/>
|
||||
<property name="templateMessage" ref="templateMessage"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
|
||||
[[mail-usage-mime]]
|
||||
|
||||
@@ -37,12 +37,28 @@ declarations to it.
|
||||
To enable support for `@JmsListener` annotations, you can add `@EnableJms` to one of
|
||||
your `@Configuration` classes, as the following example shows:
|
||||
|
||||
include-code::./JmsConfiguration[tag=snippet,indent=0]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableJms
|
||||
public class AppConfig {
|
||||
|
||||
@Bean
|
||||
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
|
||||
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
|
||||
factory.setConnectionFactory(connectionFactory());
|
||||
factory.setDestinationResolver(destinationResolver());
|
||||
factory.setSessionTransacted(true);
|
||||
factory.setConcurrency("3-10");
|
||||
return factory;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
By default, the infrastructure looks for a bean named `jmsListenerContainerFactory`
|
||||
as the source for the factory to use to create message listener containers. In this
|
||||
case (and ignoring the JMS infrastructure setup), you can invoke the `processOrder`
|
||||
method with a core poll size of three threads and a maximum pool size of ten threads.
|
||||
method with a core pool size of three threads and a maximum pool size of ten threads.
|
||||
|
||||
You can customize the listener container factory to use for each annotation or you can
|
||||
configure an explicit default by implementing the `JmsListenerConfigurer` interface.
|
||||
@@ -51,6 +67,22 @@ container factory. See the javadoc of classes that implement
|
||||
{spring-framework-api}/jms/annotation/JmsListenerConfigurer.html[`JmsListenerConfigurer`]
|
||||
for details and examples.
|
||||
|
||||
If you prefer xref:integration/jms/namespace.adoc[XML configuration], you can use the `<jms:annotation-driven>`
|
||||
element, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<jms:annotation-driven/>
|
||||
|
||||
<bean id="jmsListenerContainerFactory"
|
||||
class="org.springframework.jms.config.DefaultJmsListenerContainerFactory">
|
||||
<property name="connectionFactory" ref="connectionFactory"/>
|
||||
<property name="destinationResolver" ref="destinationResolver"/>
|
||||
<property name="sessionTransacted" value="true"/>
|
||||
<property name="concurrency" value="3-10"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
|
||||
[[jms-annotated-programmatic-registration]]
|
||||
== Programmatic Endpoint Registration
|
||||
|
||||
+51
-2
@@ -7,13 +7,62 @@ automatically determine the `ActivationSpec` class name from the provider's
|
||||
`ResourceAdapter` class name. Therefore, it is typically possible to provide
|
||||
Spring's generic `JmsActivationSpecConfig`, as the following example shows:
|
||||
|
||||
include-code::./JmsConfiguration[tag=snippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean class="org.springframework.jms.listener.endpoint.JmsMessageEndpointManager">
|
||||
<property name="resourceAdapter" ref="resourceAdapter"/>
|
||||
<property name="activationSpecConfig">
|
||||
<bean class="org.springframework.jms.listener.endpoint.JmsActivationSpecConfig">
|
||||
<property name="destinationName" value="myQueue"/>
|
||||
</bean>
|
||||
</property>
|
||||
<property name="messageListener" ref="myMessageListener"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
Alternatively, you can set up a `JmsMessageEndpointManager` with a given
|
||||
`ActivationSpec` object. The `ActivationSpec` object may also come from a JNDI lookup
|
||||
(using `<jee:jndi-lookup>`). The following example shows how to do so:
|
||||
|
||||
include-code::./AlternativeJmsConfiguration[tag=snippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean class="org.springframework.jms.listener.endpoint.JmsMessageEndpointManager">
|
||||
<property name="resourceAdapter" ref="resourceAdapter"/>
|
||||
<property name="activationSpec">
|
||||
<bean class="org.apache.activemq.ra.ActiveMQActivationSpec">
|
||||
<property name="destination" value="myQueue"/>
|
||||
<property name="destinationType" value="jakarta.jms.Queue"/>
|
||||
</bean>
|
||||
</property>
|
||||
<property name="messageListener" ref="myMessageListener"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
Using Spring's `ResourceAdapterFactoryBean`, you can configure the target `ResourceAdapter`
|
||||
locally, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="resourceAdapter" class="org.springframework.jca.support.ResourceAdapterFactoryBean">
|
||||
<property name="resourceAdapter">
|
||||
<bean class="org.apache.activemq.ra.ActiveMQResourceAdapter">
|
||||
<property name="serverUrl" value="tcp://localhost:61616"/>
|
||||
</bean>
|
||||
</property>
|
||||
<property name="workManager">
|
||||
<bean class="org.springframework.jca.work.SimpleTaskWorkManager"/>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
The specified `WorkManager` can also point to an environment-specific thread pool --
|
||||
typically through a `SimpleTaskWorkManager` instance's `asyncTaskExecutor` property.
|
||||
Consider defining a shared thread pool for all your `ResourceAdapter` instances
|
||||
if you happen to use multiple adapters.
|
||||
|
||||
In some environments, you can instead obtain the entire `ResourceAdapter` object from JNDI
|
||||
(by using `<jee:jndi-lookup>`). The Spring-based message listeners can then interact with
|
||||
the server-hosted `ResourceAdapter`, which also use the server's built-in `WorkManager`.
|
||||
|
||||
See the javadoc for {spring-framework-api}/jms/listener/endpoint/JmsMessageEndpointManager.html[`JmsMessageEndpointManager`],
|
||||
{spring-framework-api}/jms/listener/endpoint/JmsActivationSpecConfig.html[`JmsActivationSpecConfig`],
|
||||
|
||||
@@ -31,7 +31,30 @@ on multiple threads, it is important to ensure that your implementation is threa
|
||||
|
||||
The following example shows a simple implementation of an MDP:
|
||||
|
||||
include-code::./ExampleListener[tag=snippet,indent=0]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.jms.JMSException;
|
||||
import jakarta.jms.Message;
|
||||
import jakarta.jms.MessageListener;
|
||||
import jakarta.jms.TextMessage;
|
||||
|
||||
public class ExampleListener implements MessageListener {
|
||||
|
||||
public void onMessage(Message message) {
|
||||
if (message instanceof TextMessage textMessage) {
|
||||
try {
|
||||
System.out.println(textMessage.getText());
|
||||
}
|
||||
catch (JMSException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Message must be of type TextMessage");
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Once you have implemented your `MessageListener`, it is time to create a message listener
|
||||
container.
|
||||
@@ -39,7 +62,18 @@ container.
|
||||
The following example shows how to define and configure one of the message listener
|
||||
containers that ships with Spring (in this case, `DefaultMessageListenerContainer`):
|
||||
|
||||
include-code::./JmsConfiguration[tag=snippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<!-- this is the Message Driven POJO (MDP) -->
|
||||
<bean id="messageListener" class="jmsexample.ExampleListener"/>
|
||||
|
||||
<!-- and this is the message listener container -->
|
||||
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
|
||||
<property name="connectionFactory" ref="connectionFactory"/>
|
||||
<property name="destination" ref="destination"/>
|
||||
<property name="messageListener" ref="messageListener"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
See the Spring javadoc of the various message listener containers (all of which implement
|
||||
{spring-framework-api}/jms/listener/MessageListenerContainer.html[MessageListenerContainer])
|
||||
@@ -89,7 +123,19 @@ messaging support. In a nutshell, it lets you expose almost any class as an MDP
|
||||
|
||||
Consider the following interface definition:
|
||||
|
||||
include-code::./MessageDelegate[tag=snippet,indent=0]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface MessageDelegate {
|
||||
|
||||
void handleMessage(String message);
|
||||
|
||||
void handleMessage(Map message);
|
||||
|
||||
void handleMessage(byte[] message);
|
||||
|
||||
void handleMessage(Serializable message);
|
||||
}
|
||||
----
|
||||
|
||||
Notice that, although the interface extends neither the `MessageListener` nor the
|
||||
`SessionAwareMessageListener` interface, you can still use it as an MDP by using the
|
||||
@@ -99,13 +145,33 @@ receive and handle.
|
||||
|
||||
Now consider the following implementation of the `MessageDelegate` interface:
|
||||
|
||||
include-code::./DefaultMessageDelegate[tag=snippet,indent=0]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public class DefaultMessageDelegate implements MessageDelegate {
|
||||
// implementation elided for clarity...
|
||||
}
|
||||
----
|
||||
|
||||
In particular, note how the preceding implementation of the `MessageDelegate` interface (the
|
||||
`DefaultMessageDelegate` class) has no JMS dependencies at all. It truly is a
|
||||
POJO that we can make into an MDP through the following configuration:
|
||||
|
||||
include-code::./JmsConfiguration[tag=snippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<!-- this is the Message Driven POJO (MDP) -->
|
||||
<bean id="messageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
|
||||
<constructor-arg>
|
||||
<bean class="jmsexample.DefaultMessageDelegate"/>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
|
||||
<!-- and this is the message listener container... -->
|
||||
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
|
||||
<property name="connectionFactory" ref="connectionFactory"/>
|
||||
<property name="destination" ref="destination"/>
|
||||
<property name="messageListener" ref="messageListener"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
The next example shows another MDP that can handle only receiving JMS
|
||||
`TextMessage` messages. Notice how the message handling method is actually called
|
||||
@@ -115,15 +181,38 @@ also how the `receive(..)` method is strongly typed to receive and respond only
|
||||
`TextMessage` messages.
|
||||
The following listing shows the definition of the `TextMessageDelegate` interface:
|
||||
|
||||
include-code::./TextMessageDelegate[tag=snippet,indent=0]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface TextMessageDelegate {
|
||||
|
||||
void receive(TextMessage message);
|
||||
}
|
||||
----
|
||||
|
||||
The following listing shows a class that implements the `TextMessageDelegate` interface:
|
||||
|
||||
include-code::./DefaultTextMessageDelegate[tag=snippet,indent=0]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public class DefaultTextMessageDelegate implements TextMessageDelegate {
|
||||
// implementation elided for clarity...
|
||||
}
|
||||
----
|
||||
|
||||
The configuration of the attendant `MessageListenerAdapter` would then be as follows:
|
||||
|
||||
include-code::./MessageListenerConfiguration[tag=snippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="messageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
|
||||
<constructor-arg>
|
||||
<bean class="jmsexample.DefaultTextMessageDelegate"/>
|
||||
</constructor-arg>
|
||||
<property name="defaultListenerMethod" value="receive"/>
|
||||
<!-- we don't want automatic message context extraction -->
|
||||
<property name="messageConverter">
|
||||
<null/>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
Note that, if the `messageListener` receives a JMS `Message` of a type
|
||||
other than `TextMessage`, an `IllegalStateException` is thrown (and subsequently
|
||||
@@ -131,9 +220,21 @@ swallowed). Another of the capabilities of the `MessageListenerAdapter` class is
|
||||
ability to automatically send back a response `Message` if a handler method returns a
|
||||
non-void value. Consider the following interface and class:
|
||||
|
||||
include-code::./ResponsiveTextMessageDelegate[tag=snippet,indent=0]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface ResponsiveTextMessageDelegate {
|
||||
|
||||
include-code::./DefaultResponsiveTextMessageDelegate[tag=snippet,indent=0]
|
||||
// notice the return type...
|
||||
String receive(TextMessage message);
|
||||
}
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public class DefaultResponsiveTextMessageDelegate implements ResponsiveTextMessageDelegate {
|
||||
// implementation elided for clarity...
|
||||
}
|
||||
----
|
||||
|
||||
If you use the `DefaultResponsiveTextMessageDelegate` in conjunction with a
|
||||
`MessageListenerAdapter`, any non-null value that is returned from the execution of
|
||||
@@ -163,7 +264,15 @@ has committed but message processing failed to commit.
|
||||
|
||||
Consider the following bean definition:
|
||||
|
||||
include-code::./JmsConfiguration[tag=snippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
|
||||
<property name="connectionFactory" ref="connectionFactory"/>
|
||||
<property name="destination" ref="destination"/>
|
||||
<property name="messageListener" ref="messageListener"/>
|
||||
<property name="sessionTransacted" value="true"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
To participate in an externally managed transaction, you need to configure a
|
||||
transaction manager and use a listener container that supports externally managed
|
||||
@@ -179,9 +288,24 @@ semantics, at the expense of XA transaction log overhead).
|
||||
|
||||
The following bean definition creates a transaction manager:
|
||||
|
||||
include-code::./ExternalTxJmsConfiguration[tag=transactionManagerSnippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>
|
||||
----
|
||||
|
||||
Then we need to add it to our earlier container configuration. The container
|
||||
takes care of the rest. The following example shows how to do so:
|
||||
|
||||
include-code::./ExternalTxJmsConfiguration[tag=jmsContainerSnippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
|
||||
<property name="connectionFactory" ref="connectionFactory"/>
|
||||
<property name="destination" ref="destination"/>
|
||||
<property name="messageListener" ref="messageListener"/>
|
||||
<property name="transactionManager" ref="transactionManager"/> <1>
|
||||
</bean>
|
||||
----
|
||||
<1> Our transaction manager.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -5,13 +5,63 @@ The core class in Spring's JMX framework is the `MBeanExporter`. This class is
|
||||
responsible for taking your Spring beans and registering them with a JMX `MBeanServer`.
|
||||
For example, consider the following class:
|
||||
|
||||
include-code::./JmxTestBean[tag=snippet,indent=0]
|
||||
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages",chomp="-packages"]
|
||||
----
|
||||
package org.springframework.jmx;
|
||||
|
||||
public class JmxTestBean implements IJmxTestBean {
|
||||
|
||||
private String name;
|
||||
private int age;
|
||||
private boolean isSuperman;
|
||||
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public void setAge(int age) {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int add(int x, int y) {
|
||||
return x + y;
|
||||
}
|
||||
|
||||
public void dontExposeMe() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
To expose the properties and methods of this bean as attributes and operations of an
|
||||
MBean, you can configure an instance of the `MBeanExporter` class in your
|
||||
configuration file and pass in the bean, as the following example shows:
|
||||
|
||||
include-code::./JmxConfiguration[tag=snippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans>
|
||||
<!-- this bean must not be lazily initialized if the exporting is to happen -->
|
||||
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter" lazy-init="false">
|
||||
<property name="beans">
|
||||
<map>
|
||||
<entry key="bean:name=testBean1" value-ref="testBean"/>
|
||||
</map>
|
||||
</property>
|
||||
</bean>
|
||||
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
|
||||
<property name="name" value="TEST"/>
|
||||
<property name="age" value="100"/>
|
||||
</bean>
|
||||
</beans>
|
||||
----
|
||||
|
||||
The pertinent bean definition from the preceding configuration snippet is the `exporter`
|
||||
bean. The `beans` property tells the `MBeanExporter` exactly which of your beans must be
|
||||
|
||||
@@ -122,10 +122,25 @@ your management interfaces, a convenience subclass of `MBeanExporter` is availab
|
||||
`namingStrategy`, `assembler`, and `attributeSource` configuration,
|
||||
since it always uses standard Java annotation-based metadata (autodetection is
|
||||
always enabled as well). In fact, rather than defining an `MBeanExporter` bean, an even
|
||||
simpler syntax is supported by the `@EnableMBeanExport` `@Configuration` annotation or the `<context:mbean-export/>`
|
||||
element as the following example shows:
|
||||
simpler syntax is supported by the `@EnableMBeanExport` `@Configuration` annotation,
|
||||
as the following example shows:
|
||||
|
||||
include-code::./JmxConfiguration[tag=snippet,indent=0]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableMBeanExport
|
||||
public class AppConfig {
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
If you prefer XML-based configuration, the `<context:mbean-export/>` element serves the
|
||||
same purpose and is shown in the following listing:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<context:mbean-export/>
|
||||
----
|
||||
|
||||
If necessary, you can provide a reference to a particular MBean `server`, and the
|
||||
`defaultDomain` attribute (a property of `AnnotationMBeanExporter`) accepts an alternate
|
||||
@@ -133,7 +148,21 @@ value for the generated MBean `ObjectName` domains. This is used in place of the
|
||||
fully qualified package name as described in the previous section on
|
||||
xref:integration/jmx/naming.adoc#jmx-naming-metadata[MetadataNamingStrategy], as the following example shows:
|
||||
|
||||
include-code::./CustomJmxConfiguration[tag=snippet,indent=0]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@EnableMBeanExport(server="myMBeanServer", defaultDomain="myDomain")
|
||||
@Configuration
|
||||
ContextConfiguration {
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
The following example shows the XML equivalent of the preceding annotation-based example:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<context:mbean-export server="myMBeanServer" default-domain="myDomain"/>
|
||||
----
|
||||
|
||||
CAUTION: Do not use interface-based AOP proxies in combination with autodetection of JMX
|
||||
annotations in your bean classes. Interface-based proxies "`hide`" the target class, which
|
||||
|
||||
@@ -79,7 +79,38 @@ Spring's `TaskExecutor` implementations are commonly used with dependency inject
|
||||
In the following example, we define a bean that uses the `ThreadPoolTaskExecutor`
|
||||
to asynchronously print out a set of messages:
|
||||
|
||||
include-code::./TaskExecutorExample[tag=snippet,indent=0]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
|
||||
public class TaskExecutorExample {
|
||||
|
||||
private class MessagePrinterTask implements Runnable {
|
||||
|
||||
private String message;
|
||||
|
||||
public MessagePrinterTask(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
System.out.println(message);
|
||||
}
|
||||
}
|
||||
|
||||
private TaskExecutor taskExecutor;
|
||||
|
||||
public TaskExecutorExample(TaskExecutor taskExecutor) {
|
||||
this.taskExecutor = taskExecutor;
|
||||
}
|
||||
|
||||
public void printMessages() {
|
||||
for(int i = 0; i < 25; i++) {
|
||||
taskExecutor.execute(new MessagePrinterTask("Message" + i));
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
As you can see, rather than retrieving a thread from the pool and executing it yourself,
|
||||
you add your `Runnable` to the queue. Then the `TaskExecutor` uses its internal rules to
|
||||
@@ -87,7 +118,19 @@ decide when the task gets run.
|
||||
|
||||
To configure the rules that the `TaskExecutor` uses, we expose simple bean properties:
|
||||
|
||||
include-code::./TaskExecutorConfiguration[tag=snippet,indent=0]
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
|
||||
<property name="corePoolSize" value="5"/>
|
||||
<property name="maxPoolSize" value="10"/>
|
||||
<property name="queueCapacity" value="25"/>
|
||||
</bean>
|
||||
|
||||
<bean id="taskExecutorExample" class="TaskExecutorExample">
|
||||
<constructor-arg ref="taskExecutor"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[scheduling-task-scheduler]]
|
||||
@@ -226,10 +269,16 @@ execution.
|
||||
=== Enable Scheduling Annotations
|
||||
|
||||
To enable support for `@Scheduled` and `@Async` annotations, you can add `@EnableScheduling`
|
||||
and `@EnableAsync` to one of your `@Configuration` classes, or `<task:annotation-driven>` element,
|
||||
as the following example shows:
|
||||
and `@EnableAsync` to one of your `@Configuration` classes, as the following example shows:
|
||||
|
||||
include-code::./SchedulingConfiguration[tag=snippet,indent=0]
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableAsync
|
||||
@EnableScheduling
|
||||
public class AppConfig {
|
||||
}
|
||||
----
|
||||
|
||||
You can pick and choose the relevant annotations for your application. For example,
|
||||
if you need only support for `@Scheduled`, you can omit `@EnableAsync`. For more
|
||||
@@ -239,6 +288,16 @@ interface, the `AsyncConfigurer` interface, or both. See the
|
||||
and {spring-framework-api}/scheduling/annotation/AsyncConfigurer.html[`AsyncConfigurer`]
|
||||
javadoc for full details.
|
||||
|
||||
If you prefer XML configuration, you can use the `<task:annotation-driven>` element,
|
||||
as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<task:annotation-driven executor="myExecutor" scheduler="myScheduler"/>
|
||||
<task:executor id="myExecutor" pool-size="5"/>
|
||||
<task:scheduler id="myScheduler" pool-size="10"/>
|
||||
----
|
||||
|
||||
Note that, with the preceding XML, an executor reference is provided for handling those
|
||||
tasks that correspond to methods with the `@Async` annotation, and the scheduler
|
||||
reference is provided for managing those methods annotated with `@Scheduled`.
|
||||
|
||||
@@ -10,7 +10,7 @@ Spring requires Java 17+.
|
||||
|
||||
Spring supports a wide range of application scenarios. In a large enterprise, applications
|
||||
often exist for a long time and have to run on a JDK and application server whose upgrade
|
||||
cycle is beyond the developer's control. Others may run as a single jar with the server embedded,
|
||||
cycle is beyond developer control. Others may run as a single jar with the server embedded,
|
||||
possibly in a cloud environment. Yet others may be standalone applications (such as batch
|
||||
or integration workloads) that do not need a server.
|
||||
|
||||
|
||||
+5
-5
@@ -165,8 +165,8 @@ for further details.
|
||||
[[integration-testing-annotations-testconstructor]]
|
||||
== `@TestConstructor`
|
||||
|
||||
`@TestConstructor` is an annotation that can be applied to a test class to configure how
|
||||
the parameters of a test class constructor are autowired from components in the test's
|
||||
`@TestConstructor` is a type-level annotation that is used to configure how the parameters
|
||||
of a test class constructor are autowired from components in the test's
|
||||
`ApplicationContext`.
|
||||
|
||||
If `@TestConstructor` is not present or meta-present on a test class, the default _test
|
||||
@@ -199,9 +199,9 @@ Spring Boot Test.
|
||||
[[integration-testing-annotations-nestedtestconfiguration]]
|
||||
== `@NestedTestConfiguration`
|
||||
|
||||
`@NestedTestConfiguration` is an annotation that can be applied to a test class to
|
||||
configure how Spring test configuration annotations are processed within enclosing class
|
||||
hierarchies for inner test classes.
|
||||
`@NestedTestConfiguration` is a type-level annotation that is used to configure how
|
||||
Spring test configuration annotations are processed within enclosing class hierarchies
|
||||
for inner test classes.
|
||||
|
||||
If `@NestedTestConfiguration` is not present or meta-present on a test class, in its
|
||||
supertype hierarchy, or in its enclosing class hierarchy, the default _enclosing
|
||||
|
||||
@@ -13,10 +13,10 @@ xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-runne
|
||||
[[integration-testing-annotations-junit4-ifprofilevalue]]
|
||||
== `@IfProfileValue`
|
||||
|
||||
`@IfProfileValue` indicates that the annotated test class or test method is enabled for a
|
||||
specific testing environment. If the configured `ProfileValueSource` returns a matching
|
||||
`value` for the provided `name`, the test is enabled. Otherwise, the test is disabled
|
||||
and, effectively, ignored.
|
||||
`@IfProfileValue` indicates that the annotated test is enabled for a specific testing
|
||||
environment. If the configured `ProfileValueSource` returns a matching `value` for the
|
||||
provided `name`, the test is enabled. Otherwise, the test is disabled and, effectively,
|
||||
ignored.
|
||||
|
||||
You can apply `@IfProfileValue` at the class level, the method level, or both.
|
||||
Class-level usage of `@IfProfileValue` takes precedence over method-level usage for any
|
||||
@@ -90,12 +90,11 @@ Kotlin::
|
||||
[[integration-testing-annotations-junit4-profilevaluesourceconfiguration]]
|
||||
== `@ProfileValueSourceConfiguration`
|
||||
|
||||
`@ProfileValueSourceConfiguration` is an annotation that can be applied to a test class
|
||||
to specify what type of `ProfileValueSource` to use when retrieving profile values
|
||||
configured through the `@IfProfileValue` annotation. If
|
||||
`@ProfileValueSourceConfiguration` is not declared for a test, `SystemProfileValueSource`
|
||||
is used by default. The following example shows how to use
|
||||
`@ProfileValueSourceConfiguration`:
|
||||
`@ProfileValueSourceConfiguration` is a class-level annotation that specifies what type
|
||||
of `ProfileValueSource` to use when retrieving profile values configured through the
|
||||
`@IfProfileValue` annotation. If `@ProfileValueSourceConfiguration` is not declared for a
|
||||
test, `SystemProfileValueSource` is used by default. The following example shows how to
|
||||
use `@ProfileValueSourceConfiguration`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
||||
@@ -16,8 +16,6 @@ Spring's testing annotations include the following:
|
||||
* xref:testing/annotations/integration-spring/annotation-activeprofiles.adoc[`@ActiveProfiles`]
|
||||
* xref:testing/annotations/integration-spring/annotation-testpropertysource.adoc[`@TestPropertySource`]
|
||||
* xref:testing/annotations/integration-spring/annotation-dynamicpropertysource.adoc[`@DynamicPropertySource`]
|
||||
* xref:testing/annotations/integration-spring/annotation-testbean.adoc[`@TestBean`]
|
||||
* xref:testing/annotations/integration-spring/annotation-mockitobean.adoc[`@MockitoBean` and `@MockitoSpyBean`]
|
||||
* xref:testing/annotations/integration-spring/annotation-dirtiescontext.adoc[`@DirtiesContext`]
|
||||
* xref:testing/annotations/integration-spring/annotation-testexecutionlisteners.adoc[`@TestExecutionListeners`]
|
||||
* xref:testing/annotations/integration-spring/annotation-recordapplicationevents.adoc[`@RecordApplicationEvents`]
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
[[spring-testing-annotation-activeprofiles]]
|
||||
= `@ActiveProfiles`
|
||||
|
||||
`@ActiveProfiles` is an annotation that can be applied to a test class to declare which
|
||||
bean definition profiles should be active when loading an `ApplicationContext` for an
|
||||
`@ActiveProfiles` is a class-level annotation that is used to declare which bean
|
||||
definition profiles should be active when loading an `ApplicationContext` for an
|
||||
integration test.
|
||||
|
||||
The following example indicates that the `dev` profile should be active:
|
||||
|
||||
+3
-3
@@ -2,8 +2,8 @@
|
||||
= `@BootstrapWith`
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
`@BootstrapWith` is an annotation that can be applied to a test class to configure how
|
||||
the Spring TestContext Framework is bootstrapped. Specifically, you can use
|
||||
`@BootstrapWith` to specify a custom `TestContextBootstrapper`. See the section on
|
||||
`@BootstrapWith` is a class-level annotation that you can use to configure how the Spring
|
||||
TestContext Framework is bootstrapped. Specifically, you can use `@BootstrapWith` to
|
||||
specify a custom `TestContextBootstrapper`. See the section on
|
||||
xref:testing/testcontext-framework/bootstrapping.adoc[bootstrapping the TestContext framework] for further details.
|
||||
|
||||
|
||||
+4
-4
@@ -1,10 +1,10 @@
|
||||
[[spring-testing-annotation-contextconfiguration]]
|
||||
= `@ContextConfiguration`
|
||||
|
||||
`@ContextConfiguration` is an annotation that can be applied to a test class to configure
|
||||
metadata that is used to determine how to load and configure an `ApplicationContext` for
|
||||
integration tests. Specifically, `@ContextConfiguration` declares the application context
|
||||
resource `locations` or the component `classes` used to load the context.
|
||||
`@ContextConfiguration` defines class-level metadata that is used to determine how to
|
||||
load and configure an `ApplicationContext` for integration tests. Specifically,
|
||||
`@ContextConfiguration` declares the application context resource `locations` or the
|
||||
component `classes` used to load the context.
|
||||
|
||||
Resource locations are typically XML configuration files or Groovy scripts located in the
|
||||
classpath, while component classes are typically `@Configuration` classes. However,
|
||||
|
||||
+4
-4
@@ -1,10 +1,10 @@
|
||||
[[spring-testing-annotation-contextcustomizerfactories]]
|
||||
= `@ContextCustomizerFactories`
|
||||
|
||||
`@ContextCustomizerFactories` is an annotation that can be applied to a test class to
|
||||
register `ContextCustomizerFactory` implementations for the particular test class, its
|
||||
subclasses, and its nested classes. If you wish to register a factory globally, you
|
||||
should register it via the automatic discovery mechanism described in
|
||||
`@ContextCustomizerFactories` is used to register `ContextCustomizerFactory`
|
||||
implementations for a particular test class, its subclasses, and its nested classes. If
|
||||
you wish to register a factory globally, you should register it via the automatic
|
||||
discovery mechanism described in
|
||||
xref:testing/testcontext-framework/ctx-management/context-customizers.adoc[`ContextCustomizerFactory` Configuration].
|
||||
|
||||
The following example shows how to register two `ContextCustomizerFactory` implementations:
|
||||
|
||||
+6
-6
@@ -1,12 +1,12 @@
|
||||
[[spring-testing-annotation-contexthierarchy]]
|
||||
= `@ContextHierarchy`
|
||||
|
||||
`@ContextHierarchy` is an annotation that can be applied to a test class to define a
|
||||
hierarchy of `ApplicationContext` instances for integration tests. `@ContextHierarchy`
|
||||
should be declared with a list of one or more `@ContextConfiguration` instances, each of
|
||||
which defines a level in the context hierarchy. The following examples demonstrate the
|
||||
use of `@ContextHierarchy` within a single test class (`@ContextHierarchy` can also be
|
||||
used within a test class hierarchy):
|
||||
`@ContextHierarchy` is a class-level annotation that is used to define a hierarchy of
|
||||
`ApplicationContext` instances for integration tests. `@ContextHierarchy` should be
|
||||
declared with a list of one or more `@ContextConfiguration` instances, each of which
|
||||
defines a level in the context hierarchy. The following examples demonstrate the use of
|
||||
`@ContextHierarchy` within a single test class (`@ContextHierarchy` can also be used
|
||||
within a test class hierarchy):
|
||||
|
||||
[tabs]
|
||||
======
|
||||
|
||||
+3
-3
@@ -10,9 +10,9 @@ rebuilt for any subsequent test that requires a context with the same configurat
|
||||
metadata.
|
||||
|
||||
You can use `@DirtiesContext` as both a class-level and a method-level annotation within
|
||||
the same test class or test class hierarchy. In such scenarios, the `ApplicationContext`
|
||||
is marked as dirty before or after any such annotated method as well as before or after
|
||||
the current test class, depending on the configured `methodMode` and `classMode`. When
|
||||
the same class or class hierarchy. In such scenarios, the `ApplicationContext` is marked
|
||||
as dirty before or after any such annotated method as well as before or after the current
|
||||
test class, depending on the configured `methodMode` and `classMode`. When
|
||||
`@DirtiesContext` is declared at both the class level and the method level, the
|
||||
configured modes from both annotations will be honored. For example, if the class mode is
|
||||
set to `BEFORE_EACH_TEST_METHOD` and the method mode is set to `AFTER_METHOD`, the
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[[spring-testing-annotation-disabledinaotmode]]
|
||||
= `@DisabledInAotMode`
|
||||
|
||||
`@DisabledInAotMode` signals that the annotated test class is disabled in Spring AOT
|
||||
`@DisabledInAotMode` signals that an annotated test class is disabled in Spring AOT
|
||||
(ahead-of-time) mode, which means that the `ApplicationContext` for the test class will
|
||||
not be processed for AOT optimizations at build time.
|
||||
|
||||
|
||||
+6
-6
@@ -1,12 +1,12 @@
|
||||
[[spring-testing-annotation-dynamicpropertysource]]
|
||||
= `@DynamicPropertySource`
|
||||
|
||||
`@DynamicPropertySource` is an annotation that can be applied to methods in integration
|
||||
test classes that need to register _dynamic_ properties to be added to the set of
|
||||
`PropertySources` in the `Environment` for an `ApplicationContext` loaded for an
|
||||
integration test. Dynamic properties are useful when you do not know the value of the
|
||||
properties upfront – for example, if the properties are managed by an external resource
|
||||
such as for a container managed by the {testcontainers-site}[Testcontainers] project.
|
||||
`@DynamicPropertySource` is a method-level annotation that you can use to register
|
||||
_dynamic_ properties to be added to the set of `PropertySources` in the `Environment` for
|
||||
an `ApplicationContext` loaded for an integration test. Dynamic properties are useful
|
||||
when you do not know the value of the properties upfront – for example, if the properties
|
||||
are managed by an external resource such as for a container managed by the
|
||||
{testcontainers-site}[Testcontainers] project.
|
||||
|
||||
The following example demonstrates how to register a dynamic property:
|
||||
|
||||
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
[[spring-testing-annotation-beanoverriding-mockitobean]]
|
||||
= `@MockitoBean` and `@MockitoSpyBean`
|
||||
|
||||
`@MockitoBean` and `@MockitoSpyBean` are used on test class fields to override beans in
|
||||
the test's `ApplicationContext` with a Mockito mock or spy, respectively. In the latter
|
||||
case, the original bean definition is not replaced, but instead an early instance of the
|
||||
bean is captured and wrapped by the spy.
|
||||
|
||||
By default, the annotated field's type is used to search for candidate definitions to
|
||||
override, but note that `@Qualifier` annotations are also taken into account for the
|
||||
purpose of matching. Users can also make things entirely explicit by specifying a bean
|
||||
`name` in the annotation.
|
||||
|
||||
Each annotation also defines Mockito-specific attributes to fine-tune the mocking details.
|
||||
|
||||
The `@MockitoBean` annotation uses the `REPLACE_OR_CREATE_DEFINITION`
|
||||
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-custom[strategy for test bean overriding].
|
||||
|
||||
It requires that at most one matching candidate definition exists if a bean name
|
||||
is specified, or exactly one if no bean name is specified.
|
||||
|
||||
The `@MockitoSpyBean` annotation uses the `WRAP_BEAN`
|
||||
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-custom[strategy],
|
||||
and the original instance is wrapped in a Mockito spy.
|
||||
|
||||
It requires that exactly one candidate definition exists.
|
||||
|
||||
The following example shows how to configure the bean name via `@MockitoBean` and
|
||||
`@MockitoSpyBean`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
class OverrideBeanTests {
|
||||
|
||||
@MockitoBean(name = "service1") // <1>
|
||||
private CustomService mockService;
|
||||
|
||||
@MockitoSpyBean(name = "service2") // <2>
|
||||
private CustomService spyService; // <3>
|
||||
|
||||
// test case body...
|
||||
}
|
||||
----
|
||||
<1> Mark `mockService` as a Mockito mock override of bean `service1` in this test class.
|
||||
<2> Mark `spyService` as a Mockito spy override of bean `service2` in this test class.
|
||||
<3> The fields will be injected with the Mockito mock and spy, respectively.
|
||||
======
|
||||
+3
-3
@@ -2,9 +2,9 @@
|
||||
= `@RecordApplicationEvents`
|
||||
:page-section-summary-toc: 1
|
||||
|
||||
`@RecordApplicationEvents` is an annotation that can be applied to a test class to
|
||||
instruct the _Spring TestContext Framework_ to record all application events that are
|
||||
published in the `ApplicationContext` during the execution of a single test.
|
||||
`@RecordApplicationEvents` is a class-level annotation that is used to instruct the
|
||||
_Spring TestContext Framework_ to record all application events that are published in the
|
||||
`ApplicationContext` during the execution of a single test.
|
||||
|
||||
The recorded events can be accessed via the `ApplicationEvents` API within tests.
|
||||
|
||||
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
[[spring-testing-annotation-beanoverriding-testbean]]
|
||||
= `@TestBean`
|
||||
|
||||
`@TestBean` is used on a test class field to override a specific bean in the test's
|
||||
`ApplicationContext` with an instance provided by a conventionally named static factory
|
||||
method.
|
||||
|
||||
By default, the associated factory method name is derived from the annotated field's name,
|
||||
but the annotation allows for a specific method name to be provided.
|
||||
|
||||
The `@TestBean` annotation uses the `REPLACE_DEFINITION`
|
||||
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-custom[strategy for test bean overriding].
|
||||
|
||||
By default, the annotated field's type is used to search for candidate definitions to override.
|
||||
In that case it is required that exactly one definition matches, but note that `@Qualifier`
|
||||
annotations are also taken into account for the purpose of matching.
|
||||
Users can also make things entirely explicit by specifying a bean `name` in the annotation.
|
||||
|
||||
The following example shows how to fully configure the `@TestBean` annotation, with
|
||||
explicit values equivalent to the defaults:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
class OverrideBeanTests {
|
||||
@TestBean(name = "service", methodName = "serviceTestOverride") // <1>
|
||||
private CustomService service;
|
||||
|
||||
// test case body...
|
||||
|
||||
private static CustomService serviceTestOverride() { // <2>
|
||||
return new MyFakeCustomService();
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Mark a field for bean overriding in this test class.
|
||||
<2> The result of this static method will be used as the instance and injected into the field.
|
||||
======
|
||||
|
||||
NOTE: Spring searches for the factory method to invoke in the test class, in the test
|
||||
class hierarchy, and in the enclosing class hierarchy for a `@Nested` test class.
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[[spring-testing-annotation-testexecutionlisteners]]
|
||||
= `@TestExecutionListeners`
|
||||
|
||||
`@TestExecutionListeners` is used to register listeners for the annotated test class, its
|
||||
`@TestExecutionListeners` is used to register listeners for a particular test class, its
|
||||
subclasses, and its nested classes. If you wish to register a listener globally, you
|
||||
should register it via the automatic discovery mechanism described in
|
||||
xref:testing/testcontext-framework/tel-config.adoc[`TestExecutionListener` Configuration].
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
[[spring-testing-annotation-testpropertysource]]
|
||||
= `@TestPropertySource`
|
||||
|
||||
`@TestPropertySource` is an annotation that can be applied to a test class to configure
|
||||
the locations of properties files and inlined properties to be added to the set of
|
||||
`@TestPropertySource` is a class-level annotation that you can use to configure the
|
||||
locations of properties files and inlined properties to be added to the set of
|
||||
`PropertySources` in the `Environment` for an `ApplicationContext` loaded for an
|
||||
integration test.
|
||||
|
||||
|
||||
+4
-4
@@ -1,10 +1,10 @@
|
||||
[[spring-testing-annotation-webappconfiguration]]
|
||||
= `@WebAppConfiguration`
|
||||
|
||||
`@WebAppConfiguration` is an annotation that can be applied to a test class to declare
|
||||
that the `ApplicationContext` loaded for an integration test should be a
|
||||
`WebApplicationContext`. The mere presence of `@WebAppConfiguration` on a test class
|
||||
ensures that a `WebApplicationContext` is loaded for the test, using the default value of
|
||||
`@WebAppConfiguration` is a class-level annotation that you can use to declare that the
|
||||
`ApplicationContext` loaded for an integration test should be a `WebApplicationContext`.
|
||||
The mere presence of `@WebAppConfiguration` on a test class ensures that a
|
||||
`WebApplicationContext` is loaded for the test, using the default value of
|
||||
`"file:src/main/webapp"` for the path to the root of the web application (that is, the
|
||||
resource base path). The resource base path is used behind the scenes to create a
|
||||
`MockServletContext`, which serves as the `ServletContext` for the test's
|
||||
|
||||
+2
-1
@@ -8,7 +8,8 @@ to use the raw HtmlUnit libraries.
|
||||
== MockMvc and HtmlUnit Setup
|
||||
|
||||
First, make sure that you have included a test dependency on
|
||||
`org.htmlunit:htmlunit`.
|
||||
`net.sourceforge.htmlunit:htmlunit`. In order to use HtmlUnit with Apache HttpComponents
|
||||
4.5+, you need to use HtmlUnit 2.18 or higher.
|
||||
|
||||
We can easily create an HtmlUnit `WebClient` that integrates with MockMvc by using the
|
||||
`MockMvcWebClientBuilder`, as follows:
|
||||
|
||||
+1
-1
@@ -166,7 +166,7 @@ following sections to make this pattern much easier to implement.
|
||||
== MockMvc and WebDriver Setup
|
||||
|
||||
To use Selenium WebDriver with the Spring MVC Test framework, make sure that your project
|
||||
includes a test dependency on `org.seleniumhq.selenium:selenium-htmlunit3-driver`.
|
||||
includes a test dependency on `org.seleniumhq.selenium:selenium-htmlunit-driver`.
|
||||
|
||||
We can easily create a Selenium WebDriver that integrates with MockMvc by using the
|
||||
`MockMvcHtmlUnitDriverBuilder` as the following example shows:
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@ Kotlin::
|
||||
|
||||
This test has some obvious drawbacks. If we update our controller to use the parameter
|
||||
`message` instead of `text`, our form test continues to pass, even though the HTML form
|
||||
is out of sync with the controller. To resolve this we can combine our two tests, as
|
||||
is out of synch with the controller. To resolve this we can combine our two tests, as
|
||||
follows:
|
||||
|
||||
[tabs]
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
[[testcontext-bean-overriding]]
|
||||
= Bean Overriding in Tests
|
||||
|
||||
Bean overriding in tests refers to the ability to override specific beans in the
|
||||
`ApplicationContext` for a test class, by annotating one or more fields in the test class.
|
||||
|
||||
NOTE: This feature is intended as a less risky alternative to the practice of registering
|
||||
a bean via `@Bean` with the `DefaultListableBeanFactory`
|
||||
`setAllowBeanDefinitionOverriding` flag set to `true`.
|
||||
|
||||
The Spring TestContext framework provides two sets of annotations for bean overriding.
|
||||
|
||||
* xref:testing/annotations/integration-spring/annotation-testbean.adoc[`@TestBean`]
|
||||
* xref:testing/annotations/integration-spring/annotation-mockitobean.adoc[`@MockitoBean` and `@MockitoSpyBean`]
|
||||
|
||||
The former relies purely on Spring, while the latter set relies on the
|
||||
https://site.mockito.org/[Mockito] third-party library.
|
||||
|
||||
[[testcontext-bean-overriding-custom]]
|
||||
== Custom Bean Override Support
|
||||
|
||||
The three annotations mentioned above build upon the `@BeanOverride` meta-annotation and
|
||||
associated infrastructure, which allows one to define custom bean overriding variants.
|
||||
|
||||
To create custom bean override support, the following is needed:
|
||||
|
||||
* An annotation meta-annotated with `@BeanOverride` that defines the
|
||||
`BeanOverrideProcessor` to use
|
||||
* A custom `BeanOverrideProcessor` implementation
|
||||
* One or more concrete `OverrideMetadata` implementations provided by the processor
|
||||
|
||||
The Spring TestContext framework includes implementations of the following APIs that
|
||||
support bean overriding and are responsible for setting up the rest of the infrastructure.
|
||||
|
||||
* a `BeanFactoryPostProcessor`
|
||||
* a `ContextCustomizerFactory`
|
||||
* a `TestExecutionListener`
|
||||
|
||||
The `spring-test` module registers implementations of the latter two
|
||||
(`BeanOverrideContextCustomizerFactory` and `BeanOverrideTestExecutionListener`) in its
|
||||
{spring-framework-code}/spring-test/src/main/resources/META-INF/spring.factories[`META-INF/spring.factories`
|
||||
properties file].
|
||||
|
||||
The bean overriding infrastructure searches in test classes for any field meta-annotated
|
||||
with `@BeanOverride` and instantiates the corresponding `BeanOverrideProcessor` which is
|
||||
responsible for registering appropriate `OverrideMetadata`.
|
||||
|
||||
The internal `BeanOverrideBeanFactoryPostProcessor` then uses that information to alter
|
||||
the test's `ApplicationContext` by registering and replacing bean definitions as defined
|
||||
by the corresponding `BeanOverrideStrategy`:
|
||||
|
||||
* `REPLACE_DEFINITION`: Replaces the bean definition. Throws an exception if a
|
||||
corresponding bean definition does not exist.
|
||||
* `REPLACE_OR_CREATE_DEFINITION`: Replaces the bean definition if it exists. Creates a
|
||||
new bean definition if a corresponding bean definition does not exist.
|
||||
* `WRAP_BEAN`: Retrieves the original bean instance and wraps it.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
In contrast to Spring's autowiring mechanism (for example, resolution of an `@Autowired`
|
||||
field), the bean overriding infrastructure in the TestContext framework has limited
|
||||
heuristics it can perform to locate a bean. Either the `BeanOverrideProcessor` can compute
|
||||
the name of the bean to override, or it can be unambiguously selected given the type of
|
||||
the annotated field and its qualifying annotations.
|
||||
|
||||
Typically, the bean is selected by type by the `BeanOverrideFactoryPostProcessor`.
|
||||
Alternatively, the user can directly provide the bean name in the custom annotation.
|
||||
|
||||
Some `BeanOverrideProcessor`s could also internally compute a bean name based on a
|
||||
convention or another advanced method.
|
||||
====
|
||||
+26
-89
@@ -1,58 +1,43 @@
|
||||
[[testcontext-ctx-management-dynamic-property-sources]]
|
||||
= Context Configuration with Dynamic Property Sources
|
||||
|
||||
The Spring TestContext Framework provides support for _dynamic_ properties via the
|
||||
`@DynamicPropertySource` annotation and the `DynamicPropertyRegistry`.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The `@DynamicPropertySource` annotation and its supporting infrastructure were originally
|
||||
designed to allow properties from {testcontainers-site}[Testcontainers] based tests to be
|
||||
exposed easily to Spring integration tests. However, this feature may be used with any
|
||||
form of external resource whose lifecycle is managed outside the test's
|
||||
`ApplicationContext` or with beans whose lifecycle is managed by the test's
|
||||
`ApplicationContext`.
|
||||
====
|
||||
|
||||
In contrast to the
|
||||
xref:testing/testcontext-framework/ctx-management/property-sources.adoc[`@TestPropertySource`]
|
||||
annotation that is applied at the class level, `@DynamicPropertySource` can be applied to
|
||||
`static` methods in integration test classes or to `@Bean` methods in test
|
||||
`@Configuration` classes in order to add properties with dynamic values to the set of
|
||||
As of Spring Framework 5.2.5, the TestContext framework provides support for _dynamic_
|
||||
properties via the `@DynamicPropertySource` annotation. This annotation can be used in
|
||||
integration tests that need to add properties with dynamic values to the set of
|
||||
`PropertySources` in the `Environment` for the `ApplicationContext` loaded for the
|
||||
integration test.
|
||||
|
||||
A `DynamicPropertyRegistry` is used to add _name-value_ pairs to the `Environment`.
|
||||
Values are dynamic and provided via a `Supplier` which is only invoked when the property
|
||||
is resolved. Typically, method references are used to supply values.
|
||||
[NOTE]
|
||||
====
|
||||
The `@DynamicPropertySource` annotation and its supporting infrastructure were
|
||||
originally designed to allow properties from
|
||||
{testcontainers-site}[Testcontainers] based tests to be exposed easily to
|
||||
Spring integration tests. However, this feature may also be used with any form of
|
||||
external resource whose lifecycle is maintained outside the test's `ApplicationContext`.
|
||||
====
|
||||
|
||||
Methods in integration test classes that are annotated with `@DynamicPropertySource` must
|
||||
be `static` and must accept a single `DynamicPropertyRegistry` argument.
|
||||
|
||||
`@Bean` methods annotated with `@DynamicPropertySource` may either accept an argument of
|
||||
type `DynamicPropertyRegistry` or access a `DynamicPropertyRegistry` instance autowired
|
||||
into their enclosing `@Configuration` class. Note, however, that `@Bean` methods which
|
||||
interact with a `DynamicPropertyRegistry` are not required to be annotated with
|
||||
`@DynamicPropertySource` unless they need to enforce eager initialization of the bean
|
||||
within the context. See the class-level javadoc for `DynamicPropertyRegistry` for details.
|
||||
In contrast to the xref:testing/testcontext-framework/ctx-management/property-sources.adoc[`@TestPropertySource`]
|
||||
annotation that is applied at the class level, `@DynamicPropertySource` must be applied
|
||||
to a `static` method that accepts a single `DynamicPropertyRegistry` argument which is
|
||||
used to add _name-value_ pairs to the `Environment`. Values are dynamic and provided via
|
||||
a `Supplier` which is only invoked when the property is resolved. Typically, method
|
||||
references are used to supply values, as can be seen in the following example which uses
|
||||
the Testcontainers project to manage a Redis container outside of the Spring
|
||||
`ApplicationContext`. The IP address and port of the managed Redis container are made
|
||||
available to components within the test's `ApplicationContext` via the `redis.host` and
|
||||
`redis.port` properties. These properties can be accessed via Spring's `Environment`
|
||||
abstraction or injected directly into Spring-managed components – for example, via
|
||||
`@Value("${redis.host}")` and `@Value("${redis.port}")`, respectively.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
If you use `@DynamicPropertySource` in a base class and discover that tests in subclasses
|
||||
fail because the dynamic properties change between subclasses, you may need to annotate
|
||||
your base class with
|
||||
xref:testing/annotations/integration-spring/annotation-dirtiescontext.adoc[`@DirtiesContext`]
|
||||
to ensure that each subclass gets its own `ApplicationContext` with the correct dynamic
|
||||
your base class with xref:testing/annotations/integration-spring/annotation-dirtiescontext.adoc[`@DirtiesContext`] to
|
||||
ensure that each subclass gets its own `ApplicationContext` with the correct dynamic
|
||||
properties.
|
||||
====
|
||||
|
||||
The following example uses the Testcontainers project to manage a Redis container outside
|
||||
of the Spring `ApplicationContext`. The IP address and port of the managed Redis
|
||||
container are made available to components within the test's `ApplicationContext` via the
|
||||
`redis.host` and `redis.port` properties. These properties can be accessed via Spring's
|
||||
`Environment` abstraction or injected directly into Spring-managed components – for
|
||||
example, via `@Value("${redis.host}")` and `@Value("${redis.port}")`, respectively.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
@@ -107,55 +92,7 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
The following example demonstrates how to use `DynamicPropertyRegistry` and
|
||||
`@DynamicPropertySource` with a `@Bean` method. The `api.url` property can be accessed
|
||||
via Spring's `Environment` abstraction or injected directly into other Spring-managed
|
||||
components – for example, via `@Value("${api.url}")`. The value of the `api.url` property
|
||||
will be dynamically retrieved from the `ApiServer` bean.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
class TestConfig {
|
||||
|
||||
@Bean
|
||||
@DynamicPropertySource
|
||||
ApiServer apiServer(DynamicPropertyRegistry registry) {
|
||||
ApiServer apiServer = new ApiServer();
|
||||
registry.add("api.url", apiServer::getUrl);
|
||||
return apiServer;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class TestConfig {
|
||||
|
||||
@Bean
|
||||
@DynamicPropertySource
|
||||
fun apiServer(registry: DynamicPropertyRegistry): ApiServer {
|
||||
val apiServer = ApiServer()
|
||||
registry.add("api.url", apiServer::getUrl)
|
||||
return apiServer
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: The use of `@DynamicPropertySource` on the `@Bean` method is optional and results
|
||||
in the `ApiServer` bean being eagerly initialized so that other beans in the context can
|
||||
be given access to the dynamic properties sourced from the `ApiServer` bean when those
|
||||
other beans are initialized.
|
||||
|
||||
[[testcontext-ctx-management-dynamic-property-sources-precedence]]
|
||||
[[precedence]]
|
||||
== Precedence
|
||||
|
||||
Dynamic properties have higher precedence than those loaded from `@TestPropertySource`,
|
||||
|
||||
+2
-1
@@ -184,6 +184,7 @@ meta-present `@TestPropertySource` annotations. In other words, `locations` and
|
||||
meta-annotation.
|
||||
====
|
||||
|
||||
|
||||
[[default-properties-file-detection]]
|
||||
== Default Properties File Detection
|
||||
|
||||
@@ -194,7 +195,7 @@ if the annotated test class is `com.example.MyTest`, the corresponding default p
|
||||
file is `classpath:com/example/MyTest.properties`. If the default cannot be detected, an
|
||||
`IllegalStateException` is thrown.
|
||||
|
||||
[[testcontext-ctx-management-property-sources-precedence]]
|
||||
[[precedence]]
|
||||
== Precedence
|
||||
|
||||
Test properties have higher precedence than those defined in the operating system's
|
||||
|
||||
@@ -16,8 +16,6 @@ by default, exactly in the following order:
|
||||
Micrometer's `ObservationRegistry`.
|
||||
* `DirtiesContextTestExecutionListener`: Handles the `@DirtiesContext` annotation for
|
||||
"`after`" modes.
|
||||
* `CommonCachesTestExecutionListener`: Clears resource caches in the test's
|
||||
`ApplicationContext` if necessary.
|
||||
* `TransactionalTestExecutionListener`: Provides transactional test execution with
|
||||
default rollback semantics.
|
||||
* `SqlScriptsTestExecutionListener`: Runs SQL scripts configured by using the `@Sql`
|
||||
|
||||
@@ -803,7 +803,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ClassPathResource index = new ClassPathResource("static/index.html");
|
||||
List<String> extensions = List.of("js", "css", "ico", "png", "jpg", "gif");
|
||||
List<String> extensions = Arrays.asList("js", "css", "ico", "png", "jpg", "gif");
|
||||
RequestPredicate spaPredicate = path("/api/**").or(path("/error")).or(pathExtension(extensions::contains)).negate();
|
||||
RouterFunction<ServerResponse> redirectToIndex = route()
|
||||
.resource(spaPredicate, index)
|
||||
|
||||
@@ -97,8 +97,8 @@ Java::
|
||||
Mono<Person> result = client.get()
|
||||
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
|
||||
.retrieve()
|
||||
.onStatus(HttpStatus::is4xxClientError, response -> ...)
|
||||
.onStatus(HttpStatus::is5xxServerError, response -> ...)
|
||||
.onStatus(HttpStatusCode::is4xxClientError, response -> ...)
|
||||
.onStatus(HttpStatusCode::is5xxServerError, response -> ...)
|
||||
.bodyToMono(Person.class);
|
||||
----
|
||||
|
||||
@@ -109,8 +109,8 @@ Kotlin::
|
||||
val result = client.get()
|
||||
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
|
||||
.retrieve()
|
||||
.onStatus(HttpStatus::is4xxClientError) { ... }
|
||||
.onStatus(HttpStatus::is5xxServerError) { ... }
|
||||
.onStatus(HttpStatusCode::is4xxClientError) { ... }
|
||||
.onStatus(HttpStatusCode::is5xxServerError) { ... }
|
||||
.awaitBody<Person>()
|
||||
----
|
||||
======
|
||||
|
||||
@@ -44,10 +44,6 @@ has an `@ExceptionHandler` method that handles any `ErrorResponse` exception, wh
|
||||
includes all built-in web exceptions. You can add more exception handling methods, and
|
||||
use a protected method to map any exception to a `ProblemDetail`.
|
||||
|
||||
You can register `ErrorResponse` interceptors through the
|
||||
xref:web/webflux/config.adoc[WebFlux Config] with a `WebFluxConfigurer`. Use that to intercept
|
||||
any RFC 7807 response and take some action.
|
||||
|
||||
|
||||
|
||||
[[webflux-ann-rest-exceptions-non-standard]]
|
||||
|
||||
@@ -44,11 +44,6 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: When using Spring Boot, you may want to use `@Configuration` classes of type `WebFluxConfigurer` but without
|
||||
`@EnableWebFlux` to keep Spring Boot WebFlux customizations. See more details in
|
||||
xref:#webflux-config-customize[the WebFlux config API section] and in
|
||||
{spring-boot-docs}/web.html#web.reactive.webflux.auto-configuration[the dedicated Spring Boot documentation].
|
||||
|
||||
The preceding example registers a number of Spring WebFlux
|
||||
xref:web/webflux/dispatcher-handler.adoc#webflux-special-bean-types[infrastructure beans] and adapts to dependencies
|
||||
available on the classpath -- for JSON, XML, and others.
|
||||
@@ -69,6 +64,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
// Implement configuration methods...
|
||||
@@ -80,6 +76,7 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
// Implement configuration methods...
|
||||
@@ -105,6 +102,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
@@ -120,6 +118,7 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
override fun addFormatters(registry: FormatterRegistry) {
|
||||
@@ -141,6 +140,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
@@ -157,6 +157,7 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
override fun addFormatters(registry: FormatterRegistry) {
|
||||
@@ -193,6 +194,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
@@ -208,6 +210,7 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
override fun getValidator(): Validator {
|
||||
@@ -276,6 +279,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
@@ -290,6 +294,7 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
override fun configureContentTypeResolver(builder: RequestedContentTypeResolverBuilder) {
|
||||
@@ -314,6 +319,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
@@ -328,10 +334,11 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) {
|
||||
// ...
|
||||
configurer.defaultCodecs().maxInMemorySize(512 * 1024)
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -369,6 +376,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
@@ -383,6 +391,7 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
override fun configureViewResolvers(registry: ViewResolverRegistry) {
|
||||
@@ -403,6 +412,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
|
||||
@@ -427,6 +437,7 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
override fun configureViewResolvers(registry: ViewResolverRegistry) {
|
||||
@@ -452,6 +463,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
|
||||
@@ -468,6 +480,7 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
override fun configureViewResolvers(registry: ViewResolverRegistry) {
|
||||
@@ -490,6 +503,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
|
||||
@@ -510,6 +524,7 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
|
||||
@@ -550,6 +565,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
@@ -567,6 +583,7 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
override fun addResourceHandlers(registry: ResourceHandlerRegistry) {
|
||||
@@ -599,6 +616,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
@@ -617,6 +635,7 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
override fun addResourceHandlers(registry: ResourceHandlerRegistry) {
|
||||
@@ -676,6 +695,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
@@ -691,6 +711,7 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
@@ -735,6 +756,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
@@ -750,6 +772,7 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
@@ -787,6 +810,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
public class WebConfig implements WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
@@ -803,6 +827,7 @@ Kotlin::
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
class WebConfig : WebFluxConfigurer {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -7,8 +7,43 @@
|
||||
`@ExceptionHandler` methods to handle exceptions from controller methods. The following
|
||||
example includes such a handler method:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Controller
|
||||
public class SimpleController {
|
||||
|
||||
// ...
|
||||
|
||||
@ExceptionHandler // <1>
|
||||
public ResponseEntity<String> handle(IOException ex) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Declaring an `@ExceptionHandler`.
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Controller
|
||||
class SimpleController {
|
||||
|
||||
// ...
|
||||
|
||||
@ExceptionHandler // <1>
|
||||
fun handle(ex: IOException): ResponseEntity<String> {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Declaring an `@ExceptionHandler`.
|
||||
======
|
||||
|
||||
include-code::./SimpleController[indent=0]
|
||||
|
||||
|
||||
The exception can match against a top-level exception being propagated (that is, a direct
|
||||
@@ -30,22 +65,6 @@ Support for `@ExceptionHandler` methods in Spring WebFlux is provided by the
|
||||
`HandlerAdapter` for `@RequestMapping` methods. See xref:web/webflux/dispatcher-handler.adoc[`DispatcherHandler`]
|
||||
for more detail.
|
||||
|
||||
[[webflux-ann-exceptionhandler-media]]
|
||||
== Media Type Mapping
|
||||
[.small]#xref:web/webmvc/mvc-controller/ann-exceptionhandler.adoc#mvc-ann-exceptionhandler-media[See equivalent in the Servlet stack]#
|
||||
|
||||
In addition to exception types, `@ExceptionHandler` methods can also declare producible media types.
|
||||
This allows to refine error responses depending on the media types requested by HTTP clients, typically in the "Accept" HTTP request header.
|
||||
|
||||
Applications can declare producible media types directly on annotations, for the same exception type:
|
||||
|
||||
|
||||
include-code::./MediaTypeController[tag=mediatype,indent=0]
|
||||
|
||||
Here, methods handle the same exception type but will not be rejected as duplicates.
|
||||
Instead, API clients requesting "application/json" will receive a JSON error, and browsers will get an HTML error view.
|
||||
Each `@ExceptionHandler` annotation can declare several producible media types,
|
||||
the content negotiation during the error handling phase will decide which content type will be used.
|
||||
|
||||
|
||||
[[webflux-ann-exceptionhandler-args]]
|
||||
|
||||
@@ -20,7 +20,7 @@ Java::
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan("org.example.web") // <1>
|
||||
public class WebConfiguration {
|
||||
public class WebConfig {
|
||||
|
||||
// ...
|
||||
}
|
||||
@@ -33,7 +33,7 @@ Kotlin::
|
||||
----
|
||||
@Configuration
|
||||
@ComponentScan("org.example.web") // <1>
|
||||
class WebConfiguration {
|
||||
class WebConfig {
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
@@ -781,7 +781,7 @@ Java::
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ClassPathResource index = new ClassPathResource("static/index.html");
|
||||
List<String> extensions = List.of("js", "css", "ico", "png", "jpg", "gif");
|
||||
List<String> extensions = Arrays.asList("js", "css", "ico", "png", "jpg", "gif");
|
||||
RequestPredicate spaPredicate = path("/api/**").or(path("/error")).or(pathExtension(extensions::contains)).negate();
|
||||
RouterFunction<ServerResponse> redirectToIndex = route()
|
||||
.resource(spaPredicate, index)
|
||||
|
||||
@@ -445,13 +445,6 @@ directly. For example:
|
||||
}
|
||||
----
|
||||
|
||||
The following `ThreadLocalAccessor` implementations are provided out of the box:
|
||||
|
||||
* `LocaleContextThreadLocalAccessor` -- propagates `LocaleContext` via `LocaleContextHolder`
|
||||
* `RequestAttributesThreadLocalAccessor` -- propagates `RequestAttributes` via `RequestContextHolder`
|
||||
|
||||
The above are not registered automatically. You need to register them via `ContextRegistry.getInstance()` on startup.
|
||||
|
||||
For more details, see the
|
||||
https://micrometer.io/docs/contextPropagation[documentation] of the Micrometer Context
|
||||
Propagation library.
|
||||
|
||||
@@ -44,10 +44,6 @@ has an `@ExceptionHandler` method that handles any `ErrorResponse` exception, wh
|
||||
includes all built-in web exceptions. You can add more exception handling methods, and
|
||||
use a protected method to map any exception to a `ProblemDetail`.
|
||||
|
||||
You can register `ErrorResponse` interceptors through the
|
||||
xref:web/webmvc/mvc-config.adoc[MVC Config] with a `WebMvcConfigurer`. Use that to intercept
|
||||
any RFC 7807 response and take some action.
|
||||
|
||||
|
||||
|
||||
[[mvc-ann-rest-exceptions-non-standard]]
|
||||
@@ -187,7 +183,7 @@ Message codes and arguments for each error are also resolved via `MessageSource`
|
||||
|===
|
||||
|
||||
NOTE: Unlike other exceptions, the message arguments for
|
||||
`MethodArgumentValidException` and `HandlerMethodValidationException` are based on a list of
|
||||
`MethodArgumentValidException` and `HandlerMethodValidationException` are baed on a list of
|
||||
`MessageSourceResolvable` errors that can also be customized through a
|
||||
xref:core/beans/context-introduction.adoc#context-functionality-messagesource[MessageSource]
|
||||
resource bundle. See
|
||||
|
||||
@@ -12,7 +12,30 @@ For advanced mode, you can remove `@EnableWebMvc` and extend directly from
|
||||
`DelegatingWebMvcConfiguration` instead of implementing `WebMvcConfigurer`,
|
||||
as the following example shows:
|
||||
|
||||
include-code::./WebConfiguration[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
public class WebConfig extends DelegatingWebMvcConfiguration {
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
class WebConfig : DelegatingWebMvcConfiguration() {
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
You can keep existing methods in `WebConfig`, but you can now also override bean declarations
|
||||
from the base class, and you can still have any number of other `WebMvcConfigurer` implementations on
|
||||
|
||||
@@ -5,7 +5,39 @@ The MVC namespace does not have an advanced mode. If you need to customize a pro
|
||||
a bean that you cannot change otherwise, you can use the `BeanPostProcessor` lifecycle
|
||||
hook of the Spring `ApplicationContext`, as the following example shows:
|
||||
|
||||
include-code::./MyPostProcessor[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Component
|
||||
public class MyPostProcessor implements BeanPostProcessor {
|
||||
|
||||
public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Component
|
||||
class MyPostProcessor : BeanPostProcessor {
|
||||
|
||||
override fun postProcessBeforeInitialization(bean: Any, name: String): Any {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
Note that you need to declare `MyPostProcessor` as a bean, either explicitly in XML or
|
||||
by letting it be detected through a `<component-scan/>` declaration.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -13,9 +13,59 @@ strategy over path extensions. See
|
||||
xref:web/webmvc/mvc-controller/ann-requestmapping.adoc#mvc-ann-requestmapping-suffix-pattern-match[Suffix Match] and xref:web/webmvc/mvc-controller/ann-requestmapping.adoc#mvc-ann-requestmapping-rfd[Suffix Match and RFD] for
|
||||
more details.
|
||||
|
||||
You can customize requested content type resolution, as the following example shows:
|
||||
In Java configuration, you can customize requested content type resolution, as the
|
||||
following example shows:
|
||||
|
||||
include-code::./WebConfiguration[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
|
||||
configurer.mediaType("json", MediaType.APPLICATION_JSON);
|
||||
configurer.mediaType("xml", MediaType.APPLICATION_XML);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
class WebConfig : WebMvcConfigurer {
|
||||
|
||||
override fun configureContentNegotiation(configurer: ContentNegotiationConfigurer) {
|
||||
configurer.mediaType("json", MediaType.APPLICATION_JSON)
|
||||
configurer.mediaType("xml", MediaType.APPLICATION_XML)
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
The following example shows how to achieve the same configuration in XML:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager"/>
|
||||
|
||||
<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
|
||||
<property name="mediaTypes">
|
||||
<value>
|
||||
json=application/json
|
||||
xml=application/xml
|
||||
</value>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -6,16 +6,119 @@
|
||||
By default, formatters for various number and date types are installed, along with support
|
||||
for customization via `@NumberFormat` and `@DateTimeFormat` on fields.
|
||||
|
||||
To register custom formatters and converters, use the following:
|
||||
To register custom formatters and converters in Java config, use the following:
|
||||
|
||||
include-code::./WebConfiguration[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addFormatters(FormatterRegistry registry) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
class WebConfig : WebMvcConfigurer {
|
||||
|
||||
override fun addFormatters(registry: FormatterRegistry) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
To do the same in XML config, use the following:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:mvc="http://www.springframework.org/schema/mvc"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/mvc
|
||||
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
|
||||
|
||||
<mvc:annotation-driven conversion-service="conversionService"/>
|
||||
|
||||
<bean id="conversionService"
|
||||
class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
|
||||
<property name="converters">
|
||||
<set>
|
||||
<bean class="org.example.MyConverter"/>
|
||||
</set>
|
||||
</property>
|
||||
<property name="formatters">
|
||||
<set>
|
||||
<bean class="org.example.MyFormatter"/>
|
||||
<bean class="org.example.MyAnnotationFormatterFactory"/>
|
||||
</set>
|
||||
</property>
|
||||
<property name="formatterRegistrars">
|
||||
<set>
|
||||
<bean class="org.example.MyFormatterRegistrar"/>
|
||||
</set>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
By default Spring MVC considers the request Locale when parsing and formatting date
|
||||
values. This works for forms where dates are represented as Strings with "input" form
|
||||
fields. For "date" and "time" form fields, however, browsers use a fixed format defined
|
||||
in the HTML spec. For such cases date and time formatting can be customized as follows:
|
||||
|
||||
include-code::./DateTimeWebConfiguration[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addFormatters(FormatterRegistry registry) {
|
||||
DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
|
||||
registrar.setUseIsoFormat(true);
|
||||
registrar.registerFormatters(registry);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
class WebConfig : WebMvcConfigurer {
|
||||
|
||||
override fun addFormatters(registry: FormatterRegistry) {
|
||||
val registrar = DateTimeFormatterRegistrar()
|
||||
registrar.setUseIsoFormat(true)
|
||||
registrar.registerFormatters(registry)
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: See xref:core/validation/format.adoc#format-FormatterRegistrar-SPI[the `FormatterRegistrar` SPI]
|
||||
and the `FormattingConversionServiceFactoryBean` for more information on when to use
|
||||
|
||||
@@ -6,7 +6,33 @@
|
||||
In Java configuration, you can implement the `WebMvcConfigurer` interface, as the
|
||||
following example shows:
|
||||
|
||||
include-code::./WebConfiguration[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
// Implement configuration methods...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
class WebConfig : WebMvcConfigurer {
|
||||
|
||||
// Implement configuration methods...
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
In XML, you can check attributes and sub-elements of `<mvc:annotation-driven/>`. You can
|
||||
view the https://schema.spring.io/mvc/spring-mvc.xsd[Spring MVC XML schema] or use
|
||||
|
||||
+80
-2
@@ -15,7 +15,44 @@ lower than that of the `DefaultServletHttpRequestHandler`, which is `Integer.MAX
|
||||
|
||||
The following example shows how to enable the feature by using the default setup:
|
||||
|
||||
include-code::./WebConfiguration[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
|
||||
configurer.enable();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
class WebConfig : WebMvcConfigurer {
|
||||
|
||||
override fun configureDefaultServletHandling(configurer: DefaultServletHandlerConfigurer) {
|
||||
configurer.enable()
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows how to achieve the same configuration in XML:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<mvc:default-servlet-handler/>
|
||||
----
|
||||
|
||||
The caveat to overriding the `/` Servlet mapping is that the `RequestDispatcher` for the
|
||||
default Servlet must be retrieved by name rather than by path. The
|
||||
@@ -26,4 +63,45 @@ If the default Servlet has been custom-configured with a different name, or if a
|
||||
different Servlet container is being used where the default Servlet name is unknown,
|
||||
then you must explicitly provide the default Servlet's name, as the following example shows:
|
||||
|
||||
include-code::./CustomDefaultServletConfiguration[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
|
||||
configurer.enable("myCustomDefaultServlet");
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
class WebConfig : WebMvcConfigurer {
|
||||
|
||||
override fun configureDefaultServletHandling(configurer: DefaultServletHandlerConfigurer) {
|
||||
configurer.enable("myCustomDefaultServlet")
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
The following example shows how to achieve the same configuration in XML:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<mvc:default-servlet-handler default-servlet-name="myCustomDefaultServlet"/>
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,11 +3,50 @@
|
||||
|
||||
[.small]#xref:web/webflux/config.adoc#webflux-config-enable[See equivalent in the Reactive stack]#
|
||||
|
||||
You can use the `@EnableWebMvc` annotation to enable MVC configuration with programmatic configuration, or `<mvc:annotation-driven>` with XML configuration, as the following example shows:
|
||||
In Java configuration, you can use the `@EnableWebMvc` annotation to enable MVC
|
||||
configuration, as the following example shows:
|
||||
|
||||
include-code::./WebConfiguration[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class WebConfig {
|
||||
}
|
||||
----
|
||||
|
||||
NOTE: When using Spring Boot, you may want to use `@Configuration` classes of type `WebMvcConfigurer` but without `@EnableWebMvc` to keep Spring Boot MVC customizations. See more details in xref:web/webmvc/mvc-config/customize.adoc[the MVC Config API section] and in {spring-boot-docs}/web.html#web.servlet.spring-mvc.auto-configuration[the dedicated Spring Boot documentation].
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
class WebConfig
|
||||
----
|
||||
======
|
||||
|
||||
In XML configuration, you can use the `<mvc:annotation-driven>` element to enable MVC
|
||||
configuration, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:mvc="http://www.springframework.org/schema/mvc"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/mvc
|
||||
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
|
||||
|
||||
<mvc:annotation-driven/>
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
The preceding example registers a number of Spring MVC
|
||||
xref:web/webmvc/mvc-servlet/special-bean-types.adoc[infrastructure beans] and adapts to dependencies
|
||||
|
||||
@@ -1,9 +1,56 @@
|
||||
[[mvc-config-interceptors]]
|
||||
= Interceptors
|
||||
|
||||
You can register interceptors to apply to incoming requests, as the following example shows:
|
||||
In Java configuration, you can register interceptors to apply to incoming requests, as
|
||||
the following example shows:
|
||||
|
||||
include-code::./WebConfiguration[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(new LocaleChangeInterceptor());
|
||||
registry.addInterceptor(new ThemeChangeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**");
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
class WebConfig : WebMvcConfigurer {
|
||||
|
||||
override fun addInterceptors(registry: InterceptorRegistry) {
|
||||
registry.addInterceptor(LocaleChangeInterceptor())
|
||||
registry.addInterceptor(ThemeChangeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**")
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows how to achieve the same configuration in XML:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<mvc:interceptors>
|
||||
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/>
|
||||
<mvc:interceptor>
|
||||
<mvc:mapping path="/**"/>
|
||||
<mvc:exclude-mapping path="/admin/**"/>
|
||||
<bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor"/>
|
||||
</mvc:interceptor>
|
||||
</mvc:interceptors>
|
||||
----
|
||||
|
||||
WARNING: Interceptors are not ideally suited as a security layer due to the potential for
|
||||
a mismatch with annotated controller path matching. Generally, we recommend using Spring
|
||||
|
||||
@@ -15,10 +15,48 @@ Boot application, prefer to use the {spring-boot-docs}/web.html#web.servlet.spri
|
||||
mechanism. Or alternatively, use `extendMessageConverters` to modify message converters
|
||||
at the end.
|
||||
|
||||
The following example adds XML and Jackson JSON converters with a customized `ObjectMapper`
|
||||
instead of the default ones:
|
||||
The following example adds XML and Jackson JSON converters with a customized
|
||||
`ObjectMapper` instead of the default ones:
|
||||
|
||||
include-code::./WebConfiguration[tag=snippet,indent=0]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class WebConfiguration implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
|
||||
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
|
||||
.indentOutput(true)
|
||||
.dateFormat(new SimpleDateFormat("yyyy-MM-dd"))
|
||||
.modulesToInstall(new ParameterNamesModule());
|
||||
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
|
||||
converters.add(new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build()));
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
class WebConfiguration : WebMvcConfigurer {
|
||||
|
||||
override fun configureMessageConverters(converters: MutableList<HttpMessageConverter<*>>) {
|
||||
val builder = Jackson2ObjectMapperBuilder()
|
||||
.indentOutput(true)
|
||||
.dateFormat(SimpleDateFormat("yyyy-MM-dd"))
|
||||
.modulesToInstall(ParameterNamesModule())
|
||||
converters.add(MappingJackson2HttpMessageConverter(builder.build()))
|
||||
converters.add(MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build()))
|
||||
----
|
||||
======
|
||||
|
||||
In the preceding example,
|
||||
{spring-framework-api}/http/converter/json/Jackson2ObjectMapperBuilder.html[`Jackson2ObjectMapperBuilder`]
|
||||
@@ -38,7 +76,7 @@ It also automatically registers the following well-known modules if they are det
|
||||
* {jackson-github-org}/jackson-datatype-joda[jackson-datatype-joda]: Support for Joda-Time types.
|
||||
* {jackson-github-org}/jackson-datatype-jsr310[jackson-datatype-jsr310]: Support for Java 8 Date and Time API types.
|
||||
* {jackson-github-org}/jackson-datatype-jdk8[jackson-datatype-jdk8]: Support for other Java 8 types, such as `Optional`.
|
||||
* {jackson-github-org}/jackson-module-kotlin[jackson-module-kotlin]: Support for Kotlin classes and data classes.
|
||||
* {jackson-github-org}/jackson-module-kotlin[`jackson-module-kotlin`]: Support for Kotlin classes and data classes.
|
||||
|
||||
NOTE: Enabling indentation with Jackson XML support requires
|
||||
https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.codehaus.woodstox%22%20AND%20a%3A%22woodstox-core-asl%22[`woodstox-core-asl`]
|
||||
@@ -48,3 +86,29 @@ Other interesting Jackson modules are available:
|
||||
|
||||
* https://github.com/zalando/jackson-datatype-money[jackson-datatype-money]: Support for `javax.money` types (unofficial module).
|
||||
* {jackson-github-org}/jackson-datatype-hibernate[jackson-datatype-hibernate]: Support for Hibernate-specific types and properties (including lazy-loading aspects).
|
||||
|
||||
The following example shows how to achieve the same configuration in XML:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<mvc:annotation-driven>
|
||||
<mvc:message-converters>
|
||||
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
|
||||
<property name="objectMapper" ref="objectMapper"/>
|
||||
</bean>
|
||||
<bean class="org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter">
|
||||
<property name="objectMapper" ref="xmlMapper"/>
|
||||
</bean>
|
||||
</mvc:message-converters>
|
||||
</mvc:annotation-driven>
|
||||
|
||||
<bean id="objectMapper" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"
|
||||
p:indentOutput="true"
|
||||
p:simpleDateFormat="yyyy-MM-dd"
|
||||
p:modulesToInstall="com.fasterxml.jackson.module.paramnames.ParameterNamesModule"/>
|
||||
|
||||
<bean id="xmlMapper" parent="objectMapper" p:createXmlMapper="true"/>
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -7,6 +7,61 @@ You can customize options related to path matching and treatment of the URL.
|
||||
For details on the individual options, see the
|
||||
{spring-framework-api}/web/servlet/config/annotation/PathMatchConfigurer.html[`PathMatchConfigurer`] javadoc.
|
||||
|
||||
The following example shows how to customize path matching:
|
||||
The following example shows how to customize path matching in Java configuration:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void configurePathMatch(PathMatchConfigurer configurer) {
|
||||
configurer.addPathPrefix("/api", HandlerTypePredicate.forAnnotation(RestController.class));
|
||||
}
|
||||
|
||||
private PathPatternParser patternParser() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
class WebConfig : WebMvcConfigurer {
|
||||
|
||||
override fun configurePathMatch(configurer: PathMatchConfigurer) {
|
||||
configurer.addPathPrefix("/api", HandlerTypePredicate.forAnnotation(RestController::class.java))
|
||||
}
|
||||
|
||||
fun patternParser(): PathPatternParser {
|
||||
//...
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
The following example shows how to customize path matching in XML configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<mvc:annotation-driven>
|
||||
<mvc:path-matching
|
||||
path-helper="pathHelper"
|
||||
path-matcher="pathMatcher"/>
|
||||
</mvc:annotation-driven>
|
||||
|
||||
<bean id="pathHelper" class="org.example.app.MyPathHelper"/>
|
||||
<bean id="pathMatcher" class="org.example.app.MyPathMatcher"/>
|
||||
----
|
||||
|
||||
|
||||
|
||||
include-code::./WebConfiguration[tag=snippet,indent=0]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user