mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
1 Commits
v6.0.3
..
v6.0.0-RC1
| Author | SHA1 | Date | |
|---|---|---|---|
| 8cc11d1a68 |
@@ -3,8 +3,6 @@ name: Backport Bot
|
||||
on:
|
||||
issues:
|
||||
types: [labeled]
|
||||
pull_request:
|
||||
types: [labeled]
|
||||
push:
|
||||
branches:
|
||||
- '*.x'
|
||||
@@ -15,7 +13,6 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
+2
-1
@@ -21,8 +21,9 @@ derby.log
|
||||
/build
|
||||
buildSrc/build
|
||||
/spring-*/build
|
||||
/spring-core/graalvm/build
|
||||
/spring-core/kotlin-coroutines/build
|
||||
/framework-bom/build
|
||||
/framework-docs/build
|
||||
/integration-tests/build
|
||||
/src/asciidoc/build
|
||||
spring-test/test-output/
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# Enable auto-env through the sdkman_auto_env config
|
||||
# Add key=value pairs of SDKs to use below
|
||||
java=17.0.5-librca
|
||||
java=17.0.4.1-librca
|
||||
|
||||
+3
-3
@@ -123,12 +123,12 @@ define the source file coding standards we use along with some IDEA editor setti
|
||||
|
||||
### Reference Docs
|
||||
|
||||
The reference documentation is in the [framework-docs/src/docs/asciidoc](framework-docs/src/docs/asciidoc) directory, in
|
||||
The reference documentation is in the [src/docs/asciidoc](src/docs/asciidoc) directory, in
|
||||
[Asciidoctor](https://asciidoctor.org/) format. For trivial changes, you may be able to browse,
|
||||
edit source files, and submit directly from GitHub.
|
||||
|
||||
When making changes locally, execute `./gradlew :framework-docs:asciidoctor` and then browse the result under
|
||||
`framework-docs/build/docs/ref-docs/html5/index.html`.
|
||||
When making changes locally, execute `./gradlew asciidoctor` and then browse the result under
|
||||
`build/docs/ref-docs/html5/index.html`.
|
||||
|
||||
Asciidoctor also supports live editing. For more details see
|
||||
[AsciiDoc Tooling](https://docs.asciidoctor.org/asciidoctor/latest/tooling/).
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [](https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-5.3.x?groups=Build") [](https://ge.spring.io/scans?search.rootProjectNames=spring)
|
||||
# <img src="src/docs/spring-framework.png" width="80" height="80"> Spring Framework [](https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-5.3.x?groups=Build") [](https://ge.spring.io/scans?search.rootProjectNames=spring)
|
||||
|
||||
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".
|
||||
|
||||
@@ -14,7 +14,7 @@ For access to artifacts or a distribution zip, see the [Spring Framework Artifac
|
||||
|
||||
## Documentation
|
||||
|
||||
The Spring Framework maintains reference documentation ([published](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/) and [source](framework-docs/src/docs/asciidoc)), GitHub [wiki pages](https://github.com/spring-projects/spring-framework/wiki), and an
|
||||
The Spring Framework maintains reference documentation ([published](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/) and [source](src/docs/asciidoc)), GitHub [wiki pages](https://github.com/spring-projects/spring-framework/wiki), and an
|
||||
[API reference](https://docs.spring.io/spring-framework/docs/current/javadoc-api/). There are also [guides and tutorials](https://spring.io/guides) across Spring projects.
|
||||
|
||||
## Micro-Benchmarks
|
||||
|
||||
+42
-19
@@ -1,33 +1,29 @@
|
||||
plugins {
|
||||
id 'io.spring.nohttp' version '0.0.10'
|
||||
id 'io.freefair.aspectj' version '6.5.0.3' apply false
|
||||
// kotlinVersion is managed in gradle.properties
|
||||
id 'org.jetbrains.kotlin.plugin.serialization' version "${kotlinVersion}" apply false
|
||||
id 'org.jetbrains.dokka' version '1.7.20'
|
||||
id 'org.asciidoctor.jvm.convert' version '3.3.2' apply false
|
||||
id 'org.asciidoctor.jvm.pdf' version '3.3.2' apply false
|
||||
id 'org.jetbrains.dokka' version '1.7.10' apply false
|
||||
id 'org.jetbrains.kotlin.jvm' version '1.7.20' apply false
|
||||
id 'org.jetbrains.kotlin.plugin.serialization' version '1.7.20' apply false
|
||||
id 'org.asciidoctor.jvm.convert' version '3.3.2'
|
||||
id 'org.asciidoctor.jvm.pdf' version '3.3.2'
|
||||
id 'org.unbroken-dome.xjc' version '2.0.0' apply false
|
||||
id 'com.github.ben-manes.versions' version '0.42.0'
|
||||
id 'com.github.johnrengelman.shadow' version '7.1.2' apply false
|
||||
id 'de.undercouch.download' version '5.1.0'
|
||||
id 'me.champeau.jmh' version '0.6.8' apply false
|
||||
id 'me.champeau.jmh' version '0.6.6' apply false
|
||||
}
|
||||
|
||||
ext {
|
||||
moduleProjects = subprojects.findAll { it.name.startsWith("spring-") }
|
||||
javaProjects = subprojects - project(":framework-bom") - project(":framework-platform")
|
||||
withoutJclOverSlf4j = {
|
||||
exclude group: "org.slf4j", name: "jcl-over-slf4j"
|
||||
}
|
||||
}
|
||||
|
||||
configure(allprojects) { project ->
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url "https://repo.spring.io/milestone"
|
||||
content {
|
||||
// Netty 5 optional support
|
||||
includeGroup 'io.projectreactor.netty'
|
||||
}
|
||||
}
|
||||
maven { url "https://repo.spring.io/libs-spring-framework-build" }
|
||||
if (version.contains('-')) {
|
||||
maven { url "https://repo.spring.io/milestone" }
|
||||
@@ -63,22 +59,37 @@ configure([rootProject] + javaProjects) { project ->
|
||||
matching { it.name.endsWith("Classpath") }.all { it.extendsFrom(dependencyManagement) }
|
||||
}
|
||||
|
||||
pluginManager.withPlugin("kotlin") {
|
||||
apply plugin: "org.jetbrains.dokka"
|
||||
apply from: "${rootDir}/gradle/docs-dokka.gradle"
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions {
|
||||
languageVersion = "1.7"
|
||||
apiVersion = "1.7"
|
||||
freeCompilerArgs = ["-Xjsr305=strict", "-Xsuppress-version-warnings", "-opt-in=kotlin.RequiresOptIn"]
|
||||
allWarningsAsErrors = true
|
||||
}
|
||||
}
|
||||
compileTestKotlin {
|
||||
kotlinOptions {
|
||||
freeCompilerArgs = ["-Xjsr305=strict", "-opt-in=kotlin.RequiresOptIn"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
include(["**/*Tests.class", "**/*Test.class"])
|
||||
systemProperty("java.awt.headless", "true")
|
||||
systemProperty("testGroups", project.properties.get("testGroups"))
|
||||
systemProperty("io.netty.leakDetection.level", "paranoid")
|
||||
systemProperty("io.netty5.leakDetectionLevel", "paranoid")
|
||||
systemProperty("io.netty5.leakDetection.targetRecords", "32")
|
||||
systemProperty("io.netty5.buffer.lifecycleTracingEnabled", "true")
|
||||
systemProperty("io.netty5.buffer.leakDetectionEnabled", "true")
|
||||
jvmArgs(["--add-opens=java.base/java.lang=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.util=ALL-UNNAMED"])
|
||||
}
|
||||
|
||||
checkstyle {
|
||||
toolVersion = "10.5.0"
|
||||
toolVersion = "10.3.3"
|
||||
configDirectory.set(rootProject.file("src/checkstyle"))
|
||||
}
|
||||
|
||||
@@ -124,7 +135,7 @@ configure([rootProject] + javaProjects) { project ->
|
||||
"https://fasterxml.github.io/jackson-core/javadoc/2.10/",
|
||||
"https://fasterxml.github.io/jackson-databind/javadoc/2.10/",
|
||||
"https://fasterxml.github.io/jackson-dataformat-xml/javadoc/2.10/",
|
||||
"https://hc.apache.org/httpcomponents-client-5.2.x/current/httpclient5/apidocs/",
|
||||
"https://hc.apache.org/httpcomponents-client-5.1.x/current/httpclient5/apidocs/",
|
||||
"https://projectreactor.io/docs/test/release/api/",
|
||||
"https://junit.org/junit4/javadoc/4.13.2/",
|
||||
// TODO Uncomment link to JUnit 5 docs once we have sorted out
|
||||
@@ -152,8 +163,11 @@ configure(moduleProjects) { project ->
|
||||
configure(rootProject) {
|
||||
description = "Spring Framework"
|
||||
|
||||
apply plugin: "kotlin"
|
||||
apply plugin: "io.spring.nohttp"
|
||||
apply plugin: 'org.springframework.build.api-diff'
|
||||
apply from: "${rootDir}/gradle/publications.gradle"
|
||||
apply from: "${rootDir}/gradle/docs.gradle"
|
||||
|
||||
nohttp {
|
||||
source.exclude "**/test-output/**"
|
||||
@@ -174,4 +188,13 @@ configure(rootProject) {
|
||||
maxHeapSize = "1g"
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
artifact docsZip
|
||||
artifact schemaZip
|
||||
artifact distZip
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -7,8 +7,7 @@ They are declared in the `build.gradle` file in this folder.
|
||||
|
||||
The `org.springframework.build.conventions` plugin applies all conventions to the Framework build:
|
||||
|
||||
* Configuring the Java compiler, see `JavaConventions`
|
||||
* Configuring the Kotlin compiler, see `KotlinConventions`
|
||||
* Configuring the Java compiler, see `CompilerConventions`
|
||||
* Configuring testing in the build with `TestConventions`
|
||||
|
||||
|
||||
|
||||
@@ -7,18 +7,7 @@ repositories {
|
||||
gradlePluginPortal()
|
||||
}
|
||||
|
||||
ext {
|
||||
def propertiesFile = new File(new File("$projectDir").parentFile, "gradle.properties")
|
||||
propertiesFile.withInputStream {
|
||||
def properties = new Properties()
|
||||
properties.load(it)
|
||||
set("kotlinVersion", properties["kotlinVersion"])
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
|
||||
implementation("org.jetbrains.kotlin:kotlin-compiler-embeddable:${kotlinVersion}")
|
||||
implementation "me.champeau.gradle:japicmp-gradle-plugin:0.3.0"
|
||||
implementation "org.gradle:test-retry-gradle-plugin:1.4.1"
|
||||
}
|
||||
|
||||
+2
-1
@@ -23,6 +23,7 @@ import java.util.List;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.plugins.JavaBasePlugin;
|
||||
import org.gradle.api.plugins.JavaLibraryPlugin;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.tasks.compile.JavaCompile;
|
||||
|
||||
@@ -33,7 +34,7 @@ import org.gradle.api.tasks.compile.JavaCompile;
|
||||
* @author Sam Brannen
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class JavaConventions {
|
||||
public class CompilerConventions {
|
||||
|
||||
private static final List<String> COMPILER_ARGS;
|
||||
|
||||
@@ -19,7 +19,6 @@ package org.springframework.build;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.plugins.JavaBasePlugin;
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinBasePlugin;
|
||||
|
||||
/**
|
||||
* Plugin to apply conventions to projects that are part of Spring Framework's build.
|
||||
@@ -27,9 +26,7 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinBasePlugin;
|
||||
*
|
||||
* When the {@link JavaBasePlugin} is applied, the conventions in {@link TestConventions}
|
||||
* are applied.
|
||||
* When the {@link JavaBasePlugin} is applied, the conventions in {@link JavaConventions}
|
||||
* are applied.
|
||||
* When the {@link KotlinBasePlugin} is applied, the conventions in {@link KotlinConventions}
|
||||
* When the {@link JavaBasePlugin} is applied, the conventions in {@link CompilerConventions}
|
||||
* are applied.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
@@ -38,8 +35,7 @@ public class ConventionsPlugin implements Plugin<Project> {
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
new JavaConventions().apply(project);
|
||||
new KotlinConventions().apply(project);
|
||||
new TestConventions().apply(project);
|
||||
new CompilerConventions().apply(project);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.build;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.gradle.api.Project;
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions;
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile;
|
||||
|
||||
/**
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class KotlinConventions {
|
||||
|
||||
void apply(Project project) {
|
||||
project.getPlugins().withId("org.jetbrains.kotlin.jvm",
|
||||
(plugin) -> project.getTasks().withType(KotlinCompile.class, this::configure));
|
||||
}
|
||||
|
||||
private void configure(KotlinCompile compile) {
|
||||
KotlinJvmOptions kotlinOptions = compile.getKotlinOptions();
|
||||
kotlinOptions.setApiVersion("1.7");
|
||||
kotlinOptions.setLanguageVersion("1.7");
|
||||
kotlinOptions.setJvmTarget("17");
|
||||
kotlinOptions.setJavaParameters(true);
|
||||
kotlinOptions.setAllWarningsAsErrors(true);
|
||||
List<String> freeCompilerArgs = new ArrayList<>(compile.getKotlinOptions().getFreeCompilerArgs());
|
||||
freeCompilerArgs.addAll(List.of("-Xsuppress-version-warnings", "-Xjsr305=strict", "-opt-in=kotlin.RequiresOptIn"));
|
||||
compile.getKotlinOptions().setFreeCompilerArgs(freeCompilerArgs);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ubuntu:focal-20220922
|
||||
FROM ubuntu:focal-20220826
|
||||
|
||||
ADD setup.sh /setup.sh
|
||||
ADD get-jdk-url.sh /get-jdk-url.sh
|
||||
|
||||
@@ -3,13 +3,13 @@ set -e
|
||||
|
||||
case "$1" in
|
||||
java17)
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/17.0.5+8/bellsoft-jdk17.0.5+8-linux-amd64.tar.gz"
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/17.0.4.1+1/bellsoft-jdk17.0.4.1+1-linux-amd64.tar.gz"
|
||||
;;
|
||||
java18)
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/18.0.2.1%2B1/bellsoft-jdk18.0.2.1+1-linux-amd64.tar.gz"
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/18.0.2.1+1/bellsoft-jdk18.0.2.1+1-linux-amd64.tar.gz"
|
||||
;;
|
||||
java19)
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/19.0.1%2B11/bellsoft-jdk19.0.1+11-linux-amd64.tar.gz"
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/19+37/bellsoft-jdk19+37-linux-amd64.tar.gz"
|
||||
;;
|
||||
*)
|
||||
echo $"Unknown java version"
|
||||
|
||||
+4
-4
@@ -218,21 +218,21 @@ jobs:
|
||||
threads: 8
|
||||
artifact_set:
|
||||
- include:
|
||||
- "/**/framework-docs-*.zip"
|
||||
- "/**/spring-*.zip"
|
||||
properties:
|
||||
"zip.name": "spring-framework"
|
||||
"zip.displayname": "Spring Framework"
|
||||
"zip.deployed": "false"
|
||||
- include:
|
||||
- "/**/framework-docs-*-docs.zip"
|
||||
- "/**/spring-*-docs.zip"
|
||||
properties:
|
||||
"zip.type": "docs"
|
||||
- include:
|
||||
- "/**/framework-docs-*-dist.zip"
|
||||
- "/**/spring-*-dist.zip"
|
||||
properties:
|
||||
"zip.type": "dist"
|
||||
- include:
|
||||
- "/**/framework-docs-*-schema.zip"
|
||||
- "/**/spring-*-schema.zip"
|
||||
properties:
|
||||
"zip.type": "schema"
|
||||
get_params:
|
||||
|
||||
@@ -4,7 +4,7 @@ image_resource:
|
||||
type: registry-image
|
||||
source:
|
||||
repository: springio/github-changelog-generator
|
||||
tag: '0.0.8'
|
||||
tag: '0.0.7'
|
||||
inputs:
|
||||
- name: git-repo
|
||||
- name: artifactory-repo
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
:chomp: default headers packages
|
||||
:docs-site: https://docs.spring.io
|
||||
// Spring Framework
|
||||
:docs-spring-framework: {docs-site}/spring-framework/docs/{spring-version}
|
||||
:api-spring-framework: {docs-spring-framework}/javadoc-api/org/springframework
|
||||
:docs-java: {docdir}/../../main/java/org/springframework/docs
|
||||
:docs-kotlin: {docdir}/../../main/kotlin/org/springframework/docs
|
||||
:docs-resources: {docdir}/../../main/resources
|
||||
:spring-framework-main-code: https://github.com/spring-projects/spring-framework/tree/main
|
||||
// Spring portfolio Links
|
||||
:docs-spring-boot: {docs-site}/spring-boot/docs/current/reference
|
||||
:docs-spring-gemfire: {docs-site}/spring-gemfire/docs/current/reference
|
||||
:docs-spring-security: {docs-site}/spring-security/reference
|
||||
// Third-party Links
|
||||
:docs-graalvm: https://www.graalvm.org/22.3/reference-manual
|
||||
:gh-rsocket: https://github.com/rsocket
|
||||
:gh-rsocket-extensions: {gh-rsocket}/rsocket/blob/master/Extensions
|
||||
:gh-rsocket-java: {gh-rsocket}/rsocket-java
|
||||
@@ -1,307 +0,0 @@
|
||||
[[core.aot]]
|
||||
= Ahead of Time Optimizations
|
||||
|
||||
This chapter covers Spring's Ahead of Time (AOT) optimizations.
|
||||
|
||||
For AOT support specific to integration tests, see <<testing.adoc#testcontext-aot, Ahead of Time Support for Tests>>.
|
||||
|
||||
[[core.aot.introduction]]
|
||||
== Introduction to Ahead of Time Optimizations
|
||||
|
||||
Spring's support for AOT optimizations is meant to inspect an `ApplicationContext` at build time and apply decisions and discovery logic that usually happens at runtime.
|
||||
Doing so allows building an application startup arrangement that is more straightforward and focused on a fixed set of features based mainly on the classpath and the `Environment`.
|
||||
|
||||
Applying such optimizations early implies the following restrictions:
|
||||
|
||||
* The classpath is fixed and fully defined at build time.
|
||||
* The beans defined in your application cannot change at runtime, meaning:
|
||||
** `@Profile`, in particular profile-specific configuration needs to be chosen at build time.
|
||||
** Environment properties that impact the presence of a bean (`@Conditional`) are only considered at build time.
|
||||
|
||||
When these restrictions are in place, it becomes possible to perform ahead-of-time processing at build time and generate additional assets.
|
||||
A Spring AOT processed application typically generates:
|
||||
|
||||
* Java source code
|
||||
* Bytecode (usually for dynamic proxies)
|
||||
* {api-spring-framework}/aot/hint/RuntimeHints.html[`RuntimeHints`] for the use of reflection, resource loading, serialization, and JDK proxies.
|
||||
|
||||
NOTE: At the moment, AOT is focused on allowing Spring applications to be deployed as native images using GraalVM.
|
||||
We intend to support more JVM-based use cases in future generations.
|
||||
|
||||
[[core.aot.basics]]
|
||||
== AOT engine overview
|
||||
|
||||
The entry point of the AOT engine for processing an `ApplicationContext` arrangement is `ApplicationContextAotGenerator`. It takes care of the following steps, based on a `GenericApplicationContext` that represents the application to optimize and a {api-spring-framework}/aot/generate/GenerationContext.html[`GenerationContext`]:
|
||||
|
||||
* Refresh an `ApplicationContext` for AOT processing. Contrary to a traditional refresh, this version only creates bean definitions, not bean instances.
|
||||
* Invoke the available `BeanFactoryInitializationAotProcessor` implementations and apply their contributions against the `GenerationContext`.
|
||||
For instance, a core implementation iterates over all candidate bean definitions and generates the necessary code to restore the state of the `BeanFactory`.
|
||||
|
||||
Once this process completes, the `GenerationContext` will have been updated with the generated code, resources, and classes that are necessary for the application to run.
|
||||
The `RuntimeHints` instance can also be used to generate the relevant GraalVM native image configuration files.
|
||||
|
||||
`ApplicationContextAotGenerator#processAheadOfTime` returns the class name of the `ApplicationContextInitializer` entry point that allows the context to be started with AOT optimizations.
|
||||
|
||||
Those steps are covered in greater detail in the sections below.
|
||||
|
||||
[[core.aot.refresh]]
|
||||
== Refresh for AOT Processing
|
||||
|
||||
Refresh for AOT processing is supported on all `GenericApplicationContext` implementations.
|
||||
An application context is created with any number of entry points, usually in the form of `@Configuration`-annotated classes.
|
||||
|
||||
Let's look at a basic example:
|
||||
|
||||
include::code:AotProcessingSample[tag=myapplication]
|
||||
|
||||
Starting this application with the regular runtime involves a number of steps including classpath scanning, configuration class parsing, bean instantiation, and lifecycle callback handling.
|
||||
Refresh for AOT processing only applies a subset of what happens with a <<beans-introduction,regular `refresh`>>.
|
||||
AOT processing can be triggered as follows:
|
||||
|
||||
include::code:AotProcessingSample[tag=aotcontext]
|
||||
|
||||
In this mode, <<beans-factory-extension-factory-postprocessors,`BeanFactoryPostProcessor` implementations>> are invoked as usual.
|
||||
This includes configuration class parsing, import selectors, classpath scanning, etc.
|
||||
Such steps make sure that the `BeanRegistry` contains the relevant bean definitions for the application.
|
||||
If bean definitions are guarded by conditions (such as `@Profile`), these are discarded at this stage.
|
||||
|
||||
Because this mode does not actually create bean instances, `BeanPostProcessor` implementations are not invoked, except for specific variants that are relevant for AOT processing.
|
||||
These are:
|
||||
|
||||
* `MergedBeanDefinitionPostProcessor` implementations post-process bean definitions to extract additional settings, such as `init` and `destroy` methods.
|
||||
* `SmartInstantiationAwareBeanPostProcessor` implementations determine a more precise bean type if necessary.
|
||||
This makes sure to create any proxy that will be required at runtime.
|
||||
|
||||
One this part completes, the `BeanFactory` contains the bean definitions that are necessary for the application to run. It does not trigger bean instantiation but allows the AOT engine to inspect the beans that will be created at runtime.
|
||||
|
||||
[[core.aot.bean-factory-initialization-contributions]]
|
||||
== Bean Factory Initialization AOT Contributions
|
||||
|
||||
Components that want to participate in this step can implement the {api-spring-framework}/beans/factory/aot/BeanFactoryInitializationAotProcessor.html[`BeanFactoryInitializationAotProcessor`] interface.
|
||||
Each implementation can return an AOT contribution, based on the state of the bean factory.
|
||||
|
||||
An AOT contribution is a component that contributes generated code that reproduces a particular behavior.
|
||||
It can also contribute `RuntimeHints` to indicate the need for reflection, resource loading, serialization, or JDK proxies.
|
||||
|
||||
A `BeanFactoryInitializationAotProcessor` implementation can be registered in `META-INF/spring/aot.factories` with a key equal to the fully qualified name of the interface.
|
||||
|
||||
A `BeanFactoryInitializationAotProcessor` can also be implemented directly by a bean.
|
||||
In this mode, the bean provides an AOT contribution equivalent to the feature it provides with a regular runtime.
|
||||
Consequently, such a bean is automatically excluded from the AOT-optimized context.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
If a bean implements the `BeanFactoryInitializationAotProcessor` interface, the bean and **all** of its dependencies will be initialized during AOT processing.
|
||||
We generally recommend that this interface is only implemented by infrastructure beans such as `BeanFactoryPostProcessor` which have limited dependencies and are already initialized early in the bean factory lifecycle.
|
||||
If such a bean is registered using an `@Bean` factory method, ensure the method is `static` so that its enclosing `@Configuration` class does not have to be initialized.
|
||||
====
|
||||
|
||||
|
||||
[[core.aot.bean-registration-contributions]]
|
||||
=== Bean Registration AOT Contributions
|
||||
|
||||
A core `BeanFactoryInitializationAotProcessor` implementation is responsible for collecting the necessary contributions for each candidate `BeanDefinition`.
|
||||
It does so using a dedicated `BeanRegistrationAotProcessor`.
|
||||
|
||||
This interface is used as follows:
|
||||
|
||||
* Implemented by a `BeanPostProcessor` bean, to replace its runtime behavior.
|
||||
For instance <<beans-factory-extension-bpp-examples-aabpp,`AutowiredAnnotationBeanPostProcessor`>> implements this interface to generate code that injects members annotated with `@Autowired`.
|
||||
* Implemented by a type registered in `META-INF/spring/aot.factories` with a key equal to the fully qualified name of the interface.
|
||||
Typically used when the bean definition needs to be tuned for specific features of the core framework.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
If a bean implements the `BeanRegistrationAotProcessor` interface, the bean and **all** of its dependencies will be initialized during AOT processing.
|
||||
We generally recommend that this interface is only implemented by infrastructure beans such as `BeanFactoryPostProcessor` which have limited dependencies and are already initialized early in the bean factory lifecycle.
|
||||
If such a bean is registered using an `@Bean` factory method, ensure the method is `static` so that its enclosing `@Configuration` class does not have to be initialized.
|
||||
====
|
||||
|
||||
If no `BeanRegistrationAotProcessor` handles a particular registered bean, a default implementation processes it.
|
||||
This is the default behavior, since tuning the generated code for a bean definition should be restricted to corner cases.
|
||||
|
||||
Taking our previous example, let's assume that `DataSourceConfiguration` is as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class DataSourceConfiguration {
|
||||
|
||||
@Bean
|
||||
public SimpleDataSource dataSource() {
|
||||
return new SimpleDataSource();
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Since there isn't any particular condition on this class, `dataSourceConfiguration` and `dataSource` are identified as candidates.
|
||||
The AOT engine will convert the configuration class above to code similar to the following:
|
||||
|
||||
[source,java,indent=0,role="primary"]
|
||||
.Java
|
||||
----
|
||||
/**
|
||||
* Bean definitions for {@link DataSourceConfiguration}
|
||||
*/
|
||||
public class DataSourceConfiguration__BeanDefinitions {
|
||||
/**
|
||||
* Get the bean definition for 'dataSourceConfiguration'
|
||||
*/
|
||||
public static BeanDefinition getDataSourceConfigurationBeanDefinition() {
|
||||
Class<?> beanType = DataSourceConfiguration.class;
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType);
|
||||
beanDefinition.setInstanceSupplier(DataSourceConfiguration::new);
|
||||
return beanDefinition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bean instance supplier for 'dataSource'.
|
||||
*/
|
||||
private static BeanInstanceSupplier<SimpleDataSource> getDataSourceInstanceSupplier() {
|
||||
return BeanInstanceSupplier.<SimpleDataSource>forFactoryMethod(DataSourceConfiguration.class, "dataSource")
|
||||
.withGenerator((registeredBean) -> registeredBean.getBeanFactory().getBean(DataSourceConfiguration.class).dataSource());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bean definition for 'dataSource'
|
||||
*/
|
||||
public static BeanDefinition getDataSourceBeanDefinition() {
|
||||
Class<?> beanType = SimpleDataSource.class;
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType);
|
||||
beanDefinition.setInstanceSupplier(getDataSourceInstanceSupplier());
|
||||
return beanDefinition;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
NOTE: The exact code generated may differ depending on the exact nature of your bean definitions.
|
||||
|
||||
The generated code above creates bean definitions equivalent to the `@Configuration` class, but in a direct way and without the use of reflection if at all possible.
|
||||
There is a bean definition for `dataSourceConfiguration` and one for `dataSourceBean`.
|
||||
When a `datasource` instance is required, a `BeanInstanceSupplier` is called.
|
||||
This supplier invokes the `dataSource()` method on the `dataSourceConfiguration` bean.
|
||||
|
||||
|
||||
[[core.aot.hints]]
|
||||
== Runtime Hints
|
||||
|
||||
Running an application as a native image requires additional information compared to a regular JVM runtime.
|
||||
For instance, GraalVM needs to know ahead of time if a component uses reflection.
|
||||
Similarly, classpath resources are not shipped in a native image unless specified explicitly.
|
||||
Consequently, if the application needs to load a resource, it must be referenced from the corresponding GraalVM native image configuration file.
|
||||
|
||||
The {api-spring-framework}/aot/hint/RuntimeHints.html[`RuntimeHints`] API collects the need for reflection, resource loading, serialization, and JDK proxies at runtime.
|
||||
The following example makes sure that `config/app.properties` can be loaded from the classpath at runtime within a native image:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
runtimeHints.resources().registerPattern("config/app.properties");
|
||||
----
|
||||
|
||||
A number of contracts are handled automatically during AOT processing.
|
||||
For instance, the return type of a `@Controller` method is inspected, and relevant reflection hints are added if Spring detects that the type should be serialized (typically to JSON).
|
||||
|
||||
For cases that the core container cannot infer, you can register such hints programmatically.
|
||||
A number of convenient annotations are also provided for common use cases.
|
||||
|
||||
|
||||
[[core.aot.hints.import-runtime-hints]]
|
||||
=== `@ImportRuntimeHints`
|
||||
|
||||
`RuntimeHintsRegistrar` implementations allow you to get a callback to the `RuntimeHints` instance managed by the AOT engine.
|
||||
Implementations of this interface can be registered using `@ImportRuntimeHints` on any Spring bean or `@Bean` factory method.
|
||||
`RuntimeHintsRegistrar` implementations are detected and invoked at build time.
|
||||
|
||||
include::code:SpellCheckService[]
|
||||
|
||||
If at all possible, `@ImportRuntimeHints` should be used as close as possible to the component that requires the hints.
|
||||
This way, if the component is not contributed to the `BeanFactory`, the hints won't be contributed either.
|
||||
|
||||
It is also possible to register an implementation statically by adding an entry in `META-INF/spring/aot.factories` with a key equal to the fully qualified name of the `RuntimeHintsRegistrar` interface.
|
||||
|
||||
|
||||
[[core.aot.hints.reflective]]
|
||||
=== `@Reflective`
|
||||
|
||||
{api-spring-framework}/aot/hint/annotation/Reflective.html[`@Reflective`] provides an idiomatic way to flag the need for reflection on an annotated element.
|
||||
For instance, `@EventListener` is meta-annotated with `@Reflective` since the underlying implementation invokes the annotated method using reflection.
|
||||
|
||||
By default, only Spring beans are considered and an invocation hint is registered for the annotated element.
|
||||
This can be tuned by specifying a custom `ReflectiveProcessor` implementation via the
|
||||
`@Reflective` annotation.
|
||||
|
||||
Library authors can reuse this annotation for their own purposes.
|
||||
If components other than Spring beans need to be processed, a `BeanFactoryInitializationAotProcessor` can detect the relevant types and use `ReflectiveRuntimeHintsRegistrar` to process them.
|
||||
|
||||
|
||||
[[core.aot.hints.register-reflection-for-binding]]
|
||||
=== `@RegisterReflectionForBinding`
|
||||
|
||||
{api-spring-framework}/aot/hint/annotation/RegisterReflectionForBinding.html[`@RegisterReflectionForBinding`] is a specialization of `@Reflective` that registers the need for serializing arbitrary types.
|
||||
A typical use case is the use of DTOs that the container cannot infer, such as using a web client within a method body.
|
||||
|
||||
`@RegisterReflectionForBinding` can be applied to any Spring bean at the class level, but it can also be applied directly to a method, field, or constructor to better indicate where the hints are actually required.
|
||||
The following example registers `Account` for serialization.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Component
|
||||
public class OrderService {
|
||||
|
||||
@RegisterReflectionForBinding(Account.class)
|
||||
public void process(Order order) {
|
||||
// ...
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
[[core.aot.hints.testing]]
|
||||
=== Testing Runtime Hints
|
||||
|
||||
Spring Core also ships `RuntimeHintsPredicates`, a utility for checking that existing hints match a particular use case.
|
||||
This can be used in your own tests to validate that a `RuntimeHintsRegistrar` contains the expected results.
|
||||
We can write a test for our `SpellCheckService` and ensure that we will be able to load a dictionary at runtime:
|
||||
|
||||
include::code:SpellCheckServiceTests[tag=hintspredicates]
|
||||
|
||||
With `RuntimeHintsPredicates`, we can check for reflection, resource, serialization, or proxy generation hints.
|
||||
This approach works well for unit tests but implies that the runtime behavior of a component is well known.
|
||||
|
||||
You can learn more about the global runtime behavior of an application by running its test suite (or the app itself) with the {docs-graalvm}/native-image/metadata/AutomaticMetadataCollection/[GraalVM tracing agent].
|
||||
This agent will record all relevant calls requiring GraalVM hints at runtime and write them out as JSON configuration files.
|
||||
|
||||
For more targeted discovery and testing, Spring Framework ships a dedicated module with core AOT testing utilities, `"org.springframework:spring-core-test"`.
|
||||
This module contains the RuntimeHints Agent, a Java agent that records all method invocations that are related to runtime hints and helps you to assert that a given `RuntimeHints` instance covers all recorded invocations.
|
||||
Let's consider a piece of infrastructure for which we'd like to test the hints we're contributing during the AOT processing phase.
|
||||
|
||||
include::code:SampleReflection[]
|
||||
|
||||
We can then write a unit test (no native compilation required) that checks our contributed hints:
|
||||
|
||||
include::code:SampleReflectionRuntimeHintsTests[]
|
||||
|
||||
If you forgot to contribute a hint, the test will fail and provide some details about the invocation:
|
||||
|
||||
[source,txt,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
org.springframework.docs.core.aot.hints.testing.SampleReflection performReflection
|
||||
INFO: Spring version:6.0.0-SNAPSHOT
|
||||
|
||||
Missing <"ReflectionHints"> for invocation <java.lang.Class#forName>
|
||||
with arguments ["org.springframework.core.SpringVersion",
|
||||
false,
|
||||
jdk.internal.loader.ClassLoaders$AppClassLoader@251a69d7].
|
||||
Stacktrace:
|
||||
<"org.springframework.util.ClassUtils#forName, Line 284
|
||||
io.spring.runtimehintstesting.SampleReflection#performReflection, Line 19
|
||||
io.spring.runtimehintstesting.SampleReflectionRuntimeHintsTests#lambda$shouldRegisterReflectionHints$0, Line 25
|
||||
----
|
||||
|
||||
There are various ways to configure this Java agent in your build, so please refer to the documentation of your build tool and test execution plugin.
|
||||
The agent itself can be configured to instrument specific packages (by default, only `org.springframework` is instrumented).
|
||||
You'll find more details in the {spring-framework-main-code}/buildSrc/README.md[Spring Framework `buildSrc` README] file.
|
||||
@@ -1,23 +0,0 @@
|
||||
[[spring-integration]]
|
||||
= Integration
|
||||
include::attributes.adoc[]
|
||||
include::page-layout.adoc[]
|
||||
|
||||
This part of the reference documentation covers Spring Framework's integration with
|
||||
a number of technologies.
|
||||
|
||||
include::integration/rest-clients.adoc[leveloffset=+1]
|
||||
|
||||
include::integration/jms.adoc[leveloffset=+1]
|
||||
|
||||
include::integration/jmx.adoc[leveloffset=+1]
|
||||
|
||||
include::integration/email.adoc[leveloffset=+1]
|
||||
|
||||
include::integration/scheduling.adoc[leveloffset=+1]
|
||||
|
||||
include::integration/cache.adoc[leveloffset=+1]
|
||||
|
||||
include::integration/observability.adoc[leveloffset=+1]
|
||||
|
||||
include::integration/integration-appendix.adoc[leveloffset=+1]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,302 +0,0 @@
|
||||
[[mail]]
|
||||
= Email
|
||||
|
||||
This section describes how to send email with the Spring Framework.
|
||||
|
||||
.Library dependencies
|
||||
****
|
||||
The following JAR needs to be on the classpath of your application in order to use
|
||||
the Spring Framework's email library:
|
||||
|
||||
* The https://eclipse-ee4j.github.io/mail/[JavaMail / Jakarta Mail 1.6] library
|
||||
|
||||
This library is freely available on the web -- for example, in Maven Central as
|
||||
`com.sun.mail:jakarta.mail`. Please make sure to use the latest 1.6.x version
|
||||
rather than Jakarta Mail 2.0 (which comes with a different package namespace).
|
||||
****
|
||||
|
||||
The Spring Framework provides a helpful utility library for sending email that shields
|
||||
you from the specifics of the underlying mailing system and is responsible for
|
||||
low-level resource handling on behalf of the client.
|
||||
|
||||
The `org.springframework.mail` package is the root level package for the Spring
|
||||
Framework's email support. The central interface for sending emails is the `MailSender`
|
||||
interface. A simple value object that encapsulates the properties of a simple mail such
|
||||
as `from` and `to` (plus many others) is the `SimpleMailMessage` class. This package
|
||||
also contains a hierarchy of checked exceptions that provide a higher level of
|
||||
abstraction over the lower level mail system exceptions, with the root exception being
|
||||
`MailException`. See the {api-spring-framework}/mail/MailException.html[javadoc]
|
||||
for more information on the rich mail exception hierarchy.
|
||||
|
||||
The `org.springframework.mail.javamail.JavaMailSender` interface adds specialized
|
||||
JavaMail features, such as MIME message support to the `MailSender` interface
|
||||
(from which it inherits). `JavaMailSender` also provides a callback interface called
|
||||
`org.springframework.mail.javamail.MimeMessagePreparator` for preparing a `MimeMessage`.
|
||||
|
||||
|
||||
|
||||
[[mail-usage]]
|
||||
== Usage
|
||||
|
||||
Assume that we have a business interface called `OrderManager`, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface OrderManager {
|
||||
|
||||
void placeOrder(Order order);
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Further assume that we have a requirement stating that an email message with an
|
||||
order number needs to be generated and sent to a customer who placed the relevant order.
|
||||
|
||||
|
||||
[[mail-usage-simple]]
|
||||
=== Basic `MailSender` and `SimpleMailMessage` Usage
|
||||
|
||||
The following example shows how to use `MailSender` and `SimpleMailMessage` to send an
|
||||
email when someone places an order:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import org.springframework.mail.MailException;
|
||||
import org.springframework.mail.MailSender;
|
||||
import org.springframework.mail.SimpleMailMessage;
|
||||
|
||||
public class SimpleOrderManager implements OrderManager {
|
||||
|
||||
private MailSender mailSender;
|
||||
private SimpleMailMessage templateMessage;
|
||||
|
||||
public void setMailSender(MailSender mailSender) {
|
||||
this.mailSender = mailSender;
|
||||
}
|
||||
|
||||
public void setTemplateMessage(SimpleMailMessage templateMessage) {
|
||||
this.templateMessage = templateMessage;
|
||||
}
|
||||
|
||||
public void placeOrder(Order order) {
|
||||
|
||||
// Do the business calculations...
|
||||
|
||||
// Call the collaborators to persist the order...
|
||||
|
||||
// Create a thread safe "copy" of the template message and customize it
|
||||
SimpleMailMessage msg = new SimpleMailMessage(this.templateMessage);
|
||||
msg.setTo(order.getCustomer().getEmailAddress());
|
||||
msg.setText(
|
||||
"Dear " + order.getCustomer().getFirstName()
|
||||
+ order.getCustomer().getLastName()
|
||||
+ ", thank you for placing order. Your order number is "
|
||||
+ order.getOrderNumber());
|
||||
try {
|
||||
this.mailSender.send(msg);
|
||||
}
|
||||
catch (MailException ex) {
|
||||
// simply log it and go on...
|
||||
System.err.println(ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
The following example shows the bean definitions for the preceding code:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
|
||||
<property name="host" value="mail.mycompany.example"/>
|
||||
</bean>
|
||||
|
||||
<!-- this is a template message that we can pre-load with default state -->
|
||||
<bean id="templateMessage" class="org.springframework.mail.SimpleMailMessage">
|
||||
<property name="from" value="customerservice@mycompany.example"/>
|
||||
<property name="subject" value="Your order"/>
|
||||
</bean>
|
||||
|
||||
<bean id="orderManager" class="com.mycompany.businessapp.support.SimpleOrderManager">
|
||||
<property name="mailSender" ref="mailSender"/>
|
||||
<property name="templateMessage" ref="templateMessage"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
|
||||
[[mail-usage-mime]]
|
||||
=== Using `JavaMailSender` and `MimeMessagePreparator`
|
||||
|
||||
This section describes another implementation of `OrderManager` that uses the `MimeMessagePreparator`
|
||||
callback interface. In the following example, the `mailSender` property is of type
|
||||
`JavaMailSender` so that we are able to use the JavaMail `MimeMessage` class:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import jakarta.mail.Message;
|
||||
import jakarta.mail.MessagingException;
|
||||
import jakarta.mail.internet.InternetAddress;
|
||||
import jakarta.mail.internet.MimeMessage;
|
||||
|
||||
import jakarta.mail.internet.MimeMessage;
|
||||
import org.springframework.mail.MailException;
|
||||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
import org.springframework.mail.javamail.MimeMessagePreparator;
|
||||
|
||||
public class SimpleOrderManager implements OrderManager {
|
||||
|
||||
private JavaMailSender mailSender;
|
||||
|
||||
public void setMailSender(JavaMailSender mailSender) {
|
||||
this.mailSender = mailSender;
|
||||
}
|
||||
|
||||
public void placeOrder(final Order order) {
|
||||
// Do the business calculations...
|
||||
// Call the collaborators to persist the order...
|
||||
|
||||
MimeMessagePreparator preparator = new MimeMessagePreparator() {
|
||||
public void prepare(MimeMessage mimeMessage) throws Exception {
|
||||
mimeMessage.setRecipient(Message.RecipientType.TO,
|
||||
new InternetAddress(order.getCustomer().getEmailAddress()));
|
||||
mimeMessage.setFrom(new InternetAddress("mail@mycompany.example"));
|
||||
mimeMessage.setText("Dear " + order.getCustomer().getFirstName() + " " +
|
||||
order.getCustomer().getLastName() + ", thanks for your order. " +
|
||||
"Your order number is " + order.getOrderNumber() + ".");
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
this.mailSender.send(preparator);
|
||||
}
|
||||
catch (MailException ex) {
|
||||
// simply log it and go on...
|
||||
System.err.println(ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
NOTE: The mail code is a crosscutting concern and could well be a candidate for
|
||||
refactoring into a <<core.adoc#aop, custom Spring AOP aspect>>, which could then
|
||||
be run at appropriate joinpoints on the `OrderManager` target.
|
||||
|
||||
The Spring Framework's mail support ships with the standard JavaMail implementation.
|
||||
See the relevant javadoc for more information.
|
||||
|
||||
|
||||
|
||||
[[mail-javamail-mime]]
|
||||
== Using the JavaMail `MimeMessageHelper`
|
||||
|
||||
A class that comes in pretty handy when dealing with JavaMail messages is
|
||||
`org.springframework.mail.javamail.MimeMessageHelper`, which shields you from
|
||||
having to use the verbose JavaMail API. Using the `MimeMessageHelper`, it is
|
||||
pretty easy to create a `MimeMessage`, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
// of course you would use DI in any real-world cases
|
||||
JavaMailSenderImpl sender = new JavaMailSenderImpl();
|
||||
sender.setHost("mail.host.com");
|
||||
|
||||
MimeMessage message = sender.createMimeMessage();
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message);
|
||||
helper.setTo("test@host.com");
|
||||
helper.setText("Thank you for ordering!");
|
||||
|
||||
sender.send(message);
|
||||
----
|
||||
|
||||
|
||||
[[mail-javamail-mime-attachments]]
|
||||
=== Sending Attachments and Inline Resources
|
||||
|
||||
Multipart email messages allow for both attachments and inline resources. Examples of
|
||||
inline resources include an image or a stylesheet that you want to use in your message but
|
||||
that you do not want displayed as an attachment.
|
||||
|
||||
[[mail-javamail-mime-attachments-attachment]]
|
||||
==== Attachments
|
||||
|
||||
The following example shows you how to use the `MimeMessageHelper` to send an email
|
||||
with a single JPEG image attachment:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
JavaMailSenderImpl sender = new JavaMailSenderImpl();
|
||||
sender.setHost("mail.host.com");
|
||||
|
||||
MimeMessage message = sender.createMimeMessage();
|
||||
|
||||
// use the true flag to indicate you need a multipart message
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true);
|
||||
helper.setTo("test@host.com");
|
||||
|
||||
helper.setText("Check out this image!");
|
||||
|
||||
// let's attach the infamous windows Sample file (this time copied to c:/)
|
||||
FileSystemResource file = new FileSystemResource(new File("c:/Sample.jpg"));
|
||||
helper.addAttachment("CoolImage.jpg", file);
|
||||
|
||||
sender.send(message);
|
||||
----
|
||||
|
||||
[[mail-javamail-mime-attachments-inline]]
|
||||
==== Inline Resources
|
||||
|
||||
The following example shows you how to use the `MimeMessageHelper` to send an email
|
||||
with an inline image:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
JavaMailSenderImpl sender = new JavaMailSenderImpl();
|
||||
sender.setHost("mail.host.com");
|
||||
|
||||
MimeMessage message = sender.createMimeMessage();
|
||||
|
||||
// use the true flag to indicate you need a multipart message
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true);
|
||||
helper.setTo("test@host.com");
|
||||
|
||||
// use the true flag to indicate the text included is HTML
|
||||
helper.setText("<html><body><img src='cid:identifier1234'></body></html>", true);
|
||||
|
||||
// let's include the infamous windows Sample file (this time copied to c:/)
|
||||
FileSystemResource res = new FileSystemResource(new File("c:/Sample.jpg"));
|
||||
helper.addInline("identifier1234", res);
|
||||
|
||||
sender.send(message);
|
||||
----
|
||||
|
||||
WARNING: Inline resources are added to the `MimeMessage` by using the specified `Content-ID`
|
||||
(`identifier1234` in the above example). The order in which you add the text
|
||||
and the resource are very important. Be sure to first add the text and then
|
||||
the resources. If you are doing it the other way around, it does not work.
|
||||
|
||||
|
||||
[[mail-templates]]
|
||||
=== Creating Email Content by Using a Templating Library
|
||||
|
||||
The code in the examples shown in the previous sections explicitly created the content of the email message,
|
||||
by using methods calls such as `message.setText(..)`. This is fine for simple cases, and it
|
||||
is okay in the context of the aforementioned examples, where the intent was to show you
|
||||
the very basics of the API.
|
||||
|
||||
In your typical enterprise application, though, developers often do not create the content
|
||||
of email messages by using the previously shown approach for a number of reasons:
|
||||
|
||||
* Creating HTML-based email content in Java code is tedious and error prone.
|
||||
* There is no clear separation between display logic and business logic.
|
||||
* Changing the display structure of the email content requires writing Java code,
|
||||
recompiling, redeploying, and so on.
|
||||
|
||||
Typically, the approach taken to address these issues is to use a template library (such
|
||||
as FreeMarker) to define the display structure of email content. This leaves your code
|
||||
tasked only with creating the data that is to be rendered in the email template and
|
||||
sending the email. It is definitely a best practice when the content of your email messages
|
||||
becomes even moderately complex, and, with the Spring Framework's support classes for
|
||||
FreeMarker, it becomes quite easy to do.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,175 +0,0 @@
|
||||
[[integration.observability]]
|
||||
= Observability Support
|
||||
|
||||
Micrometer defines an https://micrometer.io/docs/observation[Observation concept that enables both Metrics and Traces] in applications.
|
||||
Metrics support offers a way to create timers, gauges or counters for collecting statistics about the runtime behavior of your application.
|
||||
Metrics can help you to track error rates, usage patterns, performance and more.
|
||||
Traces provide a holistic view of an entire system, crossing application boundaries; you can zoom in on particular user requests and follow their entire completion across applications.
|
||||
|
||||
Spring Framework instruments various parts of its own codebase to publish observations if an `ObservationRegistry` is configured.
|
||||
You can learn more about {docs-spring-boot}/html/actuator.html#actuator.metrics[configuring the observability infrastructure in Spring Boot].
|
||||
|
||||
[[integration.observability.concepts]]
|
||||
== Micrometer Observation concepts
|
||||
|
||||
If you are not familiar with Micrometer Observation, here's a quick summary of the new concepts you should know about.
|
||||
|
||||
* `Observation` is the actual recording of something happening in your application. This is processed by `ObservationHandler` implementations to produce metrics or traces.
|
||||
* Each observation has a corresponding `ObservationContext` implementation; this type holds all the relevant information for extracting metadata for it.
|
||||
In the case of an HTTP server observation, the context implementation could hold the HTTP request, the HTTP response, any Exception thrown during processing...
|
||||
* Each `Observation` holds `KeyValues` metadata. In the case of a server HTTP observation, this could be the HTTP request method, the HTTP response status...
|
||||
This metadata is contributed by `ObservationConvention` implementations which should declare the type of `ObservationContext` they support.
|
||||
* `KeyValues` are said to be "low cardinality" if there is a low, bounded number of possible values for the `KeyValue` tuple (HTTP method is a good example).
|
||||
Low cardinality values are contributed to metrics only.
|
||||
High cardinality values are on the other hand unbounded (for example, HTTP request URIs) and are only contributed to Traces.
|
||||
* An `ObservationDocumentation` documents all observations in a particular domain, listing the expected key names and their meaning.
|
||||
|
||||
|
||||
[[integration.observability.config]]
|
||||
== Configuring Observations
|
||||
|
||||
Global configuration options are available at the `ObservationRegistry#observationConfig()` level.
|
||||
Each instrumented component will provide two extension points:
|
||||
|
||||
* setting the `ObservationRegistry`; if not set, observations will not be recorded and will be no-ops
|
||||
* providing a custom `ObservationConvention` to change the default observation name and extracted `KeyValues`
|
||||
|
||||
|
||||
[[integration.observability.config.conventions]]
|
||||
=== Using custom Observation conventions
|
||||
|
||||
Let's take the example of the Spring MVC "http.server.requests" metrics instrumentation with the `ServerHttpObservationFilter`.
|
||||
This observation is using a `ServerRequestObservationConvention` with a `ServerRequestObservationContext`; custom conventions can be configured on the Servlet filter.
|
||||
If you would like to customize the metadata produced with the observation, you can extend the `DefaultServerRequestObservationConvention` for your requirements:
|
||||
|
||||
include::code:ExtendedServerRequestObservationConvention[]
|
||||
|
||||
If you want full control, you can then implement the entire convention contract for the observation you're interested in:
|
||||
|
||||
include::code:CustomServerRequestObservationConvention[]
|
||||
|
||||
You can also achieve similar goals using a custom `ObservationFilter` - adding or removing key values for an observation.
|
||||
Filters do not replace the default convention and are used as a post-processing component.
|
||||
|
||||
include::code:ServerRequestObservationFilter[]
|
||||
|
||||
You can configure `ObservationFilter` instances on the `ObservationRegistry`.
|
||||
|
||||
|
||||
[[integration.observability.http-server]]
|
||||
== HTTP Server instrumentation
|
||||
|
||||
HTTP server exchanges observations are created with the name `"http.server.requests"` for Servlet and Reactive applications.
|
||||
|
||||
[[integration.observability.http-server.servlet]]
|
||||
=== Servlet applications
|
||||
|
||||
Applications need to configure the `org.springframework.web.filter.ServerHttpObservationFilter` Servlet filter in their application.
|
||||
It is using the `org.springframework.http.server.observation.DefaultServerRequestObservationConvention` by default, backed by the `ServerRequestObservationContext`.
|
||||
|
||||
By default, the following `KeyValues` are created:
|
||||
|
||||
.Low cardinality Keys
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`exception` _(required)_|Name of the exception thrown during the exchange, or `KeyValue#NONE_VALUE`} if no exception happened.
|
||||
|`method` _(required)_|Name of HTTP request method or `"none"` if the request was not received properly.
|
||||
|`outcome` _(required)_|Outcome of the HTTP server exchange.
|
||||
|`status` _(required)_|HTTP response raw status code, or `"UNKNOWN"` if no response was created.
|
||||
|`uri` _(required)_|URI pattern for the matching handler if available, falling back to `REDIRECTION` for 3xx responses, `NOT_FOUND` for 404 responses, `root` for requests with no path info, and `UNKNOWN` for all other requests.
|
||||
|===
|
||||
|
||||
.High cardinality Keys
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`http.url` _(required)_|HTTP request URI.
|
||||
|===
|
||||
|
||||
|
||||
[[integration.observability.http-server.reactive]]
|
||||
=== Reactive applications
|
||||
|
||||
Applications need to configure the `org.springframework.web.filter.reactive.ServerHttpObservationFilter` reactive `WebFilter` in their application.
|
||||
It is using the `org.springframework.http.server.reactive.observation.DefaultServerRequestObservationConvention` by default, backed by the `ServerRequestObservationContext`.
|
||||
|
||||
By default, the following `KeyValues` are created:
|
||||
|
||||
.Low cardinality Keys
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`exception` _(required)_|Name of the exception thrown during the exchange, or `"none"` if no exception happened.
|
||||
|`method` _(required)_|Name of HTTP request method or `"none"` if the request was not received properly.
|
||||
|`outcome` _(required)_|Outcome of the HTTP server exchange.
|
||||
|`status` _(required)_|HTTP response raw status code, or `"UNKNOWN"` if no response was created.
|
||||
|`uri` _(required)_|URI pattern for the matching handler if available, falling back to `REDIRECTION` for 3xx responses, `NOT_FOUND` for 404 responses, `root` for requests with no path info, and `UNKNOWN` for all other requests.
|
||||
|===
|
||||
|
||||
.High cardinality Keys
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`http.url` _(required)_|HTTP request URI.
|
||||
|===
|
||||
|
||||
|
||||
|
||||
[[integration.observability.http-client]]
|
||||
== HTTP Client instrumentation
|
||||
|
||||
HTTP client exchanges observations are created with the name `"http.client.requests"` for blocking and reactive clients.
|
||||
Unlike their server counterparts, the instrumentation is implemented directly in the client so the only required step is to configure an `ObservationRegistry` on the client.
|
||||
|
||||
[[integration.observability.http-client.resttemplate]]
|
||||
=== RestTemplate
|
||||
|
||||
Instrumentation is using the `org.springframework.http.client.observation.ClientRequestObservationConvention` by default, backed by the `ClientRequestObservationContext`.
|
||||
|
||||
.Low cardinality Keys
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`exception` _(required)_|Name of the exception thrown during the exchange, or `"none"` if no exception happened.
|
||||
|`method` _(required)_|Name of HTTP request method or `"none"` if the request could not be created.
|
||||
|`outcome` _(required)_|Outcome of the HTTP client exchange.
|
||||
|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received.
|
||||
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided.
|
||||
|===
|
||||
|
||||
.High cardinality Keys
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`client.name` _(required)_|Client name derived from the request URI host.
|
||||
|`http.url` _(required)_|HTTP request URI.
|
||||
|===
|
||||
|
||||
|
||||
|
||||
[[integration.observability.http-client.webclient]]
|
||||
=== WebClient
|
||||
|
||||
Instrumentation is using the `org.springframework.web.reactive.function.client.ClientRequestObservationConvention` by default, backed by the `ClientRequestObservationContext`.
|
||||
|
||||
.Low cardinality Keys
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`exception` _(required)_|Name of the exception thrown during the exchange, or `"none"` if no exception happened.
|
||||
|`method` _(required)_|Name of HTTP request method or `"none"` if the request could not be created.
|
||||
|`outcome` _(required)_|Outcome of the HTTP client exchange.
|
||||
|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received.
|
||||
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided.
|
||||
|===
|
||||
|
||||
.High cardinality Keys
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`client.name` _(required)_|Client name derived from the request URI host.
|
||||
|`http.url` _(required)_|HTTP request URI.
|
||||
|===
|
||||
|
||||
|
||||
@@ -1,517 +0,0 @@
|
||||
[[rest-client-access]]
|
||||
= REST Clients
|
||||
|
||||
The Spring Framework provides the following choices for making calls to REST endpoints:
|
||||
|
||||
* <<rest-webclient>> - non-blocking, reactive client w fluent API.
|
||||
* <<rest-resttemplate>> - synchronous client with template method API.
|
||||
* <<rest-http-interface>> - annotated interface with generated, dynamic proxy implementation.
|
||||
|
||||
|
||||
[[rest-webclient]]
|
||||
== `WebClient`
|
||||
|
||||
`WebClient` is a non-blocking, reactive client to perform HTTP requests. It was
|
||||
introduced in 5.0 and offers an alternative to the `RestTemplate`, with support for
|
||||
synchronous, asynchronous, and streaming scenarios.
|
||||
|
||||
`WebClient` supports the following:
|
||||
|
||||
* Non-blocking I/O.
|
||||
* Reactive Streams back pressure.
|
||||
* High concurrency with fewer hardware resources.
|
||||
* Functional-style, fluent API that takes advantage of Java 8 lambdas.
|
||||
* Synchronous and asynchronous interactions.
|
||||
* Streaming up to or streaming down from a server.
|
||||
|
||||
See <<web-reactive.adoc#webflux-client, WebClient>> for more details.
|
||||
|
||||
|
||||
|
||||
|
||||
[[rest-resttemplate]]
|
||||
== `RestTemplate`
|
||||
|
||||
The `RestTemplate` provides a higher level API over HTTP client libraries. It makes it
|
||||
easy to invoke REST endpoints in a single line. It exposes the following groups of
|
||||
overloaded methods:
|
||||
|
||||
NOTE: `RestTemplate` is in maintenance mode, with only requests for minor
|
||||
changes and bugs to be accepted. Please, consider using the
|
||||
<<web-reactive.adoc#webflux-client, WebClient>> instead.
|
||||
|
||||
[[rest-overview-of-resttemplate-methods-tbl]]
|
||||
.RestTemplate methods
|
||||
[cols="1,3"]
|
||||
|===
|
||||
| Method group | Description
|
||||
|
||||
| `getForObject`
|
||||
| Retrieves a representation via GET.
|
||||
|
||||
| `getForEntity`
|
||||
| Retrieves a `ResponseEntity` (that is, status, headers, and body) by using GET.
|
||||
|
||||
| `headForHeaders`
|
||||
| Retrieves all headers for a resource by using HEAD.
|
||||
|
||||
| `postForLocation`
|
||||
| Creates a new resource by using POST and returns the `Location` header from the response.
|
||||
|
||||
| `postForObject`
|
||||
| Creates a new resource by using POST and returns the representation from the response.
|
||||
|
||||
| `postForEntity`
|
||||
| Creates a new resource by using POST and returns the representation from the response.
|
||||
|
||||
| `put`
|
||||
| Creates or updates a resource by using PUT.
|
||||
|
||||
| `patchForObject`
|
||||
| Updates a resource by using PATCH and returns the representation from the response.
|
||||
Note that the JDK `HttpURLConnection` does not support `PATCH`, but Apache
|
||||
HttpComponents and others do.
|
||||
|
||||
| `delete`
|
||||
| Deletes the resources at the specified URI by using DELETE.
|
||||
|
||||
| `optionsForAllow`
|
||||
| Retrieves allowed HTTP methods for a resource by using ALLOW.
|
||||
|
||||
| `exchange`
|
||||
| More generalized (and less opinionated) version of the preceding methods that provides extra
|
||||
flexibility when needed. It accepts a `RequestEntity` (including HTTP method, URL, headers,
|
||||
and body as input) and returns a `ResponseEntity`.
|
||||
|
||||
These methods allow the use of `ParameterizedTypeReference` instead of `Class` to specify
|
||||
a response type with generics.
|
||||
|
||||
| `execute`
|
||||
| The most generalized way to perform a request, with full control over request
|
||||
preparation and response extraction through callback interfaces.
|
||||
|
||||
|===
|
||||
|
||||
[[rest-resttemplate-create]]
|
||||
=== Initialization
|
||||
|
||||
The default constructor uses `java.net.HttpURLConnection` to perform requests. You can
|
||||
switch to a different HTTP library with an implementation of `ClientHttpRequestFactory`.
|
||||
There is built-in support for the following:
|
||||
|
||||
* Apache HttpComponents
|
||||
* Netty
|
||||
* OkHttp
|
||||
|
||||
For example, to switch to Apache HttpComponents, you can use the following:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
|
||||
----
|
||||
|
||||
Each `ClientHttpRequestFactory` exposes configuration options specific to the underlying
|
||||
HTTP client library -- for example, for credentials, connection pooling, and other details.
|
||||
|
||||
TIP: Note that the `java.net` implementation for HTTP requests can raise an exception when
|
||||
accessing the status of a response that represents an error (such as 401). If this is an
|
||||
issue, switch to another HTTP client library.
|
||||
|
||||
[[rest-resttemplate-uri]]
|
||||
==== URIs
|
||||
|
||||
Many of the `RestTemplate` methods accept a URI template and URI template variables,
|
||||
either as a `String` variable argument, or as `Map<String,String>`.
|
||||
|
||||
The following example uses a `String` variable argument:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
String result = restTemplate.getForObject(
|
||||
"https://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42", "21");
|
||||
----
|
||||
|
||||
The following example uses a `Map<String, String>`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
Map<String, String> vars = Collections.singletonMap("hotel", "42");
|
||||
|
||||
String result = restTemplate.getForObject(
|
||||
"https://example.com/hotels/{hotel}/rooms/{hotel}", String.class, vars);
|
||||
----
|
||||
|
||||
Keep in mind URI templates are automatically encoded, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
restTemplate.getForObject("https://example.com/hotel list", String.class);
|
||||
|
||||
// Results in request to "https://example.com/hotel%20list"
|
||||
----
|
||||
|
||||
You can use the `uriTemplateHandler` property of `RestTemplate` to customize how URIs
|
||||
are encoded. Alternatively, you can prepare a `java.net.URI` and pass it into one of
|
||||
the `RestTemplate` methods that accepts a `URI`.
|
||||
|
||||
For more details on working with and encoding URIs, see <<web.adoc#mvc-uri-building, URI Links>>.
|
||||
|
||||
[[rest-template-headers]]
|
||||
==== Headers
|
||||
|
||||
You can use the `exchange()` methods to specify request headers, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
String uriTemplate = "https://example.com/hotels/{hotel}";
|
||||
URI uri = UriComponentsBuilder.fromUriString(uriTemplate).build(42);
|
||||
|
||||
RequestEntity<Void> requestEntity = RequestEntity.get(uri)
|
||||
.header("MyRequestHeader", "MyValue")
|
||||
.build();
|
||||
|
||||
ResponseEntity<String> response = template.exchange(requestEntity, String.class);
|
||||
|
||||
String responseHeader = response.getHeaders().getFirst("MyResponseHeader");
|
||||
String body = response.getBody();
|
||||
----
|
||||
|
||||
You can obtain response headers through many `RestTemplate` method variants that return
|
||||
`ResponseEntity`.
|
||||
|
||||
[[rest-template-body]]
|
||||
=== Body
|
||||
|
||||
Objects passed into and returned from `RestTemplate` methods are converted to and from raw
|
||||
content with the help of an `HttpMessageConverter`.
|
||||
|
||||
On a POST, an input object is serialized to the request body, as the following example shows:
|
||||
|
||||
----
|
||||
URI location = template.postForLocation("https://example.com/people", person);
|
||||
----
|
||||
|
||||
You need not explicitly set the Content-Type header of the request. In most cases,
|
||||
you can find a compatible message converter based on the source `Object` type, and the chosen
|
||||
message converter sets the content type accordingly. If necessary, you can use the
|
||||
`exchange` methods to explicitly provide the `Content-Type` request header, and that, in
|
||||
turn, influences what message converter is selected.
|
||||
|
||||
On a GET, the body of the response is deserialized to an output `Object`, as the following example shows:
|
||||
|
||||
----
|
||||
Person person = restTemplate.getForObject("https://example.com/people/{id}", Person.class, 42);
|
||||
----
|
||||
|
||||
The `Accept` header of the request does not need to be explicitly set. In most cases,
|
||||
a compatible message converter can be found based on the expected response type, which
|
||||
then helps to populate the `Accept` header. If necessary, you can use the `exchange`
|
||||
methods to provide the `Accept` header explicitly.
|
||||
|
||||
By default, `RestTemplate` registers all built-in
|
||||
<<rest-message-conversion, message converters>>, depending on classpath checks that help
|
||||
to determine what optional conversion libraries are present. You can also set the message
|
||||
converters to use explicitly.
|
||||
|
||||
[[rest-message-conversion]]
|
||||
==== Message Conversion
|
||||
[.small]#<<web-reactive.adoc#webflux-codecs, WebFlux>>#
|
||||
|
||||
The `spring-web` module contains the `HttpMessageConverter` contract for reading and
|
||||
writing the body of HTTP requests and responses through `InputStream` and `OutputStream`.
|
||||
`HttpMessageConverter` instances are used on the client side (for example, in the `RestTemplate`) and
|
||||
on the server side (for example, in Spring MVC REST controllers).
|
||||
|
||||
Concrete implementations for the main media (MIME) types are provided in the framework
|
||||
and are, by default, registered with the `RestTemplate` on the client side and with
|
||||
`RequestMappingHandlerAdapter` on the server side (see
|
||||
<<web.adoc#mvc-config-message-converters, Configuring Message Converters>>).
|
||||
|
||||
The implementations of `HttpMessageConverter` are described in the following sections.
|
||||
For all converters, a default media type is used, but you can override it by setting the
|
||||
`supportedMediaTypes` bean property. The following table describes each implementation:
|
||||
|
||||
[[rest-message-converters-tbl]]
|
||||
.HttpMessageConverter Implementations
|
||||
[cols="1,3"]
|
||||
|===
|
||||
| MessageConverter | Description
|
||||
|
||||
| `StringHttpMessageConverter`
|
||||
| An `HttpMessageConverter` implementation that can read and write `String` instances from the HTTP
|
||||
request and response. By default, this converter supports all text media types
|
||||
(`text/{asterisk}`) and writes with a `Content-Type` of `text/plain`.
|
||||
|
||||
| `FormHttpMessageConverter`
|
||||
| An `HttpMessageConverter` implementation that can read and write form data from the HTTP
|
||||
request and response. By default, this converter reads and writes the
|
||||
`application/x-www-form-urlencoded` media type. Form data is read from and written into a
|
||||
`MultiValueMap<String, String>`. The converter can also write (but not read) multipart
|
||||
data read from a `MultiValueMap<String, Object>`. By default, `multipart/form-data` is
|
||||
supported. As of Spring Framework 5.2, additional multipart subtypes can be supported for
|
||||
writing form data. Consult the javadoc for `FormHttpMessageConverter` for further details.
|
||||
|
||||
| `ByteArrayHttpMessageConverter`
|
||||
| An `HttpMessageConverter` implementation that can read and write byte arrays from the
|
||||
HTTP request and response. By default, this converter supports all media types (`{asterisk}/{asterisk}`)
|
||||
and writes with a `Content-Type` of `application/octet-stream`. You can override this
|
||||
by setting the `supportedMediaTypes` property and overriding `getContentType(byte[])`.
|
||||
|
||||
| `MarshallingHttpMessageConverter`
|
||||
| An `HttpMessageConverter` implementation that can read and write XML by using Spring's
|
||||
`Marshaller` and `Unmarshaller` abstractions from the `org.springframework.oxm` package.
|
||||
This converter requires a `Marshaller` and `Unmarshaller` before it can be used. You can inject these
|
||||
through constructor or bean properties. By default, this converter supports
|
||||
`text/xml` and `application/xml`.
|
||||
|
||||
| `MappingJackson2HttpMessageConverter`
|
||||
| An `HttpMessageConverter` implementation that can read and write JSON by using Jackson's
|
||||
`ObjectMapper`. You can customize JSON mapping as needed through the use of Jackson's
|
||||
provided annotations. When you need further control (for cases where custom JSON
|
||||
serializers/deserializers need to be provided for specific types), you can inject a custom `ObjectMapper`
|
||||
through the `ObjectMapper` property. By default, this
|
||||
converter supports `application/json`.
|
||||
|
||||
| `MappingJackson2XmlHttpMessageConverter`
|
||||
| An `HttpMessageConverter` implementation that can read and write XML by using
|
||||
https://github.com/FasterXML/jackson-dataformat-xml[Jackson XML] extension's
|
||||
`XmlMapper`. You can customize XML mapping as needed through the use of JAXB
|
||||
or Jackson's provided annotations. When you need further control (for cases where custom XML
|
||||
serializers/deserializers need to be provided for specific types), you can inject a custom `XmlMapper`
|
||||
through the `ObjectMapper` property. By default, this
|
||||
converter supports `application/xml`.
|
||||
|
||||
| `SourceHttpMessageConverter`
|
||||
| An `HttpMessageConverter` implementation that can read and write
|
||||
`javax.xml.transform.Source` from the HTTP request and response. Only `DOMSource`,
|
||||
`SAXSource`, and `StreamSource` are supported. By default, this converter supports
|
||||
`text/xml` and `application/xml`.
|
||||
|
||||
| `BufferedImageHttpMessageConverter`
|
||||
| An `HttpMessageConverter` implementation that can read and write
|
||||
`java.awt.image.BufferedImage` from the HTTP request and response. This converter reads
|
||||
and writes the media type supported by the Java I/O API.
|
||||
|
||||
|===
|
||||
|
||||
[[rest-template-jsonview]]
|
||||
=== Jackson JSON Views
|
||||
|
||||
You can specify a https://www.baeldung.com/jackson-json-view-annotation[Jackson JSON View]
|
||||
to serialize only a subset of the object properties, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
MappingJacksonValue value = new MappingJacksonValue(new User("eric", "7!jd#h23"));
|
||||
value.setSerializationView(User.WithoutPasswordView.class);
|
||||
|
||||
RequestEntity<MappingJacksonValue> requestEntity =
|
||||
RequestEntity.post(new URI("https://example.com/user")).body(value);
|
||||
|
||||
ResponseEntity<String> response = template.exchange(requestEntity, String.class);
|
||||
----
|
||||
|
||||
[[rest-template-multipart]]
|
||||
=== Multipart
|
||||
|
||||
To send multipart data, you need to provide a `MultiValueMap<String, Object>` whose values
|
||||
may be an `Object` for part content, a `Resource` for a file part, or an `HttpEntity` for
|
||||
part content with headers. For example:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
|
||||
|
||||
parts.add("fieldPart", "fieldValue");
|
||||
parts.add("filePart", new FileSystemResource("...logo.png"));
|
||||
parts.add("jsonPart", new Person("Jason"));
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_XML);
|
||||
parts.add("xmlPart", new HttpEntity<>(myBean, headers));
|
||||
----
|
||||
|
||||
In most cases, you do not have to specify the `Content-Type` for each part. The content
|
||||
type is determined automatically based on the `HttpMessageConverter` chosen to serialize
|
||||
it or, in the case of a `Resource` based on the file extension. If necessary, you can
|
||||
explicitly provide the `MediaType` with an `HttpEntity` wrapper.
|
||||
|
||||
Once the `MultiValueMap` is ready, you can pass it to the `RestTemplate`, as show below:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
MultiValueMap<String, Object> parts = ...;
|
||||
template.postForObject("https://example.com/upload", parts, Void.class);
|
||||
----
|
||||
|
||||
If the `MultiValueMap` contains at least one non-`String` value, the `Content-Type` is set
|
||||
to `multipart/form-data` by the `FormHttpMessageConverter`. If the `MultiValueMap` has
|
||||
`String` values the `Content-Type` is defaulted to `application/x-www-form-urlencoded`.
|
||||
If necessary the `Content-Type` may also be set explicitly.
|
||||
|
||||
|
||||
[[rest-http-interface]]
|
||||
== HTTP Interface
|
||||
|
||||
The Spring Framework lets you define an HTTP service as a Java interface with annotated
|
||||
methods for HTTP exchanges. You can then generate a proxy that implements this interface
|
||||
and performs the exchanges. This helps to simplify HTTP remote access which often
|
||||
involves a facade that wraps the details of using the underlying HTTP client.
|
||||
|
||||
One, declare an interface with `@HttpExchange` methods:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
interface RepositoryService {
|
||||
|
||||
@GetExchange("/repos/{owner}/{repo}")
|
||||
Repository getRepository(@PathVariable String owner, @PathVariable String repo);
|
||||
|
||||
// more HTTP exchange methods...
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Two, create a proxy that will perform the declared HTTP exchanges:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
WebClient client = WebClient.builder().baseUrl("https://api.github.com/").build();
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).build();
|
||||
|
||||
RepositoryService service = factory.createClient(RepositoryService.class);
|
||||
----
|
||||
|
||||
`@HttpExchange` is supported at the type level where it applies to all methods:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@HttpExchange(url = "/repos/{owner}/{repo}", accept = "application/vnd.github.v3+json")
|
||||
interface RepositoryService {
|
||||
|
||||
@GetExchange
|
||||
Repository getRepository(@PathVariable String owner, @PathVariable String repo);
|
||||
|
||||
@PatchExchange(contentType = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
||||
void updateRepository(@PathVariable String owner, @PathVariable String repo,
|
||||
@RequestParam String name, @RequestParam String description, @RequestParam String homepage);
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
[[rest-http-interface-method-parameters]]
|
||||
=== Method Parameters
|
||||
|
||||
Annotated, HTTP exchange methods support flexible method signatures with the following
|
||||
method parameters:
|
||||
|
||||
[cols="1,2", options="header"]
|
||||
|===
|
||||
| Method argument | Description
|
||||
|
||||
| `URI`
|
||||
| Dynamically set the URL for the request, overriding the annotation's `url` attribute.
|
||||
|
||||
| `HttpMethod`
|
||||
| Dynamically set the HTTP method for the request, overriding the annotation's `method` attribute
|
||||
|
||||
| `@RequestHeader`
|
||||
| Add a request header or mutliple headers. The argument may be a `Map<String, ?>` or
|
||||
`MultiValueMap<String, ?>` with multiple headers, a `Collection<?>` of values, or an
|
||||
individual value. Type conversion is supported for non-String values.
|
||||
|
||||
| `@PathVariable`
|
||||
| Add a variable for expand a placeholder in the request URL. The argument may be a
|
||||
`Map<String, ?>` with multiple variables, or an individual value. Type conversion
|
||||
is supported for non-String values.
|
||||
|
||||
| `@RequestBody`
|
||||
| Provide the body of the request either as an Object to be serialized, or a
|
||||
Reactive Streams `Publisher` such as `Mono`, `Flux`, or any other async type supported
|
||||
through the configured `ReactiveAdapterRegistry`.
|
||||
|
||||
| `@RequestParam`
|
||||
| Add a request parameter or mutliple parameters. The argument may be a `Map<String, ?>`
|
||||
or `MultiValueMap<String, ?>` with multiple parameters, a `Collection<?>` of values, or
|
||||
an individual value. Type conversion is supported for non-String values.
|
||||
|
||||
When `"content-type"` is set to `"application/x-www-form-urlencoded"`, request
|
||||
parameters are encoded in the request body. Otherwise, they are added as URL query
|
||||
parameters.
|
||||
|
||||
| `@RequestPart`
|
||||
| Add a request part, which may be a String (form field), `Resource` (file part),
|
||||
Object (entity to be encoded, e.g. as JSON), `HttpEntity` (part content and headers),
|
||||
a Spring `Part`, or Reactive Streams `Publisher` of any of the above.
|
||||
|
||||
| `@CookieValue`
|
||||
| Add a cookie or mutliple cookies. The argument may be a `Map<String, ?>` or
|
||||
`MultiValueMap<String, ?>` with multiple cookies, a `Collection<?>` of values, or an
|
||||
individual value. Type conversion is supported for non-String values.
|
||||
|
||||
|===
|
||||
|
||||
|
||||
[[rest-http-interface-return-values]]
|
||||
=== Return Values
|
||||
|
||||
Annotated, HTTP exchange methods support the following return values:
|
||||
|
||||
[cols="1,2", options="header"]
|
||||
|===
|
||||
| Method return value | Description
|
||||
|
||||
| `void`, `Mono<Void>`
|
||||
| Perform the given request, and release the response content, if any.
|
||||
|
||||
| `HttpHeaders`, `Mono<HttpHeaders>`
|
||||
| Perform the given request, release the response content, if any, and return the
|
||||
response headers.
|
||||
|
||||
| `<T>`, `Mono<T>`
|
||||
| Perform the given request and decode the response content to the declared return type.
|
||||
|
||||
| `<T>`, `Flux<T>`
|
||||
| Perform the given request and decode the response content to a stream of the declared
|
||||
element type.
|
||||
|
||||
| `ResponseEntity<Void>`, `Mono<ResponseEntity<Void>>`
|
||||
| Perform the given request, and release the response content, if any, and return a
|
||||
`ResponseEntity` with the status and headers.
|
||||
|
||||
| `ResponseEntity<T>`, `Mono<ResponseEntity<T>>`
|
||||
| Perform the given request, decode the response content to the declared return type, and
|
||||
return a `ResponseEntity` with the status, headers, and the decoded body.
|
||||
|
||||
| `Mono<ResponseEntity<Flux<T>>`
|
||||
| Perform the given request, decode the response content to a stream of the declared
|
||||
element type, and return a `ResponseEntity` with the status, headers, and the decoded
|
||||
response body stream.
|
||||
|
||||
|===
|
||||
|
||||
TIP: You can also use any other async or reactive types registered in the
|
||||
`ReactiveAdapterRegistry`.
|
||||
|
||||
|
||||
[[rest-http-interface-exceptions]]
|
||||
=== Exception Handling
|
||||
|
||||
By default, `WebClient` raises `WebClientResponseException` for 4xx and 5xx HTTP status
|
||||
codes. To customize this, you can register a response status handler that applies to all
|
||||
responses performed through the client:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
WebClient webClient = WebClient.builder()
|
||||
.defaultStatusHandler(HttpStatusCode::isError, resp -> ...)
|
||||
.build();
|
||||
|
||||
WebClientAdapter clientAdapter = WebClientAdapter.forClient(webClient);
|
||||
HttpServiceProxyFactory factory = HttpServiceProxyFactory
|
||||
.builder(clientAdapter).build();
|
||||
----
|
||||
|
||||
For more details and options, such as suppressing error status codes, see the Javadoc of
|
||||
`defaultStatusHandler` in `WebClient.Builder`.
|
||||
@@ -1,967 +0,0 @@
|
||||
[[scheduling]]
|
||||
= Task Execution and Scheduling
|
||||
|
||||
The Spring Framework provides abstractions for the asynchronous execution and scheduling of
|
||||
tasks with the `TaskExecutor` and `TaskScheduler` interfaces, respectively. Spring also
|
||||
features implementations of those interfaces that support thread pools or delegation to
|
||||
CommonJ within an application server environment. Ultimately, the use of these
|
||||
implementations behind the common interfaces abstracts away the differences between Java
|
||||
SE 5, Java SE 6, and Jakarta EE environments.
|
||||
|
||||
Spring also features integration classes to support scheduling with the `Timer`
|
||||
(part of the JDK since 1.3) and the Quartz Scheduler ( https://www.quartz-scheduler.org/[]).
|
||||
You can set up both of those schedulers by using a `FactoryBean` with optional references to
|
||||
`Timer` or `Trigger` instances, respectively. Furthermore, a convenience class for both
|
||||
the Quartz Scheduler and the `Timer` is available that lets you invoke a method of
|
||||
an existing target object (analogous to the normal `MethodInvokingFactoryBean`
|
||||
operation).
|
||||
|
||||
|
||||
|
||||
[[scheduling-task-executor]]
|
||||
== The Spring `TaskExecutor` Abstraction
|
||||
|
||||
Executors are the JDK name for the concept of thread pools. The "`executor`" naming is
|
||||
due to the fact that there is no guarantee that the underlying implementation is
|
||||
actually a pool. An executor may be single-threaded or even synchronous. Spring's
|
||||
abstraction hides implementation details between the Java SE and Jakarta EE environments.
|
||||
|
||||
Spring's `TaskExecutor` interface is identical to the `java.util.concurrent.Executor`
|
||||
interface. In fact, originally, its primary reason for existence was to abstract away
|
||||
the need for Java 5 when using thread pools. The interface has a single method
|
||||
(`execute(Runnable task)`) that accepts a task for execution based on the semantics
|
||||
and configuration of the thread pool.
|
||||
|
||||
The `TaskExecutor` was originally created to give other Spring components an abstraction
|
||||
for thread pooling where needed. Components such as the `ApplicationEventMulticaster`,
|
||||
JMS's `AbstractMessageListenerContainer`, and Quartz integration all use the
|
||||
`TaskExecutor` abstraction to pool threads. However, if your beans need thread pooling
|
||||
behavior, you can also use this abstraction for your own needs.
|
||||
|
||||
|
||||
[[scheduling-task-executor-types]]
|
||||
=== `TaskExecutor` Types
|
||||
|
||||
Spring includes a number of pre-built implementations of `TaskExecutor`.
|
||||
In all likelihood, you should never need to implement your own.
|
||||
The variants that Spring provides are as follows:
|
||||
|
||||
* `SyncTaskExecutor`:
|
||||
This implementation does not run invocations asynchronously. Instead, each
|
||||
invocation takes place in the calling thread. It is primarily used in situations
|
||||
where multi-threading is not necessary, such as in simple test cases.
|
||||
* `SimpleAsyncTaskExecutor`:
|
||||
This implementation does not reuse any threads. Rather, it starts up a new thread
|
||||
for each invocation. However, it does support a concurrency limit that blocks
|
||||
any invocations that are over the limit until a slot has been freed up. If you
|
||||
are looking for true pooling, see `ThreadPoolTaskExecutor`, later in this list.
|
||||
* `ConcurrentTaskExecutor`:
|
||||
This implementation is an adapter for a `java.util.concurrent.Executor` instance.
|
||||
There is an alternative (`ThreadPoolTaskExecutor`) that exposes the `Executor`
|
||||
configuration parameters as bean properties. There is rarely a need to use
|
||||
`ConcurrentTaskExecutor` directly. However, if the `ThreadPoolTaskExecutor` is not
|
||||
flexible enough for your needs, `ConcurrentTaskExecutor` is an alternative.
|
||||
* `ThreadPoolTaskExecutor`:
|
||||
This implementation is most commonly used. It exposes bean properties for
|
||||
configuring a `java.util.concurrent.ThreadPoolExecutor` and wraps it in a `TaskExecutor`.
|
||||
If you need to adapt to a different kind of `java.util.concurrent.Executor`, we
|
||||
recommend that you use a `ConcurrentTaskExecutor` instead.
|
||||
* `DefaultManagedTaskExecutor`:
|
||||
This implementation uses a JNDI-obtained `ManagedExecutorService` in a JSR-236
|
||||
compatible runtime environment (such as a Jakarta EE application server),
|
||||
replacing a CommonJ WorkManager for that purpose.
|
||||
|
||||
|
||||
[[scheduling-task-executor-usage]]
|
||||
=== Using a `TaskExecutor`
|
||||
|
||||
Spring's `TaskExecutor` implementations are used as simple JavaBeans. In the following example,
|
||||
we define a bean that uses the `ThreadPoolTaskExecutor` to asynchronously print
|
||||
out a set of messages:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
|
||||
public class TaskExecutorExample {
|
||||
|
||||
private class MessagePrinterTask implements Runnable {
|
||||
|
||||
private String message;
|
||||
|
||||
public MessagePrinterTask(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
System.out.println(message);
|
||||
}
|
||||
}
|
||||
|
||||
private TaskExecutor taskExecutor;
|
||||
|
||||
public TaskExecutorExample(TaskExecutor taskExecutor) {
|
||||
this.taskExecutor = taskExecutor;
|
||||
}
|
||||
|
||||
public void printMessages() {
|
||||
for(int i = 0; i < 25; i++) {
|
||||
taskExecutor.execute(new MessagePrinterTask("Message" + i));
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
As you can see, rather than retrieving a thread from the pool and executing it yourself,
|
||||
you add your `Runnable` to the queue. Then the `TaskExecutor` uses its internal rules to
|
||||
decide when the task gets run.
|
||||
|
||||
To configure the rules that the `TaskExecutor` uses, we expose simple bean properties:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
|
||||
<property name="corePoolSize" value="5"/>
|
||||
<property name="maxPoolSize" value="10"/>
|
||||
<property name="queueCapacity" value="25"/>
|
||||
</bean>
|
||||
|
||||
<bean id="taskExecutorExample" class="TaskExecutorExample">
|
||||
<constructor-arg ref="taskExecutor"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[scheduling-task-scheduler]]
|
||||
== The Spring `TaskScheduler` Abstraction
|
||||
|
||||
In addition to the `TaskExecutor` abstraction, Spring 3.0 introduced a `TaskScheduler`
|
||||
with a variety of methods for scheduling tasks to run at some point in the future.
|
||||
The following listing shows the `TaskScheduler` interface definition:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface TaskScheduler {
|
||||
|
||||
ScheduledFuture schedule(Runnable task, Trigger trigger);
|
||||
|
||||
ScheduledFuture schedule(Runnable task, Instant startTime);
|
||||
|
||||
ScheduledFuture scheduleAtFixedRate(Runnable task, Instant startTime, Duration period);
|
||||
|
||||
ScheduledFuture scheduleAtFixedRate(Runnable task, Duration period);
|
||||
|
||||
ScheduledFuture scheduleWithFixedDelay(Runnable task, Instant startTime, Duration delay);
|
||||
|
||||
ScheduledFuture scheduleWithFixedDelay(Runnable task, Duration delay);
|
||||
|
||||
----
|
||||
|
||||
The simplest method is the one named `schedule` that takes only a `Runnable` and an `Instant`.
|
||||
That causes the task to run once after the specified time. All of the other methods
|
||||
are capable of scheduling tasks to run repeatedly. The fixed-rate and fixed-delay
|
||||
methods are for simple, periodic execution, but the method that accepts a `Trigger` is
|
||||
much more flexible.
|
||||
|
||||
|
||||
[[scheduling-trigger-interface]]
|
||||
=== `Trigger` Interface
|
||||
|
||||
The `Trigger` interface is essentially inspired by JSR-236 which, as of Spring 3.0,
|
||||
was not yet officially implemented. The basic idea of the `Trigger` is that execution
|
||||
times may be determined based on past execution outcomes or even arbitrary conditions.
|
||||
If these determinations do take into account the outcome of the preceding execution,
|
||||
that information is available within a `TriggerContext`. The `Trigger` interface itself
|
||||
is quite simple, as the following listing shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface Trigger {
|
||||
|
||||
Date nextExecutionTime(TriggerContext triggerContext);
|
||||
}
|
||||
----
|
||||
|
||||
The `TriggerContext` is the most important part. It encapsulates all of
|
||||
the relevant data and is open for extension in the future, if necessary. The
|
||||
`TriggerContext` is an interface (a `SimpleTriggerContext` implementation is used by
|
||||
default). The following listing shows the available methods for `Trigger` implementations.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface TriggerContext {
|
||||
|
||||
Date lastScheduledExecutionTime();
|
||||
|
||||
Date lastActualExecutionTime();
|
||||
|
||||
Date lastCompletionTime();
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
[[scheduling-trigger-implementations]]
|
||||
=== `Trigger` Implementations
|
||||
|
||||
Spring provides two implementations of the `Trigger` interface. The most interesting one
|
||||
is the `CronTrigger`. It enables the scheduling of tasks based on
|
||||
<<scheduling-cron-expression,cron expressions>>.
|
||||
For example, the following task is scheduled to run 15 minutes past each hour but only
|
||||
during the 9-to-5 "`business hours`" on weekdays:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim"]
|
||||
----
|
||||
scheduler.schedule(task, new CronTrigger("0 15 9-17 * * MON-FRI"));
|
||||
----
|
||||
|
||||
The other implementation is a `PeriodicTrigger` that accepts a fixed
|
||||
period, an optional initial delay value, and a boolean to indicate whether the period
|
||||
should be interpreted as a fixed-rate or a fixed-delay. Since the `TaskScheduler`
|
||||
interface already defines methods for scheduling tasks at a fixed rate or with a
|
||||
fixed delay, those methods should be used directly whenever possible. The value of the
|
||||
`PeriodicTrigger` implementation is that you can use it within components that rely on
|
||||
the `Trigger` abstraction. For example, it may be convenient to allow periodic triggers,
|
||||
cron-based triggers, and even custom trigger implementations to be used interchangeably.
|
||||
Such a component could take advantage of dependency injection so that you can configure such `Triggers`
|
||||
externally and, therefore, easily modify or extend them.
|
||||
|
||||
|
||||
[[scheduling-task-scheduler-implementations]]
|
||||
=== `TaskScheduler` implementations
|
||||
|
||||
As with Spring's `TaskExecutor` abstraction, the primary benefit of the `TaskScheduler`
|
||||
arrangement is that an application's scheduling needs are decoupled from the deployment
|
||||
environment. This abstraction level is particularly relevant when deploying to an
|
||||
application server environment where threads should not be created directly by the
|
||||
application itself. For such scenarios, Spring provides a `TimerManagerTaskScheduler`
|
||||
that delegates to a CommonJ `TimerManager` on WebLogic or WebSphere as well as a more recent
|
||||
`DefaultManagedTaskScheduler` that delegates to a JSR-236 `ManagedScheduledExecutorService`
|
||||
in a Jakarta EE environment. Both are typically configured with a JNDI lookup.
|
||||
|
||||
Whenever external thread management is not a requirement, a simpler alternative is
|
||||
a local `ScheduledExecutorService` setup within the application, which can be adapted
|
||||
through Spring's `ConcurrentTaskScheduler`. As a convenience, Spring also provides a
|
||||
`ThreadPoolTaskScheduler`, which internally delegates to a `ScheduledExecutorService`
|
||||
to provide common bean-style configuration along the lines of `ThreadPoolTaskExecutor`.
|
||||
These variants work perfectly fine for locally embedded thread pool setups in lenient
|
||||
application server environments, as well -- in particular on Tomcat and Jetty.
|
||||
|
||||
|
||||
|
||||
[[scheduling-annotation-support]]
|
||||
== Annotation Support for Scheduling and Asynchronous Execution
|
||||
|
||||
Spring provides annotation support for both task scheduling and asynchronous method
|
||||
execution.
|
||||
|
||||
|
||||
[[scheduling-enable-annotation-support]]
|
||||
=== Enable Scheduling Annotations
|
||||
|
||||
To enable support for `@Scheduled` and `@Async` annotations, you can add `@EnableScheduling` and
|
||||
`@EnableAsync` to one of your `@Configuration` classes, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Configuration
|
||||
@EnableAsync
|
||||
@EnableScheduling
|
||||
public class AppConfig {
|
||||
}
|
||||
----
|
||||
|
||||
You can pick and choose the relevant annotations for your application. For example,
|
||||
if you need only support for `@Scheduled`, you can omit `@EnableAsync`. For more
|
||||
fine-grained control, you can additionally implement the `SchedulingConfigurer`
|
||||
interface, the `AsyncConfigurer` interface, or both. See the
|
||||
{api-spring-framework}/scheduling/annotation/SchedulingConfigurer.html[`SchedulingConfigurer`]
|
||||
and {api-spring-framework}/scheduling/annotation/AsyncConfigurer.html[`AsyncConfigurer`]
|
||||
javadoc for full details.
|
||||
|
||||
If you prefer XML configuration, you can use the `<task:annotation-driven>` element,
|
||||
as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<task:annotation-driven executor="myExecutor" scheduler="myScheduler"/>
|
||||
<task:executor id="myExecutor" pool-size="5"/>
|
||||
<task:scheduler id="myScheduler" pool-size="10"/>
|
||||
----
|
||||
|
||||
Note that, with the preceding XML, an executor reference is provided for handling those
|
||||
tasks that correspond to methods with the `@Async` annotation, and the scheduler
|
||||
reference is provided for managing those methods annotated with `@Scheduled`.
|
||||
|
||||
NOTE: The default advice mode for processing `@Async` annotations is `proxy` which allows
|
||||
for interception of calls through the proxy only. Local calls within the same class
|
||||
cannot get intercepted that way. For a more advanced mode of interception, consider
|
||||
switching to `aspectj` mode in combination with compile-time or load-time weaving.
|
||||
|
||||
|
||||
[[scheduling-annotation-support-scheduled]]
|
||||
=== The `@Scheduled` annotation
|
||||
|
||||
You can add the `@Scheduled` annotation to a method, along with trigger metadata. For
|
||||
example, the following method is invoked every five seconds (5000 milliseconds) with a
|
||||
fixed delay, meaning that the period is measured from the completion time of each
|
||||
preceding invocation.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Scheduled(fixedDelay = 5000)
|
||||
public void doSomething() {
|
||||
// something that should run periodically
|
||||
}
|
||||
----
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
By default, milliseconds will be used as the time unit for fixed delay, fixed rate, and
|
||||
initial delay values. If you would like to use a different time unit such as seconds or
|
||||
minutes, you can configure this via the `timeUnit` attribute in `@Scheduled`.
|
||||
|
||||
For example, the previous example can also be written as follows.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Scheduled(fixedDelay = 5, timeUnit = TimeUnit.SECONDS)
|
||||
public void doSomething() {
|
||||
// something that should run periodically
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
If you need a fixed-rate execution, you can use the `fixedRate` attribute within the
|
||||
annotation. The following method is invoked every five seconds (measured between the
|
||||
successive start times of each invocation).
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Scheduled(fixedRate = 5, timeUnit = TimeUnit.SECONDS)
|
||||
public void doSomething() {
|
||||
// something that should run periodically
|
||||
}
|
||||
----
|
||||
|
||||
For fixed-delay and fixed-rate tasks, you can specify an initial delay by indicating the
|
||||
amount of time to wait before the first execution of the method, as the following
|
||||
`fixedRate` example shows.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Scheduled(initialDelay = 1000, fixedRate = 5000)
|
||||
public void doSomething() {
|
||||
// something that should run periodically
|
||||
}
|
||||
----
|
||||
|
||||
If simple periodic scheduling is not expressive enough, you can provide a
|
||||
<<scheduling-cron-expression,cron expression>>.
|
||||
The following example runs only on weekdays:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim"]
|
||||
----
|
||||
@Scheduled(cron="*/5 * * * * MON-FRI")
|
||||
public void doSomething() {
|
||||
// something that should run on weekdays only
|
||||
}
|
||||
----
|
||||
|
||||
TIP: You can also use the `zone` attribute to specify the time zone in which the cron
|
||||
expression is resolved.
|
||||
|
||||
Notice that the methods to be scheduled must have void returns and must not accept any
|
||||
arguments. If the method needs to interact with other objects from the application
|
||||
context, those would typically have been provided through dependency injection.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
As of Spring Framework 4.3, `@Scheduled` methods are supported on beans of any scope.
|
||||
|
||||
Make sure that you are not initializing multiple instances of the same `@Scheduled`
|
||||
annotation class at runtime, unless you do want to schedule callbacks to each such
|
||||
instance. Related to this, make sure that you do not use `@Configurable` on bean
|
||||
classes that are annotated with `@Scheduled` and registered as regular Spring beans
|
||||
with the container. Otherwise, you would get double initialization (once through the
|
||||
container and once through the `@Configurable` aspect), with the consequence of each
|
||||
`@Scheduled` method being invoked twice.
|
||||
====
|
||||
|
||||
|
||||
[[scheduling-annotation-support-async]]
|
||||
=== The `@Async` annotation
|
||||
|
||||
You can provide the `@Async` annotation on a method so that invocation of that method
|
||||
occurs asynchronously. In other words, the caller returns immediately upon
|
||||
invocation, while the actual execution of the method occurs in a task that has been
|
||||
submitted to a Spring `TaskExecutor`. In the simplest case, you can apply the annotation
|
||||
to a method that returns `void`, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Async
|
||||
void doSomething() {
|
||||
// this will be run asynchronously
|
||||
}
|
||||
----
|
||||
|
||||
Unlike the methods annotated with the `@Scheduled` annotation, these methods can expect
|
||||
arguments, because they are invoked in the "`normal`" way by callers at runtime rather
|
||||
than from a scheduled task being managed by the container. For example, the following code is
|
||||
a legitimate application of the `@Async` annotation:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Async
|
||||
void doSomething(String s) {
|
||||
// this will be run asynchronously
|
||||
}
|
||||
----
|
||||
|
||||
Even methods that return a value can be invoked asynchronously. However, such methods
|
||||
are required to have a `Future`-typed return value. This still provides the benefit of
|
||||
asynchronous execution so that the caller can perform other tasks prior to calling
|
||||
`get()` on that `Future`. The following example shows how to use `@Async` on a method
|
||||
that returns a value:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Async
|
||||
Future<String> returnSomething(int i) {
|
||||
// this will be run asynchronously
|
||||
}
|
||||
----
|
||||
|
||||
TIP: `@Async` methods may not only declare a regular `java.util.concurrent.Future` return type
|
||||
but also Spring's `org.springframework.util.concurrent.ListenableFuture` or, as of Spring
|
||||
4.2, JDK 8's `java.util.concurrent.CompletableFuture`, for richer interaction with the
|
||||
asynchronous task and for immediate composition with further processing steps.
|
||||
|
||||
You can not use `@Async` in conjunction with lifecycle callbacks such as
|
||||
`@PostConstruct`. To asynchronously initialize Spring beans, you currently have to use
|
||||
a separate initializing Spring bean that then invokes the `@Async` annotated method on the
|
||||
target, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public class SampleBeanImpl implements SampleBean {
|
||||
|
||||
@Async
|
||||
void doSomething() {
|
||||
// ...
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class SampleBeanInitializer {
|
||||
|
||||
private final SampleBean bean;
|
||||
|
||||
public SampleBeanInitializer(SampleBean bean) {
|
||||
this.bean = bean;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void initialize() {
|
||||
bean.doSomething();
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
NOTE: There is no direct XML equivalent for `@Async`, since such methods should be designed
|
||||
for asynchronous execution in the first place, not externally re-declared to be asynchronous.
|
||||
However, you can manually set up Spring's `AsyncExecutionInterceptor` with Spring AOP,
|
||||
in combination with a custom pointcut.
|
||||
|
||||
|
||||
[[scheduling-annotation-support-qualification]]
|
||||
=== Executor Qualification with `@Async`
|
||||
|
||||
By default, when specifying `@Async` on a method, the executor that is used is the
|
||||
one <<scheduling-enable-annotation-support, configured when enabling async support>>,
|
||||
i.e. the "`annotation-driven`" element if you are using XML or your `AsyncConfigurer`
|
||||
implementation, if any. However, you can use the `value` attribute of the `@Async`
|
||||
annotation when you need to indicate that an executor other than the default should be
|
||||
used when executing a given method. The following example shows how to do so:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Async("otherExecutor")
|
||||
void doSomething(String s) {
|
||||
// this will be run asynchronously by "otherExecutor"
|
||||
}
|
||||
----
|
||||
|
||||
In this case, `"otherExecutor"` can be the name of any `Executor` bean in the Spring
|
||||
container, or it may be the name of a qualifier associated with any `Executor` (for example, as
|
||||
specified with the `<qualifier>` element or Spring's `@Qualifier` annotation).
|
||||
|
||||
|
||||
[[scheduling-annotation-support-exception]]
|
||||
=== Exception Management with `@Async`
|
||||
|
||||
When an `@Async` method has a `Future`-typed return value, it is easy to manage
|
||||
an exception that was thrown during the method execution, as this exception is
|
||||
thrown when calling `get` on the `Future` result. With a `void` return type,
|
||||
however, the exception is uncaught and cannot be transmitted. You can provide an
|
||||
`AsyncUncaughtExceptionHandler` to handle such exceptions. The following example shows
|
||||
how to do so:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {
|
||||
|
||||
@Override
|
||||
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
|
||||
// handle exception
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
By default, the exception is merely logged. You can define a custom `AsyncUncaughtExceptionHandler`
|
||||
by using `AsyncConfigurer` or the `<task:annotation-driven/>` XML element.
|
||||
|
||||
|
||||
|
||||
[[scheduling-task-namespace]]
|
||||
== The `task` Namespace
|
||||
|
||||
As of version 3.0, Spring includes an XML namespace for configuring `TaskExecutor` and
|
||||
`TaskScheduler` instances. It also provides a convenient way to configure tasks to be
|
||||
scheduled with a trigger.
|
||||
|
||||
|
||||
[[scheduling-task-namespace-scheduler]]
|
||||
=== The 'scheduler' Element
|
||||
|
||||
The following element creates a `ThreadPoolTaskScheduler` instance with the
|
||||
specified thread pool size:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<task:scheduler id="scheduler" pool-size="10"/>
|
||||
----
|
||||
|
||||
The value provided for the `id` attribute is used as the prefix for thread names
|
||||
within the pool. The `scheduler` element is relatively straightforward. If you do not
|
||||
provide a `pool-size` attribute, the default thread pool has only a single thread.
|
||||
There are no other configuration options for the scheduler.
|
||||
|
||||
|
||||
[[scheduling-task-namespace-executor]]
|
||||
=== The `executor` Element
|
||||
|
||||
The following creates a `ThreadPoolTaskExecutor` instance:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<task:executor id="executor" pool-size="10"/>
|
||||
----
|
||||
|
||||
As with the scheduler shown in the <<scheduling-task-namespace-scheduler, previous section>>,
|
||||
the value provided for the `id` attribute is used as the prefix for thread names within
|
||||
the pool. As far as the pool size is concerned, the `executor` element supports more
|
||||
configuration options than the `scheduler` element. For one thing, the thread pool for
|
||||
a `ThreadPoolTaskExecutor` is itself more configurable. Rather than only a single size,
|
||||
an executor's thread pool can have different values for the core and the max size.
|
||||
If you provide a single value, the executor has a fixed-size thread pool (the core and
|
||||
max sizes are the same). However, the `executor` element's `pool-size` attribute also
|
||||
accepts a range in the form of `min-max`. The following example sets a minimum value of
|
||||
`5` and a maximum value of `25`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<task:executor
|
||||
id="executorWithPoolSizeRange"
|
||||
pool-size="5-25"
|
||||
queue-capacity="100"/>
|
||||
----
|
||||
|
||||
In the preceding configuration, a `queue-capacity` value has also been provided.
|
||||
The configuration of the thread pool should also be considered in light of the
|
||||
executor's queue capacity. For the full description of the relationship between pool
|
||||
size and queue capacity, see the documentation for
|
||||
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ThreadPoolExecutor.html[`ThreadPoolExecutor`].
|
||||
The main idea is that, when a task is submitted, the executor first tries to use a
|
||||
free thread if the number of active threads is currently less than the core size.
|
||||
If the core size has been reached, the task is added to the queue, as long as its
|
||||
capacity has not yet been reached. Only then, if the queue's capacity has been
|
||||
reached, does the executor create a new thread beyond the core size. If the max size
|
||||
has also been reached, then the executor rejects the task.
|
||||
|
||||
By default, the queue is unbounded, but this is rarely the desired configuration,
|
||||
because it can lead to `OutOfMemoryErrors` if enough tasks are added to that queue while
|
||||
all pool threads are busy. Furthermore, if the queue is unbounded, the max size has
|
||||
no effect at all. Since the executor always tries the queue before creating a new
|
||||
thread beyond the core size, a queue must have a finite capacity for the thread pool to
|
||||
grow beyond the core size (this is why a fixed-size pool is the only sensible case
|
||||
when using an unbounded queue).
|
||||
|
||||
Consider the case, as mentioned above, when a task is rejected. By default, when a
|
||||
task is rejected, a thread pool executor throws a `TaskRejectedException`. However,
|
||||
the rejection policy is actually configurable. The exception is thrown when using
|
||||
the default rejection policy, which is the `AbortPolicy` implementation.
|
||||
For applications where some tasks can be skipped under heavy load, you can instead
|
||||
configure either `DiscardPolicy` or `DiscardOldestPolicy`. Another option that works
|
||||
well for applications that need to throttle the submitted tasks under heavy load is
|
||||
the `CallerRunsPolicy`. Instead of throwing an exception or discarding tasks,
|
||||
that policy forces the thread that is calling the submit method to run the task itself.
|
||||
The idea is that such a caller is busy while running that task and not able to submit
|
||||
other tasks immediately. Therefore, it provides a simple way to throttle the incoming
|
||||
load while maintaining the limits of the thread pool and queue. Typically, this allows
|
||||
the executor to "`catch up`" on the tasks it is handling and thereby frees up some
|
||||
capacity on the queue, in the pool, or both. You can choose any of these options from an
|
||||
enumeration of values available for the `rejection-policy` attribute on the `executor`
|
||||
element.
|
||||
|
||||
The following example shows an `executor` element with a number of attributes to specify
|
||||
various behaviors:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<task:executor
|
||||
id="executorWithCallerRunsPolicy"
|
||||
pool-size="5-25"
|
||||
queue-capacity="100"
|
||||
rejection-policy="CALLER_RUNS"/>
|
||||
----
|
||||
|
||||
Finally, the `keep-alive` setting determines the time limit (in seconds) for which threads
|
||||
may remain idle before being stopped. If there are more than the core number of threads
|
||||
currently in the pool, after waiting this amount of time without processing a task, excess
|
||||
threads get stopped. A time value of zero causes excess threads to stop
|
||||
immediately after executing a task without remaining follow-up work in the task queue.
|
||||
The following example sets the `keep-alive` value to two minutes:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<task:executor
|
||||
id="executorWithKeepAlive"
|
||||
pool-size="5-25"
|
||||
keep-alive="120"/>
|
||||
----
|
||||
|
||||
|
||||
[[scheduling-task-namespace-scheduled-tasks]]
|
||||
=== The 'scheduled-tasks' Element
|
||||
|
||||
The most powerful feature of Spring's task namespace is the support for configuring
|
||||
tasks to be scheduled within a Spring Application Context. This follows an approach
|
||||
similar to other "`method-invokers`" in Spring, such as that provided by the JMS namespace
|
||||
for configuring message-driven POJOs. Basically, a `ref` attribute can point to any
|
||||
Spring-managed object, and the `method` attribute provides the name of a method to be
|
||||
invoked on that object. The following listing shows a simple example:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<task:scheduled-tasks scheduler="myScheduler">
|
||||
<task:scheduled ref="beanA" method="methodA" fixed-delay="5000"/>
|
||||
</task:scheduled-tasks>
|
||||
|
||||
<task:scheduler id="myScheduler" pool-size="10"/>
|
||||
----
|
||||
|
||||
The scheduler is referenced by the outer element, and each individual
|
||||
task includes the configuration of its trigger metadata. In the preceding example, that
|
||||
metadata defines a periodic trigger with a fixed delay indicating the number of
|
||||
milliseconds to wait after each task execution has completed. Another option is
|
||||
`fixed-rate`, indicating how often the method should be run regardless of how long
|
||||
any previous execution takes. Additionally, for both `fixed-delay` and `fixed-rate` tasks, you can specify an
|
||||
'initial-delay' parameter, indicating the number of milliseconds to wait
|
||||
before the first execution of the method. For more control, you can instead provide a `cron` attribute
|
||||
to provide a <<scheduling-cron-expression,cron expression>>.
|
||||
The following example shows these other options:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim"]
|
||||
----
|
||||
<task:scheduled-tasks scheduler="myScheduler">
|
||||
<task:scheduled ref="beanA" method="methodA" fixed-delay="5000" initial-delay="1000"/>
|
||||
<task:scheduled ref="beanB" method="methodB" fixed-rate="5000"/>
|
||||
<task:scheduled ref="beanC" method="methodC" cron="*/5 * * * * MON-FRI"/>
|
||||
</task:scheduled-tasks>
|
||||
|
||||
<task:scheduler id="myScheduler" pool-size="10"/>
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[scheduling-cron-expression]]
|
||||
== Cron Expressions
|
||||
|
||||
All Spring cron expressions have to conform to the same format, whether you are using them in
|
||||
<<scheduling-annotation-support-scheduled,`@Scheduled` annotations>>,
|
||||
<<scheduling-task-namespace-scheduled-tasks,`task:scheduled-tasks` elements>>,
|
||||
or someplace else.
|
||||
A well-formed cron expression, such as `* * * * * *`, consists of six space-separated time and date
|
||||
fields, each with its own range of valid values:
|
||||
|
||||
|
||||
....
|
||||
┌───────────── second (0-59)
|
||||
│ ┌───────────── minute (0 - 59)
|
||||
│ │ ┌───────────── hour (0 - 23)
|
||||
│ │ │ ┌───────────── day of the month (1 - 31)
|
||||
│ │ │ │ ┌───────────── month (1 - 12) (or JAN-DEC)
|
||||
│ │ │ │ │ ┌───────────── day of the week (0 - 7)
|
||||
│ │ │ │ │ │ (0 or 7 is Sunday, or MON-SUN)
|
||||
│ │ │ │ │ │
|
||||
* * * * * *
|
||||
....
|
||||
|
||||
There are some rules that apply:
|
||||
|
||||
* A field may be an asterisk (`*`), which always stands for "`first-last`".
|
||||
For the day-of-the-month or day-of-the-week fields, a question mark (`?`) may be used instead of an
|
||||
asterisk.
|
||||
* Commas (`,`) are used to separate items of a list.
|
||||
* Two numbers separated with a hyphen (`-`) express a range of numbers.
|
||||
The specified range is inclusive.
|
||||
* Following a range (or `*`) with `/` specifies the interval of the number's value through the range.
|
||||
* English names can also be used for the month and day-of-week fields.
|
||||
Use the first three letters of the particular day or month (case does not matter).
|
||||
* The day-of-month and day-of-week fields can contain an `L` character, which has a different meaning.
|
||||
** In the day-of-month field, `L` stands for _the last day of the month_.
|
||||
If followed by a negative offset (that is, `L-n`), it means _``n``th-to-last day of the month_.
|
||||
** In the day-of-week field, `L` stands for _the last day of the week_.
|
||||
If prefixed by a number or three-letter name (`dL` or `DDDL`), it means _the last day of week (`d`
|
||||
or `DDD`) in the month_.
|
||||
* The day-of-month field can be `nW`, which stands for _the nearest weekday to day of the month ``n``_.
|
||||
If `n` falls on Saturday, this yields the Friday before it.
|
||||
If `n` falls on Sunday, this yields the Monday after, which also happens if `n` is `1` and falls on
|
||||
a Saturday (that is: `1W` stands for _the first weekday of the month_).
|
||||
* If the day-of-month field is `LW`, it means _the last weekday of the month_.
|
||||
* The day-of-week field can be `d#n` (or `DDD#n`), which stands for _the ``n``th day of week `d`
|
||||
(or ``DDD``) in the month_.
|
||||
|
||||
Here are some examples:
|
||||
|
||||
|===
|
||||
| Cron Expression | Meaning
|
||||
|
||||
|`0 0 * * * *` | top of every hour of every day
|
||||
|`*/10 * * * * *` | every ten seconds
|
||||
| `0 0 8-10 * * *` | 8, 9 and 10 o'clock of every day
|
||||
| `0 0 6,19 * * *` | 6:00 AM and 7:00 PM every day
|
||||
| `0 0/30 8-10 * * *` | 8:00, 8:30, 9:00, 9:30, 10:00 and 10:30 every day
|
||||
| `0 0 9-17 * * MON-FRI`| on the hour nine-to-five weekdays
|
||||
| `0 0 0 25 DEC ?` | every Christmas Day at midnight
|
||||
| `0 0 0 L * *` | last day of the month at midnight
|
||||
| `0 0 0 L-3 * *` | third-to-last day of the month at midnight
|
||||
| `0 0 0 * * 5L` | last Friday of the month at midnight
|
||||
| `0 0 0 * * THUL` | last Thursday of the month at midnight
|
||||
| `0 0 0 1W * *` | first weekday of the month at midnight
|
||||
| `0 0 0 LW * *` | last weekday of the month at midnight
|
||||
| `0 0 0 ? * 5#2` | the second Friday in the month at midnight
|
||||
| `0 0 0 ? * MON#1` | the first Monday in the month at midnight
|
||||
|===
|
||||
|
||||
=== Macros
|
||||
|
||||
Expressions such as `0 0 * * * *` are hard for humans to parse and are, therefore, hard to fix in case of bugs.
|
||||
To improve readability, Spring supports the following macros, which represent commonly used sequences.
|
||||
You can use these macros instead of the six-digit value, thus: `@Scheduled(cron = "@hourly")`.
|
||||
|
||||
|===
|
||||
|Macro | Meaning
|
||||
|
||||
| `@yearly` (or `@annually`) | once a year (`0 0 0 1 1 *`)
|
||||
| `@monthly` | once a month (`0 0 0 1 * *`)
|
||||
| `@weekly` | once a week (`0 0 0 * * 0`)
|
||||
| `@daily` (or `@midnight`) | once a day (`0 0 0 * * *`), or
|
||||
| `@hourly` | once an hour, (`0 0 * * * *`)
|
||||
|===
|
||||
|
||||
|
||||
|
||||
[[scheduling-quartz]]
|
||||
== Using the Quartz Scheduler
|
||||
|
||||
Quartz uses `Trigger`, `Job`, and `JobDetail` objects to realize scheduling of all kinds
|
||||
of jobs. For the basic concepts behind Quartz, see
|
||||
https://www.quartz-scheduler.org/[]. For convenience purposes, Spring offers a couple of
|
||||
classes that simplify using Quartz within Spring-based applications.
|
||||
|
||||
|
||||
[[scheduling-quartz-jobdetail]]
|
||||
=== Using the `JobDetailFactoryBean`
|
||||
|
||||
Quartz `JobDetail` objects contain all the information needed to run a job. Spring provides a
|
||||
`JobDetailFactoryBean`, which provides bean-style properties for XML configuration purposes.
|
||||
Consider the following example:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean name="exampleJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
|
||||
<property name="jobClass" value="example.ExampleJob"/>
|
||||
<property name="jobDataAsMap">
|
||||
<map>
|
||||
<entry key="timeout" value="5"/>
|
||||
</map>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
The job detail configuration has all the information it needs to run the job (`ExampleJob`).
|
||||
The timeout is specified in the job data map. The job data map is available through the
|
||||
`JobExecutionContext` (passed to you at execution time), but the `JobDetail` also gets
|
||||
its properties from the job data mapped to properties of the job instance. So, in the following example,
|
||||
the `ExampleJob` contains a bean property named `timeout`, and the `JobDetail`
|
||||
has it applied automatically:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim"]
|
||||
----
|
||||
package example;
|
||||
|
||||
public class ExampleJob extends QuartzJobBean {
|
||||
|
||||
private int timeout;
|
||||
|
||||
/**
|
||||
* Setter called after the ExampleJob is instantiated
|
||||
* with the value from the JobDetailFactoryBean (5)
|
||||
*/
|
||||
public void setTimeout(int timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException {
|
||||
// do the actual work
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
All additional properties from the job data map are available to you as well.
|
||||
|
||||
NOTE: By using the `name` and `group` properties, you can modify the name and the group
|
||||
of the job, respectively. By default, the name of the job matches the bean name
|
||||
of the `JobDetailFactoryBean` (`exampleJob` in the preceding example above).
|
||||
|
||||
|
||||
[[scheduling-quartz-method-invoking-job]]
|
||||
=== Using the `MethodInvokingJobDetailFactoryBean`
|
||||
|
||||
Often you merely need to invoke a method on a specific object. By using the
|
||||
`MethodInvokingJobDetailFactoryBean`, you can do exactly this, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
|
||||
<property name="targetObject" ref="exampleBusinessObject"/>
|
||||
<property name="targetMethod" value="doIt"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
The preceding example results in the `doIt` method being called on the
|
||||
`exampleBusinessObject` method, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public class ExampleBusinessObject {
|
||||
|
||||
// properties and collaborators
|
||||
|
||||
public void doIt() {
|
||||
// do the actual work
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="exampleBusinessObject" class="examples.ExampleBusinessObject"/>
|
||||
----
|
||||
|
||||
By using the `MethodInvokingJobDetailFactoryBean`, you need not create one-line jobs
|
||||
that merely invoke a method. You need only create the actual business object and
|
||||
wire up the detail object.
|
||||
|
||||
By default, Quartz Jobs are stateless, resulting in the possibility of jobs interfering
|
||||
with each other. If you specify two triggers for the same `JobDetail`, it is
|
||||
possible that, before the first job has finished, the second one starts. If
|
||||
`JobDetail` classes implement the `Stateful` interface, this does not happen. The second
|
||||
job does not start before the first one has finished. To make jobs resulting from the
|
||||
`MethodInvokingJobDetailFactoryBean` be non-concurrent, set the `concurrent` flag to
|
||||
`false`, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
|
||||
<property name="targetObject" ref="exampleBusinessObject"/>
|
||||
<property name="targetMethod" value="doIt"/>
|
||||
<property name="concurrent" value="false"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
NOTE: By default, jobs will run in a concurrent fashion.
|
||||
|
||||
|
||||
[[scheduling-quartz-cron]]
|
||||
=== Wiring up Jobs by Using Triggers and `SchedulerFactoryBean`
|
||||
|
||||
We have created job details and jobs. We have also reviewed the convenience bean that lets
|
||||
you invoke a method on a specific object. Of course, we still need to schedule the
|
||||
jobs themselves. This is done by using triggers and a `SchedulerFactoryBean`. Several
|
||||
triggers are available within Quartz, and Spring offers two Quartz `FactoryBean`
|
||||
implementations with convenient defaults: `CronTriggerFactoryBean` and
|
||||
`SimpleTriggerFactoryBean`.
|
||||
|
||||
Triggers need to be scheduled. Spring offers a `SchedulerFactoryBean` that exposes
|
||||
triggers to be set as properties. `SchedulerFactoryBean` schedules the actual jobs with
|
||||
those triggers.
|
||||
|
||||
The following listing uses both a `SimpleTriggerFactoryBean` and a `CronTriggerFactoryBean`:
|
||||
|
||||
[source,xml,indent=0]
|
||||
[subs="verbatim"]
|
||||
----
|
||||
<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
|
||||
<!-- see the example of method invoking job above -->
|
||||
<property name="jobDetail" ref="jobDetail"/>
|
||||
<!-- 10 seconds -->
|
||||
<property name="startDelay" value="10000"/>
|
||||
<!-- repeat every 50 seconds -->
|
||||
<property name="repeatInterval" value="50000"/>
|
||||
</bean>
|
||||
|
||||
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
|
||||
<property name="jobDetail" ref="exampleJob"/>
|
||||
<!-- run every morning at 6 AM -->
|
||||
<property name="cronExpression" value="0 0 6 * * ?"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
The preceding example sets up two triggers, one running every 50 seconds with a starting delay of 10
|
||||
seconds and one running every morning at 6 AM. To finalize everything, we need to set up the
|
||||
`SchedulerFactoryBean`, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
|
||||
<property name="triggers">
|
||||
<list>
|
||||
<ref bean="cronTrigger"/>
|
||||
<ref bean="simpleTrigger"/>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
More properties are available for the `SchedulerFactoryBean`, such as the calendars used by the
|
||||
job details, properties to customize Quartz with, and a Spring-provided JDBC DataSource. See
|
||||
the {api-spring-framework}/scheduling/quartz/SchedulerFactoryBean.html[`SchedulerFactoryBean`]
|
||||
javadoc for more information.
|
||||
|
||||
NOTE: `SchedulerFactoryBean` also recognizes a `quartz.properties` file in the classpath,
|
||||
based on Quartz property keys, as with regular Quartz configuration. Please note that many
|
||||
`SchedulerFactoryBean` settings interact with common Quartz settings in the properties file;
|
||||
it is therefore not recommended to specify values at both levels. For example, do not set
|
||||
an "org.quartz.jobStore.class" property if you mean to rely on a Spring-provided DataSource,
|
||||
or specify an `org.springframework.scheduling.quartz.LocalDataSourceJobStore` variant which
|
||||
is a full-fledged replacement for the standard `org.quartz.impl.jdbcjobstore.JobStoreTX`.
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
:toc: left
|
||||
:toclevels: 4
|
||||
:tabsize: 4
|
||||
:docinfo1:
|
||||
@@ -1,24 +0,0 @@
|
||||
:noheader:
|
||||
:toc:
|
||||
include::attributes.adoc[]
|
||||
= Spring Framework Documentation
|
||||
Rod Johnson; Juergen Hoeller; Keith Donald; Colin Sampaleanu; Rob Harrop; Thomas Risberg; Alef Arendsen; Darren Davison; Dmitriy Kopylenko; Mark Pollack; Thierry Templier; Erwin Vervaet; Portia Tung; Ben Hale; Adrian Colyer; John Lewis; Costin Leau; Mark Fisher; Sam Brannen; Ramnivas Laddad; Arjen Poutsma; Chris Beams; Tareq Abedrabbo; Andy Clement; Dave Syer; Oliver Gierke; Rossen Stoyanchev; Phillip Webb; Rob Winch; Brian Clozel; Stephane Nicoll; Sebastien Deleuze; Jay Bryant; Mark Paluch
|
||||
|
||||
NOTE: This documentation is also available in {docs-spring-framework}/reference/html/index.html[HTML] format.
|
||||
|
||||
[[legal]]
|
||||
== Legal
|
||||
|
||||
Copyright © 2002 - 2022 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.
|
||||
|
||||
include::overview.adoc[leveloffset=+1]
|
||||
include::core.adoc[leveloffset=+1]
|
||||
include::testing.adoc[leveloffset=+1]
|
||||
include::data-access.adoc[leveloffset=+1]
|
||||
include::web.adoc[leveloffset=+1]
|
||||
include::web-reactive.adoc[leveloffset=+1]
|
||||
include::integration.adoc[leveloffset=+1]
|
||||
include::languages.adoc[leveloffset=+1]
|
||||
include::appendix.adoc[leveloffset=+1]
|
||||
@@ -1,30 +0,0 @@
|
||||
[[testing]]
|
||||
= Testing
|
||||
include::attributes.adoc[]
|
||||
include::page-layout.adoc[]
|
||||
|
||||
This chapter covers Spring's support for integration testing and best practices for unit
|
||||
testing. The Spring team advocates test-driven development (TDD). The Spring team has
|
||||
found that the correct use of inversion of control (IoC) certainly does make both unit
|
||||
and integration testing easier (in that the presence of setter methods and appropriate
|
||||
constructors on classes makes them easier to wire together in a test without having to
|
||||
set up service locator registries and similar structures).
|
||||
|
||||
|
||||
include::testing/testing-introduction.adoc[leveloffset=+1]
|
||||
|
||||
include::testing/unit-testing.adoc[leveloffset=+1]
|
||||
|
||||
include::testing/integration-testing.adoc[leveloffset=+1]
|
||||
|
||||
include::testing/testing-support-jdbc.adoc[leveloffset=+1]
|
||||
|
||||
include::testing/testcontext-framework.adoc[leveloffset=+1]
|
||||
|
||||
include::testing/testing-webtestclient.adoc[leveloffset=+1]
|
||||
|
||||
include::testing/spring-mvc-test-framework.adoc[leveloffset=+1]
|
||||
|
||||
include::testing/spring-mvc-test-client.adoc[leveloffset=+1]
|
||||
|
||||
include::testing/testing-appendix.adoc[leveloffset=+1]
|
||||
@@ -1,155 +0,0 @@
|
||||
[[integration-testing]]
|
||||
= Integration Testing
|
||||
|
||||
It is important to be able to perform some integration testing without requiring
|
||||
deployment to your application server or connecting to other enterprise infrastructure.
|
||||
Doing so lets you test things such as:
|
||||
|
||||
* The correct wiring of your Spring IoC container contexts.
|
||||
* Data access using JDBC or an ORM tool. This can include such things as the correctness
|
||||
of SQL statements, Hibernate queries, JPA entity mappings, and so forth.
|
||||
|
||||
The Spring Framework provides first-class support for integration testing in the
|
||||
`spring-test` module. The name of the actual JAR file might include the release version
|
||||
and might also be in the long `org.springframework.test` form, depending on where you get
|
||||
it from (see the <<core.adoc#beans-dependencies, section on Dependency Management>>
|
||||
for an explanation). This library includes the `org.springframework.test` package, which
|
||||
contains valuable classes for integration testing with a Spring container. This testing
|
||||
does not rely on an application server or other deployment environment. Such tests are
|
||||
slower to run than unit tests but much faster than the equivalent Selenium tests or
|
||||
remote tests that rely on deployment to an application server.
|
||||
|
||||
Unit and integration testing support is provided in the form of the annotation-driven
|
||||
<<testcontext-framework, Spring TestContext Framework>>. The TestContext framework is
|
||||
agnostic of the actual testing framework in use, which allows instrumentation of tests
|
||||
in various environments, including JUnit, TestNG, and others.
|
||||
|
||||
The following section provides an overview of the high-level goals of Spring's
|
||||
integration support, and the rest of this chapter then focuses on dedicated topics:
|
||||
|
||||
* <<integration-testing-support-jdbc>>
|
||||
* <<testcontext-framework>>
|
||||
* <<webtestclient>>
|
||||
* <<spring-mvc-test-framework>>
|
||||
* <<spring-mvc-test-client>>
|
||||
* <<integration-testing-annotations>>
|
||||
|
||||
|
||||
|
||||
[[integration-testing-goals]]
|
||||
== Goals of Integration Testing
|
||||
|
||||
Spring's integration testing support has the following primary goals:
|
||||
|
||||
* To manage <<testing-ctx-management, Spring IoC container caching>> between tests.
|
||||
* To provide <<testing-fixture-di, Dependency Injection of test fixture instances>>.
|
||||
* To provide <<testing-tx, transaction management>> appropriate to integration testing.
|
||||
* To supply <<testing-support-classes, Spring-specific base classes>> that assist
|
||||
developers in writing integration tests.
|
||||
|
||||
The next few sections describe each goal and provide links to implementation and
|
||||
configuration details.
|
||||
|
||||
|
||||
[[testing-ctx-management]]
|
||||
=== Context Management and Caching
|
||||
|
||||
The Spring TestContext Framework provides consistent loading of Spring
|
||||
`ApplicationContext` instances and `WebApplicationContext` instances as well as caching
|
||||
of those contexts. Support for the caching of loaded contexts is important, because
|
||||
startup time can become an issue -- not because of the overhead of Spring itself, but
|
||||
because the objects instantiated by the Spring container take time to instantiate. For
|
||||
example, a project with 50 to 100 Hibernate mapping files might take 10 to 20 seconds to
|
||||
load the mapping files, and incurring that cost before running every test in every test
|
||||
fixture leads to slower overall test runs that reduce developer productivity.
|
||||
|
||||
Test classes typically declare either an array of resource locations for XML or Groovy
|
||||
configuration metadata -- often in the classpath -- or an array of component classes that
|
||||
is used to configure the application. These locations or classes are the same as or
|
||||
similar to those specified in `web.xml` or other configuration files for production
|
||||
deployments.
|
||||
|
||||
By default, once loaded, the configured `ApplicationContext` is reused for each test.
|
||||
Thus, the setup cost is incurred only once per test suite, and subsequent test execution
|
||||
is much faster. In this context, the term "`test suite`" means all tests run in the same
|
||||
JVM -- for example, all tests run from an Ant, Maven, or Gradle build for a given project
|
||||
or module. In the unlikely case that a test corrupts the application context and requires
|
||||
reloading (for example, by modifying a bean definition or the state of an application
|
||||
object) the TestContext framework can be configured to reload the configuration and
|
||||
rebuild the application context before executing the next test.
|
||||
|
||||
See <<testcontext-ctx-management>> and <<testcontext-ctx-management-caching>> with the
|
||||
TestContext framework.
|
||||
|
||||
|
||||
[[testing-fixture-di]]
|
||||
=== Dependency Injection of Test Fixtures
|
||||
|
||||
When the TestContext framework loads your application context, it can optionally
|
||||
configure instances of your test classes by using Dependency Injection. This provides a
|
||||
convenient mechanism for setting up test fixtures by using preconfigured beans from your
|
||||
application context. A strong benefit here is that you can reuse application contexts
|
||||
across various testing scenarios (for example, for configuring Spring-managed object
|
||||
graphs, transactional proxies, `DataSource` instances, and others), thus avoiding the
|
||||
need to duplicate complex test fixture setup for individual test cases.
|
||||
|
||||
As an example, consider a scenario where we have a class (`HibernateTitleRepository`)
|
||||
that implements data access logic for a `Title` domain entity. We want to write
|
||||
integration tests that test the following areas:
|
||||
|
||||
* The Spring configuration: Basically, is everything related to the configuration of the
|
||||
`HibernateTitleRepository` bean correct and present?
|
||||
* The Hibernate mapping file configuration: Is everything mapped correctly and are the
|
||||
correct lazy-loading settings in place?
|
||||
* The logic of the `HibernateTitleRepository`: Does the configured instance of this class
|
||||
perform as anticipated?
|
||||
|
||||
See dependency injection of test fixtures with the
|
||||
<<testcontext-fixture-di, TestContext framework>>.
|
||||
|
||||
|
||||
[[testing-tx]]
|
||||
=== Transaction Management
|
||||
|
||||
One common issue in tests that access a real database is their effect on the state of the
|
||||
persistence store. Even when you use a development database, changes to the state may
|
||||
affect future tests. Also, many operations -- such as inserting or modifying persistent
|
||||
data -- cannot be performed (or verified) outside of a transaction.
|
||||
|
||||
The TestContext framework addresses this issue. By default, the framework creates and
|
||||
rolls back a transaction for each test. You can write code that can assume the existence
|
||||
of a transaction. If you call transactionally proxied objects in your tests, they behave
|
||||
correctly, according to their configured transactional semantics. In addition, if a test
|
||||
method deletes the contents of selected tables while running within the transaction
|
||||
managed for the test, the transaction rolls back by default, and the database returns to
|
||||
its state prior to execution of the test. Transactional support is provided to a test by
|
||||
using a `PlatformTransactionManager` bean defined in the test's application context.
|
||||
|
||||
If you want a transaction to commit (unusual, but occasionally useful when you want a
|
||||
particular test to populate or modify the database), you can tell the TestContext
|
||||
framework to cause the transaction to commit instead of roll back by using the
|
||||
<<integration-testing-annotations, `@Commit`>> annotation.
|
||||
|
||||
See transaction management with the <<testcontext-tx, TestContext framework>>.
|
||||
|
||||
|
||||
[[testing-support-classes]]
|
||||
=== Support Classes for Integration Testing
|
||||
|
||||
The Spring TestContext Framework provides several `abstract` support classes that
|
||||
simplify the writing of integration tests. These base test classes provide well-defined
|
||||
hooks into the testing framework as well as convenient instance variables and methods,
|
||||
which let you access:
|
||||
|
||||
* The `ApplicationContext`, for performing explicit bean lookups or testing the state of
|
||||
the context as a whole.
|
||||
* A `JdbcTemplate`, for executing SQL statements to query the database. You can use such
|
||||
queries to confirm database state both before and after execution of database-related
|
||||
application code, and Spring ensures that such queries run in the scope of the same
|
||||
transaction as the application code. When used in conjunction with an ORM tool, be sure
|
||||
to avoid <<testcontext-tx-false-positives, false positives>>.
|
||||
|
||||
In addition, you may want to create your own custom, application-wide superclass with
|
||||
instance variables and methods specific to your project.
|
||||
|
||||
See support classes for the <<testcontext-support-classes, TestContext framework>>.
|
||||
@@ -1,136 +0,0 @@
|
||||
[[spring-mvc-test-client]]
|
||||
= Testing Client Applications
|
||||
|
||||
You can use client-side tests to test code that internally uses the `RestTemplate`. The
|
||||
idea is to declare expected requests and to provide "`stub`" responses so that you can
|
||||
focus on testing the code in isolation (that is, without running a server). The following
|
||||
example shows how to do so:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build();
|
||||
mockServer.expect(requestTo("/greeting")).andRespond(withSuccess());
|
||||
|
||||
// Test code that uses the above RestTemplate ...
|
||||
|
||||
mockServer.verify();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val restTemplate = RestTemplate()
|
||||
|
||||
val mockServer = MockRestServiceServer.bindTo(restTemplate).build()
|
||||
mockServer.expect(requestTo("/greeting")).andRespond(withSuccess())
|
||||
|
||||
// Test code that uses the above RestTemplate ...
|
||||
|
||||
mockServer.verify()
|
||||
----
|
||||
|
||||
In the preceding example, `MockRestServiceServer` (the central class for client-side REST
|
||||
tests) configures the `RestTemplate` with a custom `ClientHttpRequestFactory` that
|
||||
asserts actual requests against expectations and returns "`stub`" responses. In this
|
||||
case, we expect a request to `/greeting` and want to return a 200 response with
|
||||
`text/plain` content. We can define additional expected requests and stub responses as
|
||||
needed. When we define expected requests and stub responses, the `RestTemplate` can be
|
||||
used in client-side code as usual. At the end of testing, `mockServer.verify()` can be
|
||||
used to verify that all expectations have been satisfied.
|
||||
|
||||
By default, requests are expected in the order in which expectations were declared. You
|
||||
can set the `ignoreExpectOrder` option when building the server, in which case all
|
||||
expectations are checked (in order) to find a match for a given request. That means
|
||||
requests are allowed to come in any order. The following example uses `ignoreExpectOrder`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
server = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
server = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build()
|
||||
----
|
||||
|
||||
Even with unordered requests by default, each request is allowed to run once only.
|
||||
The `expect` method provides an overloaded variant that accepts an `ExpectedCount`
|
||||
argument that specifies a count range (for example, `once`, `manyTimes`, `max`, `min`,
|
||||
`between`, and so on). The following example uses `times`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build();
|
||||
mockServer.expect(times(2), requestTo("/something")).andRespond(withSuccess());
|
||||
mockServer.expect(times(3), requestTo("/somewhere")).andRespond(withSuccess());
|
||||
|
||||
// ...
|
||||
|
||||
mockServer.verify();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val restTemplate = RestTemplate()
|
||||
|
||||
val mockServer = MockRestServiceServer.bindTo(restTemplate).build()
|
||||
mockServer.expect(times(2), requestTo("/something")).andRespond(withSuccess())
|
||||
mockServer.expect(times(3), requestTo("/somewhere")).andRespond(withSuccess())
|
||||
|
||||
// ...
|
||||
|
||||
mockServer.verify()
|
||||
----
|
||||
|
||||
Note that, when `ignoreExpectOrder` is not set (the default), and, therefore, requests
|
||||
are expected in order of declaration, then that order applies only to the first of any
|
||||
expected request. For example if "/something" is expected two times followed by
|
||||
"/somewhere" three times, then there should be a request to "/something" before there is
|
||||
a request to "/somewhere", but, aside from that subsequent "/something" and "/somewhere",
|
||||
requests can come at any time.
|
||||
|
||||
As an alternative to all of the above, the client-side test support also provides a
|
||||
`ClientHttpRequestFactory` implementation that you can configure into a `RestTemplate` to
|
||||
bind it to a `MockMvc` instance. That allows processing requests using actual server-side
|
||||
logic but without running a server. The following example shows how to do so:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
|
||||
this.restTemplate = new RestTemplate(new MockMvcClientHttpRequestFactory(mockMvc));
|
||||
|
||||
// Test code that uses the above RestTemplate ...
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build()
|
||||
restTemplate = RestTemplate(MockMvcClientHttpRequestFactory(mockMvc))
|
||||
|
||||
// Test code that uses the above RestTemplate ...
|
||||
----
|
||||
|
||||
[[spring-mvc-test-client-static-imports]]
|
||||
== Static Imports
|
||||
|
||||
As with server-side tests, the fluent API for client-side tests requires a few static
|
||||
imports. Those are easy to find by searching for `MockRest*`. Eclipse users should add
|
||||
`MockRestRequestMatchers.{asterisk}` and `MockRestResponseCreators.{asterisk}` as
|
||||
"`favorite static members`" in the Eclipse preferences under Java -> Editor -> Content
|
||||
Assist -> Favorites. That allows using content assist after typing the first character of
|
||||
the static method name. Other IDEs (such IntelliJ) may not require any additional
|
||||
configuration. Check for the support for code completion on static members.
|
||||
|
||||
[[spring-mvc-test-client-resources]]
|
||||
== Further Examples of Client-side REST Tests
|
||||
|
||||
Spring MVC Test's own tests include
|
||||
{spring-framework-main-code}/spring-test/src/test/java/org/springframework/test/web/client/samples[example
|
||||
tests] of client-side REST tests.
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,6 +0,0 @@
|
||||
[[testing.appendix]]
|
||||
= Appendix
|
||||
|
||||
include::testing-annotations.adoc[leveloffset=+1]
|
||||
|
||||
include::testing-resources.adoc[leveloffset=+1]
|
||||
@@ -1,8 +0,0 @@
|
||||
[[testing-introduction]]
|
||||
= Introduction to Spring Testing
|
||||
|
||||
Testing is an integral part of enterprise software development. This chapter focuses on
|
||||
the value added by the IoC principle to <<unit-testing, unit testing>> and on the benefits
|
||||
of the Spring Framework's support for <<integration-testing, integration testing>>. (A
|
||||
thorough treatment of testing in the enterprise is beyond the scope of this reference
|
||||
manual.)
|
||||
@@ -1,32 +0,0 @@
|
||||
[[testing-resources]]
|
||||
= Further Resources
|
||||
See the following resources for more information about testing:
|
||||
|
||||
* https://www.junit.org/[JUnit]: "A programmer-friendly testing framework for Java and the JVM".
|
||||
Used by the Spring Framework in its test suite and supported in the
|
||||
<<testcontext-framework, Spring TestContext Framework>>.
|
||||
* https://testng.org/[TestNG]: A testing framework inspired by JUnit with added support
|
||||
for test groups, data-driven testing, distributed testing, and other features. Supported
|
||||
in the <<testcontext-framework, Spring TestContext Framework>>
|
||||
* https://assertj.github.io/doc/[AssertJ]: "Fluent assertions for Java",
|
||||
including support for Java 8 lambdas, streams, and numerous other features.
|
||||
* https://en.wikipedia.org/wiki/Mock_Object[Mock Objects]: Article in Wikipedia.
|
||||
* http://www.mockobjects.com/[MockObjects.com]: Web site dedicated to mock objects, a
|
||||
technique for improving the design of code within test-driven development.
|
||||
* https://mockito.github.io[Mockito]: Java mock library based on the
|
||||
http://xunitpatterns.com/Test%20Spy.html[Test Spy] pattern. Used by the Spring Framework
|
||||
in its test suite.
|
||||
* https://easymock.org/[EasyMock]: Java library "that provides Mock Objects for
|
||||
interfaces (and objects through the class extension) by generating them on the fly using
|
||||
Java's proxy mechanism."
|
||||
* https://jmock.org/[JMock]: Library that supports test-driven development of Java code
|
||||
with mock objects.
|
||||
* https://www.dbunit.org/[DbUnit]: JUnit extension (also usable with Ant and Maven) that
|
||||
is targeted at database-driven projects and, among other things, puts your database into
|
||||
a known state between test runs.
|
||||
* https://www.testcontainers.org/[Testcontainers]: Java library that supports JUnit
|
||||
tests, providing lightweight, throwaway instances of common databases, Selenium web
|
||||
browsers, or anything else that can run in a Docker container.
|
||||
* https://sourceforge.net/projects/grinder/[The Grinder]: Java load testing framework.
|
||||
* https://github.com/Ninja-Squad/springmockk[SpringMockK]: Support for Spring Boot
|
||||
integration tests written in Kotlin using https://mockk.io/[MockK] instead of Mockito.
|
||||
@@ -1,35 +0,0 @@
|
||||
[[integration-testing-support-jdbc]]
|
||||
= JDBC Testing Support
|
||||
|
||||
[[integration-testing-support-jdbc-test-utils]]
|
||||
== JdbcTestUtils
|
||||
|
||||
The `org.springframework.test.jdbc` package contains `JdbcTestUtils`, which is a
|
||||
collection of JDBC-related utility functions intended to simplify standard database
|
||||
testing scenarios. Specifically, `JdbcTestUtils` provides the following static utility
|
||||
methods.
|
||||
|
||||
* `countRowsInTable(..)`: Counts the number of rows in the given table.
|
||||
* `countRowsInTableWhere(..)`: Counts the number of rows in the given table by using the
|
||||
provided `WHERE` clause.
|
||||
* `deleteFromTables(..)`: Deletes all rows from the specified tables.
|
||||
* `deleteFromTableWhere(..)`: Deletes rows from the given table by using the provided
|
||||
`WHERE` clause.
|
||||
* `dropTables(..)`: Drops the specified tables.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
<<testcontext-support-classes-junit4, `AbstractTransactionalJUnit4SpringContextTests`>>
|
||||
and <<testcontext-support-classes-testng, `AbstractTransactionalTestNGSpringContextTests`>>
|
||||
provide convenience methods that delegate to the aforementioned methods in
|
||||
`JdbcTestUtils`.
|
||||
====
|
||||
|
||||
[[integration-testing-support-jdbc-embedded-database]]
|
||||
== Embedded Databases
|
||||
|
||||
The `spring-jdbc` module provides support for configuring and launching an embedded
|
||||
database, which you can use in integration tests that interact with a database.
|
||||
For details, see <<data-access.adoc#jdbc-embedded-database-support, Embedded Database
|
||||
Support>> and <<data-access.adoc#jdbc-embedded-database-dao-testing, Testing Data Access
|
||||
Logic with an Embedded Database>>.
|
||||
@@ -1,168 +0,0 @@
|
||||
[[unit-testing]]
|
||||
= Unit Testing
|
||||
|
||||
Dependency injection should make your code less dependent on the container than it would
|
||||
be with traditional J2EE / Java EE development. The POJOs that make up your application
|
||||
should be testable in JUnit or TestNG tests, with objects instantiated by using the `new`
|
||||
operator, without Spring or any other container. You can use <<mock-objects, mock objects>>
|
||||
(in conjunction with other valuable testing techniques) to test your code in isolation.
|
||||
If you follow the architecture recommendations for Spring, the resulting clean layering
|
||||
and componentization of your codebase facilitate easier unit testing. For example,
|
||||
you can test service layer objects by stubbing or mocking DAO or repository interfaces,
|
||||
without needing to access persistent data while running unit tests.
|
||||
|
||||
True unit tests typically run extremely quickly, as there is no runtime infrastructure to
|
||||
set up. Emphasizing true unit tests as part of your development methodology can boost
|
||||
your productivity. You may not need this section of the testing chapter to help you write
|
||||
effective unit tests for your IoC-based applications. For certain unit testing scenarios,
|
||||
however, the Spring Framework provides mock objects and testing support classes, which
|
||||
are described in this chapter.
|
||||
|
||||
|
||||
|
||||
[[mock-objects]]
|
||||
== Mock Objects
|
||||
|
||||
Spring includes a number of packages dedicated to mocking:
|
||||
|
||||
* <<mock-objects-env>>
|
||||
* <<mock-objects-jndi>>
|
||||
* <<mock-objects-servlet>>
|
||||
* <<mock-objects-web-reactive>>
|
||||
|
||||
|
||||
[[mock-objects-env]]
|
||||
=== Environment
|
||||
|
||||
The `org.springframework.mock.env` package contains mock implementations of the
|
||||
`Environment` and `PropertySource` abstractions (see
|
||||
<<core.adoc#beans-definition-profiles, Bean Definition Profiles>>
|
||||
and <<core.adoc#beans-property-source-abstraction, `PropertySource` Abstraction>>).
|
||||
`MockEnvironment` and `MockPropertySource` are useful for developing
|
||||
out-of-container tests for code that depends on environment-specific properties.
|
||||
|
||||
|
||||
[[mock-objects-jndi]]
|
||||
=== JNDI
|
||||
|
||||
The `org.springframework.mock.jndi` package contains a partial implementation of the JNDI
|
||||
SPI, which you can use to set up a simple JNDI environment for test suites or stand-alone
|
||||
applications. If, for example, JDBC `DataSource` instances get bound to the same JNDI
|
||||
names in test code as they do in a Jakarta EE container, you can reuse both application code
|
||||
and configuration in testing scenarios without modification.
|
||||
|
||||
WARNING: The mock JNDI support in the `org.springframework.mock.jndi` package is
|
||||
officially deprecated as of Spring Framework 5.2 in favor of complete solutions from third
|
||||
parties such as https://github.com/h-thurow/Simple-JNDI[Simple-JNDI].
|
||||
|
||||
|
||||
[[mock-objects-servlet]]
|
||||
=== Servlet API
|
||||
|
||||
The `org.springframework.mock.web` package contains a comprehensive set of Servlet API
|
||||
mock objects that are useful for testing web contexts, controllers, and filters. These
|
||||
mock objects are targeted at usage with Spring's Web MVC framework and are generally more
|
||||
convenient to use than dynamic mock objects (such as https://easymock.org/[EasyMock])
|
||||
or alternative Servlet API mock objects (such as http://www.mockobjects.com[MockObjects]).
|
||||
|
||||
TIP: Since Spring Framework 6.0, the mock objects in `org.springframework.mock.web` are
|
||||
based on the Servlet 6.0 API.
|
||||
|
||||
The Spring MVC Test framework builds on the mock Servlet API objects to provide an
|
||||
integration testing framework for Spring MVC. See <<spring-mvc-test-framework>>.
|
||||
|
||||
|
||||
[[mock-objects-web-reactive]]
|
||||
=== Spring Web Reactive
|
||||
|
||||
The `org.springframework.mock.http.server.reactive` package contains mock implementations
|
||||
of `ServerHttpRequest` and `ServerHttpResponse` for use in WebFlux applications. The
|
||||
`org.springframework.mock.web.server` package contains a mock `ServerWebExchange` that
|
||||
depends on those mock request and response objects.
|
||||
|
||||
Both `MockServerHttpRequest` and `MockServerHttpResponse` extend from the same abstract
|
||||
base classes as server-specific implementations and share behavior with them. For
|
||||
example, a mock request is immutable once created, but you can use the `mutate()` method
|
||||
from `ServerHttpRequest` to create a modified instance.
|
||||
|
||||
In order for the mock response to properly implement the write contract and return a
|
||||
write completion handle (that is, `Mono<Void>`), it by default uses a `Flux` with
|
||||
`cache().then()`, which buffers the data and makes it available for assertions in tests.
|
||||
Applications can set a custom write function (for example, to test an infinite stream).
|
||||
|
||||
The <<webtestclient>> builds on the mock request and response to provide support for
|
||||
testing WebFlux applications without an HTTP server. The client can also be used for
|
||||
end-to-end tests with a running server.
|
||||
|
||||
|
||||
|
||||
[[unit-testing-support-classes]]
|
||||
== Unit Testing Support Classes
|
||||
|
||||
Spring includes a number of classes that can help with unit testing. They fall into two
|
||||
categories:
|
||||
|
||||
* <<unit-testing-utilities>>
|
||||
* <<unit-testing-spring-mvc>>
|
||||
|
||||
|
||||
[[unit-testing-utilities]]
|
||||
=== General Testing Utilities
|
||||
|
||||
The `org.springframework.test.util` package contains several general purpose utilities
|
||||
for use in unit and integration testing.
|
||||
|
||||
{api-spring-framework}/test/util/AopTestUtils.html[`AopTestUtils`] is a collection of
|
||||
AOP-related utility methods. You can use these methods to obtain a reference to the
|
||||
underlying target object hidden behind one or more Spring proxies. For example, if you
|
||||
have configured a bean as a dynamic mock by using a library such as EasyMock or Mockito,
|
||||
and the mock is wrapped in a Spring proxy, you may need direct access to the underlying
|
||||
mock to configure expectations on it and perform verifications. For Spring's core AOP
|
||||
utilities, see {api-spring-framework}/aop/support/AopUtils.html[`AopUtils`] and
|
||||
{api-spring-framework}/aop/framework/AopProxyUtils.html[`AopProxyUtils`].
|
||||
|
||||
{api-spring-framework}/test/util/ReflectionTestUtils.html[`ReflectionTestUtils`] is a
|
||||
collection of reflection-based utility methods. You can use these methods in testing
|
||||
scenarios where you need to change the value of a constant, set a non-`public` field,
|
||||
invoke a non-`public` setter method, or invoke a non-`public` configuration or lifecycle
|
||||
callback method when testing application code for use cases such as the following:
|
||||
|
||||
* ORM frameworks (such as JPA and Hibernate) that condone `private` or `protected` field
|
||||
access as opposed to `public` setter methods for properties in a domain entity.
|
||||
* Spring's support for annotations (such as `@Autowired`, `@Inject`, and `@Resource`),
|
||||
that provide dependency injection for `private` or `protected` fields, setter methods,
|
||||
and configuration methods.
|
||||
* Use of annotations such as `@PostConstruct` and `@PreDestroy` for lifecycle callback
|
||||
methods.
|
||||
|
||||
{api-spring-framework}/test/util/TestSocketUtils.html[`TestSocketUtils`] is a simple
|
||||
utility for finding available TCP ports on `localhost` for use in integration testing
|
||||
scenarios.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
`TestSocketUtils` can be used in integration tests which start an external server on an
|
||||
available random port. However, these utilities make no guarantee about the subsequent
|
||||
availability of a given port and are therefore unreliable. Instead of using
|
||||
`TestSocketUtils` to find an available local port for a server, it is recommended that
|
||||
you rely on a server's ability to start on a random ephemeral port that it selects or is
|
||||
assigned by the operating system. To interact with that server, you should query the
|
||||
server for the port it is currently using.
|
||||
====
|
||||
|
||||
|
||||
[[unit-testing-spring-mvc]]
|
||||
=== Spring MVC Testing Utilities
|
||||
|
||||
The `org.springframework.test.web` package contains
|
||||
{api-spring-framework}/test/web/ModelAndViewAssert.html[`ModelAndViewAssert`], which you
|
||||
can use in combination with JUnit, TestNG, or any other testing framework for unit tests
|
||||
that deal with Spring MVC `ModelAndView` objects.
|
||||
|
||||
.Unit testing Spring MVC Controllers
|
||||
TIP: To unit test your Spring MVC `Controller` classes as POJOs, use `ModelAndViewAssert`
|
||||
combined with `MockHttpServletRequest`, `MockHttpSession`, and so on from Spring's
|
||||
<<mock-objects-servlet, Servlet API mocks>>. For thorough integration testing of your
|
||||
Spring MVC and REST `Controller` classes in conjunction with your `WebApplicationContext`
|
||||
configuration for Spring MVC, use the
|
||||
<<spring-mvc-test-framework, Spring MVC Test Framework>> instead.
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.core.aot.hints.importruntimehints;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
||||
import org.springframework.context.annotation.ImportRuntimeHints;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@ImportRuntimeHints(SpellCheckService.SpellCheckServiceRuntimeHints.class)
|
||||
public class SpellCheckService {
|
||||
|
||||
public void loadDictionary(Locale locale) {
|
||||
ClassPathResource resource = new ClassPathResource("dicts/" + locale.getLanguage() + ".txt");
|
||||
//...
|
||||
}
|
||||
|
||||
static class SpellCheckServiceRuntimeHints implements RuntimeHintsRegistrar {
|
||||
|
||||
@Override
|
||||
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
|
||||
hints.resources().registerPattern("dicts/*");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.core.aot.hints.testing;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
public class SampleReflection {
|
||||
|
||||
private final Log logger = LogFactory.getLog(SampleReflection.class);
|
||||
|
||||
public void performReflection() {
|
||||
try {
|
||||
Class<?> springVersion = ClassUtils.forName("org.springframework.core.SpringVersion", null);
|
||||
Method getVersion = ClassUtils.getMethod(springVersion, "getVersion");
|
||||
String version = (String) getVersion.invoke(null);
|
||||
logger.info("Spring version:" + version);
|
||||
}
|
||||
catch (Exception exc) {
|
||||
logger.error("reflection failed", exc);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-54
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.core.aot.hints.testing;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.hint.ExecutableMode;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.test.agent.EnabledIfRuntimeHintsAgent;
|
||||
import org.springframework.aot.test.agent.RuntimeHintsInvocations;
|
||||
import org.springframework.aot.test.agent.RuntimeHintsRecorder;
|
||||
import org.springframework.core.SpringVersion;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
// @EnabledIfRuntimeHintsAgent signals that the annotated test class or test
|
||||
// method is only enabled if the RuntimeHintsAgent is loaded on the current JVM.
|
||||
// It also tags tests with the "RuntimeHints" JUnit tag.
|
||||
@EnabledIfRuntimeHintsAgent
|
||||
class SampleReflectionRuntimeHintsTests {
|
||||
|
||||
@Test
|
||||
void shouldRegisterReflectionHints() {
|
||||
RuntimeHints runtimeHints = new RuntimeHints();
|
||||
// Call a RuntimeHintsRegistrar that contributes hints like:
|
||||
runtimeHints.reflection().registerType(SpringVersion.class, typeHint ->
|
||||
typeHint.withMethod("getVersion", List.of(), ExecutableMode.INVOKE));
|
||||
|
||||
// Invoke the relevant piece of code we want to test within a recording lambda
|
||||
RuntimeHintsInvocations invocations = RuntimeHintsRecorder.record(() -> {
|
||||
SampleReflection sample = new SampleReflection();
|
||||
sample.performReflection();
|
||||
});
|
||||
// assert that the recorded invocations are covered by the contributed hints
|
||||
assertThat(invocations).match(runtimeHints);
|
||||
}
|
||||
|
||||
}
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.core.aot.hints.testing;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
||||
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class SpellCheckServiceTests {
|
||||
|
||||
// tag::hintspredicates[]
|
||||
@Test
|
||||
void shouldRegisterResourceHints() {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
new SpellCheckServiceRuntimeHints().registerHints(hints, getClass().getClassLoader());
|
||||
assertThat(RuntimeHintsPredicates.resource().forResource("dicts/en.txt"))
|
||||
.accepts(hints);
|
||||
}
|
||||
// end::hintspredicates[]
|
||||
|
||||
// Copied here because it is package private in SpellCheckService
|
||||
static class SpellCheckServiceRuntimeHints implements RuntimeHintsRegistrar {
|
||||
|
||||
@Override
|
||||
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
|
||||
hints.resources().registerPattern("dicts/*");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.core.aot.refresh;
|
||||
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
public class AotProcessingSample {
|
||||
|
||||
public void createAotContext() {
|
||||
// tag::aotcontext[]
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
||||
context.register(MyApplication.class);
|
||||
context.refreshForAotProcessing(hints);
|
||||
// end::aotcontext[]
|
||||
}
|
||||
|
||||
// tag::myapplication[]
|
||||
@Configuration(proxyBeanMethods=false)
|
||||
@ComponentScan
|
||||
@Import({DataSourceConfiguration.class, ContainerConfiguration.class})
|
||||
public class MyApplication {
|
||||
}
|
||||
// end::myapplication[]
|
||||
|
||||
class DataSourceConfiguration {
|
||||
}
|
||||
|
||||
class ContainerConfiguration {
|
||||
}
|
||||
|
||||
}
|
||||
-71
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.integration.observability.config.conventions;
|
||||
|
||||
import io.micrometer.common.KeyValue;
|
||||
import io.micrometer.common.KeyValues;
|
||||
|
||||
import org.springframework.http.server.observation.ServerHttpObservationDocumentation;
|
||||
import org.springframework.http.server.observation.ServerRequestObservationContext;
|
||||
import org.springframework.http.server.observation.ServerRequestObservationConvention;
|
||||
|
||||
public class CustomServerRequestObservationConvention implements ServerRequestObservationConvention {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
// will be used as the metric name
|
||||
return "http.server.requests";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContextualName(ServerRequestObservationContext context) {
|
||||
// will be used for the trace name
|
||||
return "http " + context.getCarrier().getMethod().toLowerCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyValues getLowCardinalityKeyValues(ServerRequestObservationContext context) {
|
||||
return KeyValues.of(method(context), status(context), exception(context));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public KeyValues getHighCardinalityKeyValues(ServerRequestObservationContext context) {
|
||||
return KeyValues.of(httpUrl(context));
|
||||
}
|
||||
|
||||
private KeyValue method(ServerRequestObservationContext context) {
|
||||
// You should reuse as much as possible the corresponding ObservationDocumentation for key names
|
||||
return KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.METHOD, context.getCarrier().getMethod());
|
||||
}
|
||||
|
||||
// @fold:on // status(), exception(), httpUrl()...
|
||||
private KeyValue status(ServerRequestObservationContext context) {
|
||||
return KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.STATUS, String.valueOf(context.getResponse().getStatus()));
|
||||
}
|
||||
|
||||
private KeyValue exception(ServerRequestObservationContext context) {
|
||||
String exception = (context.getError() != null) ? context.getError().getClass().getSimpleName() : KeyValue.NONE_VALUE;
|
||||
return KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.EXCEPTION, exception);
|
||||
}
|
||||
|
||||
private KeyValue httpUrl(ServerRequestObservationContext context) {
|
||||
return KeyValue.of(ServerHttpObservationDocumentation.HighCardinalityKeyNames.HTTP_URL, context.getCarrier().getRequestURI());
|
||||
}
|
||||
// @fold:off
|
||||
|
||||
}
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.integration.observability.config.conventions;
|
||||
|
||||
import io.micrometer.common.KeyValue;
|
||||
import io.micrometer.common.KeyValues;
|
||||
|
||||
import org.springframework.http.server.observation.DefaultServerRequestObservationConvention;
|
||||
import org.springframework.http.server.observation.ServerRequestObservationContext;
|
||||
|
||||
public class ExtendedServerRequestObservationConvention extends DefaultServerRequestObservationConvention {
|
||||
|
||||
@Override
|
||||
public KeyValues getLowCardinalityKeyValues(ServerRequestObservationContext context) {
|
||||
// here, we just want to have an additional KeyValue to the observation, keeping the default values
|
||||
return super.getLowCardinalityKeyValues(context).and(custom(context));
|
||||
}
|
||||
|
||||
private KeyValue custom(ServerRequestObservationContext context) {
|
||||
return KeyValue.of("custom.method", context.getCarrier().getMethod());
|
||||
}
|
||||
|
||||
}
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.docs.integration.observability.config.conventions;
|
||||
|
||||
|
||||
import io.micrometer.common.KeyValue;
|
||||
import io.micrometer.observation.Observation;
|
||||
import io.micrometer.observation.ObservationFilter;
|
||||
|
||||
import org.springframework.http.server.observation.ServerRequestObservationContext;
|
||||
|
||||
public class ServerRequestObservationFilter implements ObservationFilter {
|
||||
|
||||
@Override
|
||||
public Observation.Context map(Observation.Context context) {
|
||||
if (context instanceof ServerRequestObservationContext serverContext) {
|
||||
context.setName("custom.observation.name");
|
||||
context.addLowCardinalityKeyValue(KeyValue.of("project", "spring"));
|
||||
String customAttribute = (String) serverContext.getCarrier().getAttribute("customAttribute");
|
||||
context.addLowCardinalityKeyValue(KeyValue.of("custom.attribute", customAttribute));
|
||||
}
|
||||
return context;
|
||||
}
|
||||
}
|
||||
@@ -7,29 +7,29 @@ javaPlatform {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(platform("com.fasterxml.jackson:jackson-bom:2.14.1"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.10.2"))
|
||||
api(platform("io.netty:netty-bom:4.1.85.Final"))
|
||||
api(platform("com.fasterxml.jackson:jackson-bom:2.13.4"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.10.0-RC1"))
|
||||
api(platform("io.netty:netty-bom:4.1.84.Final"))
|
||||
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
|
||||
api(platform("io.projectreactor:reactor-bom:2022.0.1"))
|
||||
api(platform("io.projectreactor:reactor-bom:2022.0.0-RC1"))
|
||||
api(platform("io.rsocket:rsocket-bom:1.1.3"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.6"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.5"))
|
||||
api(platform("org.apache.logging.log4j:log4j-bom:2.19.0"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:11.0.12"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.4"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.4.0"))
|
||||
api(platform("org.junit:junit-bom:5.9.1"))
|
||||
api(platform("org.mockito:mockito-bom:4.9.0"))
|
||||
api(platform("org.mockito:mockito-bom:4.8.0"))
|
||||
|
||||
constraints {
|
||||
api("com.fasterxml.woodstox:woodstox-core:6.3.1")
|
||||
api("com.fasterxml:aalto-xml:1.3.1")
|
||||
api("com.fasterxml.woodstox:woodstox-core:6.4.0")
|
||||
api("com.github.ben-manes.caffeine:caffeine:3.1.2")
|
||||
api("com.github.librepdf:openpdf:1.3.30")
|
||||
api("com.github.ben-manes.caffeine:caffeine:3.1.1")
|
||||
api("com.github.librepdf:openpdf:1.3.29")
|
||||
api("com.google.code.findbugs:findbugs:3.0.1")
|
||||
api("com.google.code.findbugs:jsr305:3.0.2")
|
||||
api("com.google.code.gson:gson:2.10")
|
||||
api("com.google.protobuf:protobuf-java-util:3.21.11")
|
||||
api("com.google.code.gson:gson:2.9.1")
|
||||
api("com.google.protobuf:protobuf-java-util:3.21.5")
|
||||
api("com.googlecode.protobuf-java-format:protobuf-java-format:1.4")
|
||||
api("com.h2database:h2:2.1.214")
|
||||
api("com.jayway.jsonpath:json-path:2.7.0")
|
||||
@@ -45,19 +45,18 @@ dependencies {
|
||||
api("com.thoughtworks.xstream:xstream:1.4.19")
|
||||
api("commons-io:commons-io:2.11.0")
|
||||
api("de.bechte.junit:junit-hierarchicalcontextrunner:4.12.1")
|
||||
api("info.picocli:picocli:4.7.0")
|
||||
api("io.micrometer:context-propagation:1.0.0")
|
||||
api("info.picocli:picocli:4.6.3")
|
||||
api("io.micrometer:context-propagation:1.0.0-RC1")
|
||||
api("io.mockk:mockk:1.12.1")
|
||||
api("io.projectreactor.netty:reactor-netty5-http:2.0.0-M3")
|
||||
api("io.projectreactor.tools:blockhound:1.0.6.RELEASE")
|
||||
api("io.r2dbc:r2dbc-h2:1.0.0.RELEASE")
|
||||
api("io.r2dbc:r2dbc-h2:1.0.0.RC1")
|
||||
api("io.r2dbc:r2dbc-spi-test:1.0.0.RELEASE")
|
||||
api("io.r2dbc:r2dbc-spi:1.0.0.RELEASE")
|
||||
api("io.reactivex.rxjava3:rxjava:3.1.5")
|
||||
api("io.smallrye.reactive:mutiny:1.8.0")
|
||||
api("io.undertow:undertow-core:2.3.0.Final")
|
||||
api("io.undertow:undertow-servlet:2.3.0.Final")
|
||||
api("io.undertow:undertow-websockets-jsr:2.3.0.Final")
|
||||
api("io.smallrye.reactive:mutiny:1.7.0")
|
||||
api("io.undertow:undertow-core:2.2.19.Final")
|
||||
api("io.undertow:undertow-servlet-jakarta:2.2.19.Final")
|
||||
api("io.undertow:undertow-websockets-jsr-jakarta:2.2.19.Final")
|
||||
api("io.vavr:vavr:0.10.4")
|
||||
api("jakarta.activation:jakarta.activation-api:2.0.1")
|
||||
api("jakarta.annotation:jakarta.annotation-api:2.0.0")
|
||||
@@ -74,35 +73,33 @@ dependencies {
|
||||
api("jakarta.mail:jakarta.mail-api:2.0.1")
|
||||
api("jakarta.persistence:jakarta.persistence-api:3.0.0")
|
||||
api("jakarta.resource:jakarta.resource-api:2.0.0")
|
||||
api("jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api:3.0.0")
|
||||
api("jakarta.servlet.jsp:jakarta.servlet.jsp-api:3.1.0")
|
||||
api("jakarta.servlet:jakarta.servlet-api:6.0.0")
|
||||
api("jakarta.transaction:jakarta.transaction-api:2.0.1")
|
||||
api("jakarta.validation:jakarta.validation-api:3.0.2")
|
||||
api("jakarta.websocket:jakarta.websocket-api:2.1.0")
|
||||
api("jakarta.websocket:jakarta.websocket-client-api:2.1.0")
|
||||
api("jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api:2.0.0")
|
||||
api("jakarta.servlet.jsp:jakarta.servlet.jsp-api:3.0.0")
|
||||
api("jakarta.servlet:jakarta.servlet-api:5.0.0")
|
||||
api("jakarta.transaction:jakarta.transaction-api:2.0.0")
|
||||
api("jakarta.validation:jakarta.validation-api:3.0.0")
|
||||
api("jakarta.websocket:jakarta.websocket-api:2.0.0")
|
||||
api("jakarta.xml.bind:jakarta.xml.bind-api:3.0.1")
|
||||
api("javax.cache:cache-api:1.1.1")
|
||||
api("javax.money:money-api:1.1")
|
||||
api("jaxen:jaxen:1.2.0")
|
||||
api("junit:junit:4.13.2")
|
||||
api("net.sf.jopt-simple:jopt-simple:5.0.4")
|
||||
api("net.sourceforge.htmlunit:htmlunit:2.67.0")
|
||||
api("net.sourceforge.htmlunit:htmlunit:2.65.1")
|
||||
api("org.apache-extras.beanshell:bsh:2.0b6")
|
||||
api("org.apache.activemq:activemq-broker:5.16.2")
|
||||
api("org.apache.activemq:activemq-kahadb-store:5.16.2")
|
||||
api("org.apache.activemq:activemq-stomp:5.16.2")
|
||||
api("org.apache.commons:commons-pool2:2.9.0")
|
||||
api("org.apache.derby:derby:10.16.1.1")
|
||||
api("org.apache.derby:derbyclient:10.16.1.1")
|
||||
api("org.apache.derby:derbytools:10.16.1.1")
|
||||
api("org.apache.httpcomponents.client5:httpclient5:5.2")
|
||||
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.2")
|
||||
api("org.apache.poi:poi-ooxml:5.2.3")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.1")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.1")
|
||||
api("org.apache.tomcat:tomcat-util:10.1.1")
|
||||
api("org.apache.tomcat:tomcat-websocket:10.1.1")
|
||||
api("org.apache.derby:derby:10.14.2.0")
|
||||
api("org.apache.derby:derbyclient:10.14.2.0")
|
||||
api("org.apache.httpcomponents.client5:httpclient5:5.1.3")
|
||||
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.1.3")
|
||||
api("org.apache.poi:poi-ooxml:5.2.2")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.0")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.0")
|
||||
api("org.apache.tomcat:tomcat-util:10.0.22")
|
||||
api("org.apache.tomcat:tomcat-websocket:10.0.22")
|
||||
api("org.aspectj:aspectjrt:1.9.9.1")
|
||||
api("org.aspectj:aspectjtools:1.9.9.1")
|
||||
api("org.aspectj:aspectjweaver:1.9.9.1")
|
||||
@@ -111,7 +108,7 @@ dependencies {
|
||||
api("org.bouncycastle:bcpkix-jdk18on:1.71")
|
||||
api("org.codehaus.jettison:jettison:1.3.8")
|
||||
api("org.dom4j:dom4j:2.1.3")
|
||||
api("org.eclipse.jetty:jetty-reactive-httpclient:3.0.7")
|
||||
api("org.eclipse.jetty:jetty-reactive-httpclient:3.0.6")
|
||||
api("org.eclipse.persistence:org.eclipse.persistence.jpa:3.0.3")
|
||||
api("org.eclipse:yasson:2.0.4")
|
||||
api("org.ehcache:ehcache:3.4.0")
|
||||
@@ -122,27 +119,27 @@ dependencies {
|
||||
api("org.glassfish.external:opendmk_jmxremote_optional_jar:1.0-b01-ea")
|
||||
api("org.glassfish.tyrus:tyrus-container-servlet:2.0.1")
|
||||
api("org.glassfish:jakarta.el:4.0.2")
|
||||
api("org.graalvm.sdk:graal-sdk:22.3.0")
|
||||
api("org.graalvm.nativeimage:svm:22.1.0.1")
|
||||
api("org.hamcrest:hamcrest:2.2")
|
||||
api("org.hibernate:hibernate-core-jakarta:5.6.14.Final")
|
||||
api("org.hibernate:hibernate-core-jakarta:5.6.12.Final")
|
||||
api("org.hibernate:hibernate-validator:7.0.5.Final")
|
||||
api("org.hsqldb:hsqldb:2.7.1")
|
||||
api("org.hsqldb:hsqldb:2.7.0")
|
||||
api("org.javamoney:moneta:1.4.2")
|
||||
api("org.jruby:jruby:9.4.0.0")
|
||||
api("org.jruby:jruby:9.3.8.0")
|
||||
api("org.junit.support:testng-engine:1.0.4")
|
||||
api("org.mozilla:rhino:1.7.11")
|
||||
api("org.ogce:xpp3:1.1.6")
|
||||
api("org.python:jython-standalone:2.7.1")
|
||||
api("org.quartz-scheduler:quartz:2.3.2")
|
||||
api("org.seleniumhq.selenium:htmlunit-driver:2.66.0")
|
||||
api("org.seleniumhq.selenium:htmlunit-driver:2.65.0")
|
||||
api("org.seleniumhq.selenium:selenium-java:3.141.59")
|
||||
api("org.skyscreamer:jsonassert:1.5.0")
|
||||
api("org.slf4j:slf4j-api:2.0.5")
|
||||
api("org.slf4j:slf4j-api:2.0.2")
|
||||
api("org.testng:testng:7.6.1")
|
||||
api("org.webjars:underscorejs:1.8.3")
|
||||
api("org.webjars:webjars-locator-core:0.52")
|
||||
api("org.webjars:webjars-locator-core:0.48")
|
||||
api("org.xmlunit:xmlunit-assertj:2.9.0")
|
||||
api("org.xmlunit:xmlunit-matchers:2.9.0")
|
||||
api("org.yaml:snakeyaml:1.33")
|
||||
api("org.yaml:snakeyaml:1.30")
|
||||
}
|
||||
}
|
||||
|
||||
+2
-6
@@ -1,9 +1,5 @@
|
||||
version=6.0.3
|
||||
|
||||
org.gradle.caching=true
|
||||
version=6.0.0-RC1
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
org.gradle.caching=true
|
||||
org.gradle.parallel=true
|
||||
|
||||
kotlinVersion=1.7.21
|
||||
|
||||
kotlin.stdlib.default.dependency=false
|
||||
|
||||
@@ -1,32 +1,7 @@
|
||||
description = "Spring Framework Docs"
|
||||
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'org.asciidoctor.jvm.convert'
|
||||
apply plugin: 'org.asciidoctor.jvm.pdf'
|
||||
apply from: "${rootDir}/gradle/publications.gradle"
|
||||
|
||||
|
||||
configurations {
|
||||
asciidoctorExtensions
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(project(":spring-context"))
|
||||
api(project(":spring-web"))
|
||||
api("jakarta.servlet:jakarta.servlet-api")
|
||||
|
||||
implementation(project(":spring-core-test"))
|
||||
implementation("org.assertj:assertj-core")
|
||||
}
|
||||
|
||||
jar {
|
||||
enabled = false
|
||||
}
|
||||
|
||||
javadoc {
|
||||
enabled = false
|
||||
}
|
||||
|
||||
dependencies {
|
||||
asciidoctorExtensions "io.spring.asciidoctor.backends:spring-asciidoctor-backends:0.0.3"
|
||||
}
|
||||
@@ -64,7 +39,7 @@ task api(type: Javadoc) {
|
||||
author = true
|
||||
header = rootProject.description
|
||||
use = true
|
||||
overview = "framework-docs/src/docs/api/overview.html"
|
||||
overview = "src/docs/api/overview.html"
|
||||
splitIndex = true
|
||||
links(project.ext.javadocLinks)
|
||||
addBooleanOption('Xdoclint:syntax', true) // only check syntax with doclint
|
||||
@@ -80,15 +55,19 @@ task api(type: Javadoc) {
|
||||
/**
|
||||
* Produce KDoc for all Spring Framework modules in "build/docs/kdoc"
|
||||
*/
|
||||
rootProject.tasks.dokkaHtmlMultiModule.configure {
|
||||
dependsOn {
|
||||
tasks.getByName("api")
|
||||
pluginManager.withPlugin("kotlin") {
|
||||
tasks.dokkaHtmlMultiModule.configure {
|
||||
dependsOn {
|
||||
tasks.getByName("api")
|
||||
}
|
||||
moduleName.set("spring-framework")
|
||||
outputDirectory.set(project.file("$buildDir/docs/kdoc"))
|
||||
}
|
||||
moduleName.set("spring-framework")
|
||||
outputDirectory.set(project.file("$buildDir/docs/kdoc"))
|
||||
}
|
||||
|
||||
asciidoctorj {
|
||||
def docRoot = 'https://docs.spring.io'
|
||||
def docsSpringFramework = "${docRoot}/spring-framework/docs/${project.version}"
|
||||
version = '2.4.3'
|
||||
fatalWarnings ".*"
|
||||
options doctype: 'book', eruby: 'erubis'
|
||||
@@ -99,13 +78,17 @@ asciidoctorj {
|
||||
revnumber: project.version,
|
||||
sectanchors: '',
|
||||
sectnums: '',
|
||||
'spring-version': project.version
|
||||
'spring-version': project.version,
|
||||
'spring-framework-main-code': 'https://github.com/spring-projects/spring-framework/tree/main',
|
||||
'doc-root': docRoot,
|
||||
'docs-spring-framework': docsSpringFramework,
|
||||
'api-spring-framework': "${docsSpringFramework}/javadoc-api/org/springframework"
|
||||
])
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the Spring Framework Reference documentation from
|
||||
* "src/docs/asciidoc" in "build/docs/ref-docs/html5".
|
||||
* Generate the Spring Framework Reference documentation from "src/docs/asciidoc"
|
||||
* in "build/docs/ref-docs/html5".
|
||||
*/
|
||||
asciidoctor {
|
||||
baseDirFollowsSourceDir()
|
||||
@@ -113,23 +96,18 @@ asciidoctor {
|
||||
sources {
|
||||
include '*.adoc'
|
||||
}
|
||||
outputDir "$buildDir/docs/ref-docs/html5"
|
||||
outputOptions {
|
||||
backends "spring-html"
|
||||
}
|
||||
logDocuments = true
|
||||
resources {
|
||||
from(sourceDir) {
|
||||
include 'images/*.png'
|
||||
}
|
||||
}
|
||||
outputDir "$buildDir/docs/ref-docs/html5"
|
||||
outputOptions {
|
||||
backends "spring-html"
|
||||
}
|
||||
forkOptions {
|
||||
jvmArgs += ["--add-opens", "java.base/sun.nio.ch=ALL-UNNAMED", "--add-opens", "java.base/java.io=ALL-UNNAMED"]
|
||||
}
|
||||
logDocuments = true
|
||||
}
|
||||
|
||||
asciidoctor.mustRunAfter "check"
|
||||
|
||||
/**
|
||||
* Generate the Spring Framework Reference documentation from "src/docs/asciidoc"
|
||||
* in "build/docs/ref-docs/pdf".
|
||||
@@ -138,7 +116,7 @@ asciidoctorPdf {
|
||||
baseDirFollowsSourceDir()
|
||||
configurations 'asciidoctorExtensions'
|
||||
sources {
|
||||
include 'spring-framework.adocbook'
|
||||
include '*.adoc'
|
||||
}
|
||||
outputDir "$buildDir/docs/ref-docs/pdf"
|
||||
forkOptions {
|
||||
@@ -150,7 +128,7 @@ asciidoctorPdf {
|
||||
/**
|
||||
* Zip all docs (API and reference) into a single archive
|
||||
*/
|
||||
task docsZip(type: Zip, dependsOn: ['api', 'asciidoctor', 'asciidoctorPdf', rootProject.tasks.dokkaHtmlMultiModule]) {
|
||||
task docsZip(type: Zip, dependsOn: ['api', 'asciidoctor', 'asciidoctorPdf', 'dokkaHtmlMultiModule']) {
|
||||
group = "Distribution"
|
||||
description = "Builds -${archiveClassifier} archive containing api and reference " +
|
||||
"for deployment at https://docs.spring.io/spring-framework/docs/."
|
||||
@@ -169,7 +147,7 @@ task docsZip(type: Zip, dependsOn: ['api', 'asciidoctor', 'asciidoctorPdf', root
|
||||
from ("$asciidoctorPdf.outputDir") {
|
||||
into "reference/pdf"
|
||||
}
|
||||
from (rootProject.tasks.dokkaHtmlMultiModule.outputDirectory) {
|
||||
from (dokkaHtmlMultiModule.outputDirectory) {
|
||||
into "kdoc-api"
|
||||
}
|
||||
}
|
||||
@@ -248,13 +226,3 @@ task distZip(type: Zip, dependsOn: [docsZip, schemaZip]) {
|
||||
}
|
||||
|
||||
distZip.mustRunAfter moduleProjects.check
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
artifact docsZip
|
||||
artifact schemaZip
|
||||
artifact distZip
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,16 +8,11 @@ apply plugin: 'me.champeau.jmh'
|
||||
apply from: "$rootDir/gradle/publications.gradle"
|
||||
|
||||
dependencies {
|
||||
jmh 'org.openjdk.jmh:jmh-core:1.36'
|
||||
jmh 'org.openjdk.jmh:jmh-generator-annprocess:1.36'
|
||||
jmh 'org.openjdk.jmh:jmh-core:1.32'
|
||||
jmh 'org.openjdk.jmh:jmh-generator-annprocess:1.32'
|
||||
jmh 'net.sf.jopt-simple:jopt-simple'
|
||||
}
|
||||
|
||||
pluginManager.withPlugin("kotlin") {
|
||||
apply plugin: "org.jetbrains.dokka"
|
||||
apply from: "${rootDir}/gradle/docs-dokka.gradle"
|
||||
}
|
||||
|
||||
jmh {
|
||||
duplicateClassesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
@@ -49,7 +44,7 @@ jar {
|
||||
manifest.attributes["Created-By"] =
|
||||
"${System.getProperty("java.version")} (${System.getProperty("java.specification.vendor")})"
|
||||
|
||||
from("${rootDir}/framework-docs/src/docs/dist") {
|
||||
from("${rootDir}/src/docs/dist") {
|
||||
include "license.txt"
|
||||
include "notice.txt"
|
||||
into "META-INF"
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+1
-2
@@ -1,6 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
|
||||
networkTimeout=10000
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@@ -80,11 +80,11 @@ do
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=${0##*/}
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
@@ -143,16 +143,12 @@ fi
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
@@ -209,12 +205,6 @@ set -- \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
|
||||
Vendored
+6
-9
@@ -14,7 +14,7 @@
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@@ -25,8 +25,7 @@
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@@ -41,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
@@ -76,15 +75,13 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -34,7 +34,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for scoped proxy use in conjunction with aop: namespace.
|
||||
* Deemed an integration test because web mocks and application contexts are required.
|
||||
* Deemed an integration test because .web mocks and application contexts are required.
|
||||
*
|
||||
* @author Rob Harrop
|
||||
* @author Juergen Hoeller
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@ class EnableCachingIntegrationTests {
|
||||
ctx.register(Config.class, AspectJCacheConfig.class);
|
||||
// this test is a bit fragile, but gets the job done, proving that an
|
||||
// attempt was made to look up the AJ aspect. It's due to classpath issues
|
||||
// in integration-tests that it's not found.
|
||||
// in .integration-tests that it's not found.
|
||||
assertThatException().isThrownBy(ctx::refresh)
|
||||
.withMessageContaining("AspectJCachingConfiguration");
|
||||
}
|
||||
|
||||
+1
-1
@@ -97,7 +97,7 @@ class EnableTransactionManagementIntegrationTests {
|
||||
ctx.register(Config.class, AspectJTxConfig.class);
|
||||
// this test is a bit fragile, but gets the job done, proving that an
|
||||
// attempt was made to look up the AJ aspect. It's due to classpath issues
|
||||
// in integration-tests that it's not found.
|
||||
// in .integration-tests that it's not found.
|
||||
assertThatException()
|
||||
.isThrownBy(ctx::refresh)
|
||||
.withMessageContaining("AspectJJtaTransactionManagementConfiguration");
|
||||
|
||||
+2
-1
@@ -18,6 +18,8 @@ include "spring-context"
|
||||
include "spring-context-indexer"
|
||||
include "spring-context-support"
|
||||
include "spring-core"
|
||||
include "graalvm-feature"
|
||||
project(':graalvm-feature').projectDir = file('spring-core/graalvm')
|
||||
include "spring-core-test"
|
||||
include "spring-expression"
|
||||
include "spring-instrument"
|
||||
@@ -35,7 +37,6 @@ include "spring-webflux"
|
||||
include "spring-webmvc"
|
||||
include "spring-websocket"
|
||||
include "framework-bom"
|
||||
include "framework-docs"
|
||||
include "framework-platform"
|
||||
include "integration-tests"
|
||||
|
||||
|
||||
+8
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -132,17 +132,17 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
private static final int STEP_REFERENCE_PCUT_BINDING = 7;
|
||||
private static final int STEP_FINISHED = 8;
|
||||
|
||||
private static final Set<String> singleValuedAnnotationPcds = Set.of(
|
||||
"@this",
|
||||
"@target",
|
||||
"@within",
|
||||
"@withincode",
|
||||
"@annotation");
|
||||
|
||||
private static final Set<String> singleValuedAnnotationPcds = new HashSet<>();
|
||||
private static final Set<String> nonReferencePointcutTokens = new HashSet<>();
|
||||
|
||||
|
||||
static {
|
||||
singleValuedAnnotationPcds.add("@this");
|
||||
singleValuedAnnotationPcds.add("@target");
|
||||
singleValuedAnnotationPcds.add("@within");
|
||||
singleValuedAnnotationPcds.add("@withincode");
|
||||
singleValuedAnnotationPcds.add("@annotation");
|
||||
|
||||
Set<PointcutPrimitive> pointcutPrimitives = PointcutParser.getAllSupportedPointcutPrimitives();
|
||||
for (PointcutPrimitive primitive : pointcutPrimitives) {
|
||||
nonReferencePointcutTokens.add(primitive.getName());
|
||||
|
||||
+16
-11
@@ -21,6 +21,7 @@ import java.io.ObjectInputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -84,17 +85,21 @@ import org.springframework.util.StringUtils;
|
||||
public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
||||
implements ClassFilter, IntroductionAwareMethodMatcher, BeanFactoryAware {
|
||||
|
||||
private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = Set.of(
|
||||
PointcutPrimitive.EXECUTION,
|
||||
PointcutPrimitive.ARGS,
|
||||
PointcutPrimitive.REFERENCE,
|
||||
PointcutPrimitive.THIS,
|
||||
PointcutPrimitive.TARGET,
|
||||
PointcutPrimitive.WITHIN,
|
||||
PointcutPrimitive.AT_ANNOTATION,
|
||||
PointcutPrimitive.AT_WITHIN,
|
||||
PointcutPrimitive.AT_ARGS,
|
||||
PointcutPrimitive.AT_TARGET);
|
||||
private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<>();
|
||||
|
||||
static {
|
||||
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
|
||||
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.ARGS);
|
||||
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.REFERENCE);
|
||||
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.THIS);
|
||||
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.TARGET);
|
||||
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.WITHIN);
|
||||
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ANNOTATION);
|
||||
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_WITHIN);
|
||||
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ARGS);
|
||||
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_TARGET);
|
||||
}
|
||||
|
||||
|
||||
private static final Log logger = LogFactory.getLog(AspectJExpressionPointcut.class);
|
||||
|
||||
|
||||
+20
-15
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,6 +21,7 @@ import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
@@ -162,22 +163,24 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
|
||||
|
||||
|
||||
/**
|
||||
* Class modeling an AspectJ annotation, exposing its type enumeration and
|
||||
* Class modelling an AspectJ annotation, exposing its type enumeration and
|
||||
* pointcut String.
|
||||
* @param <A> the annotation type
|
||||
*/
|
||||
protected static class AspectJAnnotation<A extends Annotation> {
|
||||
|
||||
private static final String[] EXPRESSION_ATTRIBUTES = {"pointcut", "value"};
|
||||
private static final String[] EXPRESSION_ATTRIBUTES = new String[] {"pointcut", "value"};
|
||||
|
||||
private static final Map<Class<?>, AspectJAnnotationType> annotationTypeMap = Map.of(
|
||||
Pointcut.class, AspectJAnnotationType.AtPointcut, //
|
||||
Around.class, AspectJAnnotationType.AtAround, //
|
||||
Before.class, AspectJAnnotationType.AtBefore, //
|
||||
After.class, AspectJAnnotationType.AtAfter, //
|
||||
AfterReturning.class, AspectJAnnotationType.AtAfterReturning, //
|
||||
AfterThrowing.class, AspectJAnnotationType.AtAfterThrowing //
|
||||
);
|
||||
private static Map<Class<?>, AspectJAnnotationType> annotationTypeMap = new HashMap<>(8);
|
||||
|
||||
static {
|
||||
annotationTypeMap.put(Pointcut.class, AspectJAnnotationType.AtPointcut);
|
||||
annotationTypeMap.put(Around.class, AspectJAnnotationType.AtAround);
|
||||
annotationTypeMap.put(Before.class, AspectJAnnotationType.AtBefore);
|
||||
annotationTypeMap.put(After.class, AspectJAnnotationType.AtAfter);
|
||||
annotationTypeMap.put(AfterReturning.class, AspectJAnnotationType.AtAfterReturning);
|
||||
annotationTypeMap.put(AfterThrowing.class, AspectJAnnotationType.AtAfterThrowing);
|
||||
}
|
||||
|
||||
private final A annotation;
|
||||
|
||||
@@ -193,7 +196,7 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
|
||||
try {
|
||||
this.pointcutExpression = resolveExpression(annotation);
|
||||
Object argNames = AnnotationUtils.getValue(annotation, "argNames");
|
||||
this.argumentNames = (argNames instanceof String names ? names : "");
|
||||
this.argumentNames = (argNames instanceof String ? (String) argNames : "");
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalArgumentException(annotation + " is not a valid AspectJ annotation", ex);
|
||||
@@ -211,11 +214,13 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
|
||||
private String resolveExpression(A annotation) {
|
||||
for (String attributeName : EXPRESSION_ATTRIBUTES) {
|
||||
Object val = AnnotationUtils.getValue(annotation, attributeName);
|
||||
if (val instanceof String str && !str.isEmpty()) {
|
||||
return str;
|
||||
if (val instanceof String str) {
|
||||
if (!str.isEmpty()) {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Failed to resolve expression in: " + annotation);
|
||||
throw new IllegalStateException("Failed to resolve expression: " + annotation);
|
||||
}
|
||||
|
||||
public AspectJAnnotationType getAnnotationType() {
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -58,7 +58,7 @@ public class SimpleBeanFactoryAwareAspectInstanceFactory implements AspectInstan
|
||||
|
||||
|
||||
/**
|
||||
* Look up the aspect bean from the {@link BeanFactory} and return it.
|
||||
* Look up the aspect bean from the {@link BeanFactory} and returns it.
|
||||
* @see #setAspectBeanName
|
||||
*/
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -334,8 +334,8 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
}
|
||||
if (!CollectionUtils.isEmpty(advisors)) {
|
||||
for (Advisor advisor : advisors) {
|
||||
if (advisor instanceof IntroductionAdvisor introductionAdvisor) {
|
||||
validateIntroductionAdvisor(introductionAdvisor);
|
||||
if (advisor instanceof IntroductionAdvisor) {
|
||||
validateIntroductionAdvisor((IntroductionAdvisor) advisor);
|
||||
}
|
||||
Assert.notNull(advisor, "Advisor must not be null");
|
||||
this.advisors.add(advisor);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -86,7 +86,7 @@ public class ProxyCreatorSupport extends AdvisedSupport {
|
||||
|
||||
/**
|
||||
* Remove the given AdvisedSupportListener from this proxy configuration.
|
||||
* @param listener the listener to remove
|
||||
* @param listener the listener to deregister
|
||||
*/
|
||||
public void removeListener(AdvisedSupportListener listener) {
|
||||
Assert.notNull(listener, "AdvisedSupportListener must not be null");
|
||||
|
||||
+5
-2
@@ -145,11 +145,13 @@ class ScopedProxyBeanRegistrationAotProcessor implements BeanRegistrationAotProc
|
||||
|
||||
GeneratedMethod generatedMethod = beanRegistrationCode.getMethods()
|
||||
.add("getScopedProxyInstance", method -> {
|
||||
Class<?> beanClass = this.targetBeanDefinition.getResolvableType()
|
||||
.toClass();
|
||||
method.addJavadoc(
|
||||
"Create the scoped proxy bean instance for '$L'.",
|
||||
this.registeredBean.getBeanName());
|
||||
method.addModifiers(Modifier.PRIVATE, Modifier.STATIC);
|
||||
method.returns(ScopedProxyFactoryBean.class);
|
||||
method.returns(beanClass);
|
||||
method.addParameter(RegisteredBean.class,
|
||||
REGISTERED_BEAN_PARAMETER_NAME);
|
||||
method.addStatement("$T factory = new $T()",
|
||||
@@ -160,7 +162,8 @@ class ScopedProxyBeanRegistrationAotProcessor implements BeanRegistrationAotProc
|
||||
method.addStatement(
|
||||
"factory.setBeanFactory($L.getBeanFactory())",
|
||||
REGISTERED_BEAN_PARAMETER_NAME);
|
||||
method.addStatement("return factory");
|
||||
method.addStatement("return ($T) factory.getObject()",
|
||||
beanClass);
|
||||
});
|
||||
return CodeBlock.of("$T.of($L)", InstanceSupplier.class,
|
||||
generatedMethod.toMethodReference().toCodeBlock());
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -94,8 +94,8 @@ public abstract class MethodMatchers {
|
||||
*/
|
||||
public static boolean matches(MethodMatcher mm, Method method, Class<?> targetClass, boolean hasIntroductions) {
|
||||
Assert.notNull(mm, "MethodMatcher must not be null");
|
||||
return (mm instanceof IntroductionAwareMethodMatcher iamm ?
|
||||
iamm.matches(method, targetClass, hasIntroductions) :
|
||||
return (mm instanceof IntroductionAwareMethodMatcher ?
|
||||
((IntroductionAwareMethodMatcher) mm).matches(method, targetClass, hasIntroductions) :
|
||||
mm.matches(method, targetClass));
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -63,7 +63,7 @@ public class BeanFactoryRefreshableTargetSource extends AbstractRefreshableTarge
|
||||
*/
|
||||
@Override
|
||||
protected final Object freshTarget() {
|
||||
return obtainFreshBean(this.beanFactory, this.beanName);
|
||||
return this.obtainFreshBean(this.beanFactory, this.beanName);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+3
-3
@@ -195,7 +195,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
|
||||
Advised advised = (Advised) itb;
|
||||
// Will be ExposeInvocationInterceptor, synthetic instantiation advisor, 2 method advisors
|
||||
assertThat(advised.getAdvisors()).hasSize(4);
|
||||
assertThat(advised.getAdvisors().length).isEqualTo(4);
|
||||
ReflectiveAspectJAdvisorFactory.SyntheticInstantiationAdvisor sia =
|
||||
(ReflectiveAspectJAdvisorFactory.SyntheticInstantiationAdvisor) advised.getAdvisors()[1];
|
||||
assertThat(sia.getPointcut().getMethodMatcher().matches(TestBean.class.getMethod("getSpouse"), null)).isTrue();
|
||||
@@ -231,7 +231,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
|
||||
Advised advised = (Advised) itb;
|
||||
// Will be ExposeInvocationInterceptor, synthetic instantiation advisor, 2 method advisors
|
||||
assertThat(advised.getAdvisors()).hasSize(4);
|
||||
assertThat(advised.getAdvisors().length).isEqualTo(4);
|
||||
ReflectiveAspectJAdvisorFactory.SyntheticInstantiationAdvisor sia =
|
||||
(ReflectiveAspectJAdvisorFactory.SyntheticInstantiationAdvisor) advised.getAdvisors()[1];
|
||||
assertThat(sia.getPointcut().getMethodMatcher().matches(TestBean.class.getMethod("getSpouse"), null)).isTrue();
|
||||
@@ -366,7 +366,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
new SingletonMetadataAwareAspectInstanceFactory(new MakeLockable(), "someBean")),
|
||||
CannotBeUnlocked.class).isEmpty()).isTrue();
|
||||
assertThat(AopUtils.findAdvisorsThatCanApply(getFixture().getAdvisors(
|
||||
new SingletonMetadataAwareAspectInstanceFactory(new MakeLockable(),"someBean")), NotLockable.class)).hasSize(2);
|
||||
new SingletonMetadataAwareAspectInstanceFactory(new MakeLockable(),"someBean")), NotLockable.class).size()).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.aop.support;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.testfixture.beans.TestBean;
|
||||
import org.springframework.core.testfixture.io.SerializationTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Rod Johnson
|
||||
* @author Dmitriy Kopylenko
|
||||
* @author Chris Beams
|
||||
*/
|
||||
public abstract class AbstractRegexpMethodPointcutTests {
|
||||
|
||||
private AbstractRegexpMethodPointcut rpc;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
rpc = getRegexpMethodPointcut();
|
||||
}
|
||||
|
||||
protected abstract AbstractRegexpMethodPointcut getRegexpMethodPointcut();
|
||||
|
||||
@Test
|
||||
public void testNoPatternSupplied() throws Exception {
|
||||
noPatternSuppliedTests(rpc);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerializationWithNoPatternSupplied() throws Exception {
|
||||
rpc = SerializationTestUtils.serializeAndDeserialize(rpc);
|
||||
noPatternSuppliedTests(rpc);
|
||||
}
|
||||
|
||||
protected void noPatternSuppliedTests(AbstractRegexpMethodPointcut rpc) throws Exception {
|
||||
assertThat(rpc.matches(Object.class.getMethod("hashCode"), String.class)).isFalse();
|
||||
assertThat(rpc.matches(Object.class.getMethod("wait"), Object.class)).isFalse();
|
||||
assertThat(rpc.getPatterns().length).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExactMatch() throws Exception {
|
||||
rpc.setPattern("java.lang.Object.hashCode");
|
||||
exactMatchTests(rpc);
|
||||
rpc = SerializationTestUtils.serializeAndDeserialize(rpc);
|
||||
exactMatchTests(rpc);
|
||||
}
|
||||
|
||||
protected void exactMatchTests(AbstractRegexpMethodPointcut rpc) throws Exception {
|
||||
// assumes rpc.setPattern("java.lang.Object.hashCode");
|
||||
assertThat(rpc.matches(Object.class.getMethod("hashCode"), String.class)).isTrue();
|
||||
assertThat(rpc.matches(Object.class.getMethod("hashCode"), Object.class)).isTrue();
|
||||
assertThat(rpc.matches(Object.class.getMethod("wait"), Object.class)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpecificMatch() throws Exception {
|
||||
rpc.setPattern("java.lang.String.hashCode");
|
||||
assertThat(rpc.matches(Object.class.getMethod("hashCode"), String.class)).isTrue();
|
||||
assertThat(rpc.matches(Object.class.getMethod("hashCode"), Object.class)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWildcard() throws Exception {
|
||||
rpc.setPattern(".*Object.hashCode");
|
||||
assertThat(rpc.matches(Object.class.getMethod("hashCode"), Object.class)).isTrue();
|
||||
assertThat(rpc.matches(Object.class.getMethod("wait"), Object.class)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWildcardForOneClass() throws Exception {
|
||||
rpc.setPattern("java.lang.Object.*");
|
||||
assertThat(rpc.matches(Object.class.getMethod("hashCode"), String.class)).isTrue();
|
||||
assertThat(rpc.matches(Object.class.getMethod("wait"), String.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchesObjectClass() throws Exception {
|
||||
rpc.setPattern("java.lang.Object.*");
|
||||
assertThat(rpc.matches(Exception.class.getMethod("hashCode"), IOException.class)).isTrue();
|
||||
// Doesn't match a method from Throwable
|
||||
assertThat(rpc.matches(Exception.class.getMethod("getMessage"), Exception.class)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithExclusion() throws Exception {
|
||||
this.rpc.setPattern(".*get.*");
|
||||
this.rpc.setExcludedPattern(".*Age.*");
|
||||
assertThat(this.rpc.matches(TestBean.class.getMethod("getName"), TestBean.class)).isTrue();
|
||||
assertThat(this.rpc.matches(TestBean.class.getMethod("getAge"), TestBean.class)).isFalse();
|
||||
}
|
||||
|
||||
}
|
||||
+5
-84
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,93 +16,14 @@
|
||||
|
||||
package org.springframework.aop.support;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.testfixture.beans.TestBean;
|
||||
import org.springframework.core.testfixture.io.SerializationTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Rod Johnson
|
||||
* @author Dmitriy Kopylenko
|
||||
* @author Chris Beams
|
||||
* @author Dmitriy Kopylenko
|
||||
*/
|
||||
class JdkRegexpMethodPointcutTests {
|
||||
public class JdkRegexpMethodPointcutTests extends AbstractRegexpMethodPointcutTests {
|
||||
|
||||
private AbstractRegexpMethodPointcut rpc = new JdkRegexpMethodPointcut();
|
||||
|
||||
|
||||
@Test
|
||||
void noPatternSupplied() throws Exception {
|
||||
noPatternSuppliedTests(rpc);
|
||||
}
|
||||
|
||||
@Test
|
||||
void serializationWithNoPatternSupplied() throws Exception {
|
||||
rpc = SerializationTestUtils.serializeAndDeserialize(rpc);
|
||||
noPatternSuppliedTests(rpc);
|
||||
}
|
||||
|
||||
private void noPatternSuppliedTests(AbstractRegexpMethodPointcut rpc) throws Exception {
|
||||
assertThat(rpc.matches(Object.class.getMethod("hashCode"), String.class)).isFalse();
|
||||
assertThat(rpc.matches(Object.class.getMethod("wait"), Object.class)).isFalse();
|
||||
assertThat(rpc.getPatterns()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void exactMatch() throws Exception {
|
||||
rpc.setPattern("java.lang.Object.hashCode");
|
||||
exactMatchTests(rpc);
|
||||
rpc = SerializationTestUtils.serializeAndDeserialize(rpc);
|
||||
exactMatchTests(rpc);
|
||||
}
|
||||
|
||||
private void exactMatchTests(AbstractRegexpMethodPointcut rpc) throws Exception {
|
||||
// assumes rpc.setPattern("java.lang.Object.hashCode");
|
||||
assertThat(rpc.matches(Object.class.getMethod("hashCode"), String.class)).isTrue();
|
||||
assertThat(rpc.matches(Object.class.getMethod("hashCode"), Object.class)).isTrue();
|
||||
assertThat(rpc.matches(Object.class.getMethod("wait"), Object.class)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void specificMatch() throws Exception {
|
||||
rpc.setPattern("java.lang.String.hashCode");
|
||||
assertThat(rpc.matches(Object.class.getMethod("hashCode"), String.class)).isTrue();
|
||||
assertThat(rpc.matches(Object.class.getMethod("hashCode"), Object.class)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void wildcard() throws Exception {
|
||||
rpc.setPattern(".*Object.hashCode");
|
||||
assertThat(rpc.matches(Object.class.getMethod("hashCode"), Object.class)).isTrue();
|
||||
assertThat(rpc.matches(Object.class.getMethod("wait"), Object.class)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void wildcardForOneClass() throws Exception {
|
||||
rpc.setPattern("java.lang.Object.*");
|
||||
assertThat(rpc.matches(Object.class.getMethod("hashCode"), String.class)).isTrue();
|
||||
assertThat(rpc.matches(Object.class.getMethod("wait"), String.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesObjectClass() throws Exception {
|
||||
rpc.setPattern("java.lang.Object.*");
|
||||
assertThat(rpc.matches(Exception.class.getMethod("hashCode"), IOException.class)).isTrue();
|
||||
// Doesn't match a method from Throwable
|
||||
assertThat(rpc.matches(Exception.class.getMethod("getMessage"), Exception.class)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void withExclusion() throws Exception {
|
||||
this.rpc.setPattern(".*get.*");
|
||||
this.rpc.setExcludedPattern(".*Age.*");
|
||||
assertThat(this.rpc.matches(TestBean.class.getMethod("getName"), TestBean.class)).isTrue();
|
||||
assertThat(this.rpc.matches(TestBean.class.getMethod("getAge"), TestBean.class)).isFalse();
|
||||
@Override
|
||||
protected AbstractRegexpMethodPointcut getRegexpMethodPointcut() {
|
||||
return new JdkRegexpMethodPointcut();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -11,16 +11,10 @@ sourceSets.test.java.srcDirs = files()
|
||||
compileAspectj {
|
||||
sourceCompatibility "17"
|
||||
targetCompatibility "17"
|
||||
ajcOptions {
|
||||
compilerArgs += "-parameters"
|
||||
}
|
||||
}
|
||||
compileTestAspectj {
|
||||
sourceCompatibility "17"
|
||||
targetCompatibility "17"
|
||||
ajcOptions {
|
||||
compilerArgs += "-parameters"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -61,7 +61,7 @@ import org.springframework.util.StringUtils;
|
||||
* <p>Mainly for internal use within the framework, but to some degree also
|
||||
* useful for application classes. Consider
|
||||
* <a href="https://commons.apache.org/proper/commons-beanutils/">Apache Commons BeanUtils</a>,
|
||||
* <a href="https://github.com/ExpediaGroup/bull">BULL - Bean Utils Light Library</a>,
|
||||
* <a href="https://hotelsdotcom.github.io/bull/">BULL - Bean Utils Light Library</a>,
|
||||
* or similar third-party frameworks for more comprehensive bean utilities.
|
||||
*
|
||||
* @author Rod Johnson
|
||||
@@ -882,7 +882,7 @@ public abstract class BeanUtils {
|
||||
List<KParameter> parameters = kotlinConstructor.getParameters();
|
||||
|
||||
Assert.isTrue(args.length <= parameters.size(),
|
||||
"Number of provided arguments must be less than or equal to the number of constructor parameters");
|
||||
"Number of provided arguments should be less of equals than number of constructor parameters");
|
||||
if (parameters.isEmpty()) {
|
||||
return kotlinConstructor.call();
|
||||
}
|
||||
|
||||
+47
-22
@@ -18,6 +18,7 @@ package org.springframework.beans;
|
||||
|
||||
import java.beans.BeanInfo;
|
||||
import java.beans.IntrospectionException;
|
||||
import java.beans.Introspector;
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
@@ -35,6 +36,7 @@ import java.util.concurrent.ConcurrentMap;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.core.SpringProperties;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -58,15 +60,14 @@ import org.springframework.util.StringUtils;
|
||||
* <p>Note that for caching to work effectively, some preconditions need to be met:
|
||||
* Prefer an arrangement where the Spring jars live in the same ClassLoader as the
|
||||
* application classes, which allows for clean caching along with the application's
|
||||
* lifecycle in any case.
|
||||
* lifecycle in any case. For a web application, consider declaring a local
|
||||
* {@link org.springframework.web.util.IntrospectorCleanupListener} in {@code web.xml}
|
||||
* in case of a multi-ClassLoader layout, which will allow for effective caching as well.
|
||||
*
|
||||
* <p>As of 6.0, Spring's default introspection discovers basic JavaBeans properties
|
||||
* through an efficient method reflection pass. For full JavaBeans introspection
|
||||
* including indexed properties and all JDK-supported customizers, configure a
|
||||
* {@code META-INF/spring.factories} file with the following content:
|
||||
* {@code org.springframework.beans.BeanInfoFactory=org.springframework.beans.StandardBeanInfoFactory}
|
||||
* For Spring 5.3 compatible extended introspection including non-void setter methods:
|
||||
* {@code org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory}
|
||||
* <p>In case of a non-clean ClassLoader arrangement without a cleanup listener having
|
||||
* been set up, this class will fall back to a weak-reference-based caching model that
|
||||
* recreates much-requested entries every time the garbage collector removed them. In
|
||||
* such a scenario, consider the {@link #IGNORE_BEANINFO_PROPERTY_NAME} system property.
|
||||
*
|
||||
* @author Rod Johnson
|
||||
* @author Juergen Hoeller
|
||||
@@ -77,11 +78,35 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public final class CachedIntrospectionResults {
|
||||
|
||||
/**
|
||||
* System property that instructs Spring to use the {@link Introspector#IGNORE_ALL_BEANINFO}
|
||||
* mode when calling the JavaBeans {@link Introspector}: "spring.beaninfo.ignore", with a
|
||||
* value of "true" skipping the search for {@code BeanInfo} classes (typically for scenarios
|
||||
* where no such classes are being defined for beans in the application in the first place).
|
||||
* <p>The default is "false", considering all {@code BeanInfo} metadata classes, like for
|
||||
* standard {@link Introspector#getBeanInfo(Class)} calls. Consider switching this flag to
|
||||
* "true" if you experience repeated ClassLoader access for non-existing {@code BeanInfo}
|
||||
* classes, in case such access is expensive on startup or on lazy loading.
|
||||
* <p>Note that such an effect may also indicate a scenario where caching doesn't work
|
||||
* effectively: Prefer an arrangement where the Spring jars live in the same ClassLoader
|
||||
* as the application classes, which allows for clean caching along with the application's
|
||||
* lifecycle in any case. For a web application, consider declaring a local
|
||||
* {@link org.springframework.web.util.IntrospectorCleanupListener} in {@code web.xml}
|
||||
* in case of a multi-ClassLoader layout, which will allow for effective caching as well.
|
||||
* @see Introspector#getBeanInfo(Class, int)
|
||||
*/
|
||||
public static final String IGNORE_BEANINFO_PROPERTY_NAME = "spring.beaninfo.ignore";
|
||||
|
||||
private static final PropertyDescriptor[] EMPTY_PROPERTY_DESCRIPTOR_ARRAY = {};
|
||||
|
||||
|
||||
private static final boolean shouldIntrospectorIgnoreBeaninfoClasses =
|
||||
SpringProperties.getFlag(IGNORE_BEANINFO_PROPERTY_NAME);
|
||||
|
||||
/** Stores the BeanInfoFactory instances. */
|
||||
private static final List<BeanInfoFactory> beanInfoFactories = SpringFactoriesLoader.loadFactories(
|
||||
BeanInfoFactory.class, CachedIntrospectionResults.class.getClassLoader());
|
||||
|
||||
private static final SimpleBeanInfoFactory simpleBeanInfoFactory = new SimpleBeanInfoFactory();
|
||||
|
||||
private static final Log logger = LogFactory.getLog(CachedIntrospectionResults.class);
|
||||
|
||||
/**
|
||||
@@ -216,7 +241,7 @@ public final class CachedIntrospectionResults {
|
||||
* Retrieve a {@link BeanInfo} descriptor for the given target class.
|
||||
* @param beanClass the target class to introspect
|
||||
* @return the resulting {@code BeanInfo} descriptor (never {@code null})
|
||||
* @throws IntrospectionException from introspecting the given bean class
|
||||
* @throws IntrospectionException from the underlying {@link Introspector}
|
||||
*/
|
||||
private static BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionException {
|
||||
for (BeanInfoFactory beanInfoFactory : beanInfoFactories) {
|
||||
@@ -225,7 +250,9 @@ public final class CachedIntrospectionResults {
|
||||
return beanInfo;
|
||||
}
|
||||
}
|
||||
return simpleBeanInfoFactory.getBeanInfo(beanClass);
|
||||
return (shouldIntrospectorIgnoreBeaninfoClasses ?
|
||||
Introspector.getBeanInfo(beanClass, Introspector.IGNORE_ALL_BEANINFO) :
|
||||
Introspector.getBeanInfo(beanClass));
|
||||
}
|
||||
|
||||
|
||||
@@ -270,7 +297,7 @@ public final class CachedIntrospectionResults {
|
||||
// Only allow URL attribute introspection, not content resolution
|
||||
continue;
|
||||
}
|
||||
if (pd.getWriteMethod() == null && isInvalidReadOnlyPropertyType(pd.getPropertyType(), beanClass)) {
|
||||
if (pd.getWriteMethod() == null && isInvalidReadOnlyPropertyType(pd.getPropertyType())) {
|
||||
// Ignore read-only properties such as ClassLoader - no need to bind to those
|
||||
continue;
|
||||
}
|
||||
@@ -320,8 +347,7 @@ public final class CachedIntrospectionResults {
|
||||
// GenericTypeAwarePropertyDescriptor leniently resolves a set* write method
|
||||
// against a declared read method, so we prefer read method descriptors here.
|
||||
pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd);
|
||||
if (pd.getWriteMethod() == null &&
|
||||
isInvalidReadOnlyPropertyType(pd.getPropertyType(), beanClass)) {
|
||||
if (pd.getWriteMethod() == null && isInvalidReadOnlyPropertyType(pd.getPropertyType())) {
|
||||
// Ignore read-only properties such as ClassLoader - no need to bind to those
|
||||
continue;
|
||||
}
|
||||
@@ -354,7 +380,7 @@ public final class CachedIntrospectionResults {
|
||||
if (Modifier.isStatic(method.getModifiers()) ||
|
||||
method.getDeclaringClass() == Object.class || method.getDeclaringClass() == Class.class ||
|
||||
method.getParameterCount() > 0 || method.getReturnType() == void.class ||
|
||||
isInvalidReadOnlyPropertyType(method.getReturnType(), method.getDeclaringClass())) {
|
||||
isInvalidReadOnlyPropertyType(method.getReturnType())) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
@@ -367,11 +393,10 @@ public final class CachedIntrospectionResults {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isInvalidReadOnlyPropertyType(@Nullable Class<?> returnType, Class<?> beanClass) {
|
||||
return (returnType != null && (ClassLoader.class.isAssignableFrom(returnType) ||
|
||||
ProtectionDomain.class.isAssignableFrom(returnType) ||
|
||||
(AutoCloseable.class.isAssignableFrom(returnType) &&
|
||||
!AutoCloseable.class.isAssignableFrom(beanClass))));
|
||||
private boolean isInvalidReadOnlyPropertyType(@Nullable Class<?> returnType) {
|
||||
return (returnType != null && (AutoCloseable.class.isAssignableFrom(returnType) ||
|
||||
ClassLoader.class.isAssignableFrom(returnType) ||
|
||||
ProtectionDomain.class.isAssignableFrom(returnType)));
|
||||
}
|
||||
|
||||
|
||||
@@ -397,7 +422,7 @@ public final class CachedIntrospectionResults {
|
||||
}
|
||||
|
||||
PropertyDescriptor[] getPropertyDescriptors() {
|
||||
return this.propertyDescriptors.values().toArray(PropertyDescriptorUtils.EMPTY_PROPERTY_DESCRIPTOR_ARRAY);
|
||||
return this.propertyDescriptors.values().toArray(EMPTY_PROPERTY_DESCRIPTOR_ARRAY);
|
||||
}
|
||||
|
||||
private PropertyDescriptor buildGenericTypeAwarePropertyDescriptor(Class<?> beanClass, PropertyDescriptor pd) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,35 +18,34 @@ package org.springframework.beans;
|
||||
|
||||
import java.beans.BeanInfo;
|
||||
import java.beans.IntrospectionException;
|
||||
import java.beans.Introspector;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Extension of {@link StandardBeanInfoFactory} that supports "non-standard"
|
||||
* JavaBeans setter methods through introspection by Spring's
|
||||
* (package-visible) {@code ExtendedBeanInfo} implementation.
|
||||
*
|
||||
* <p>To be configured via a {@code META-INF/spring.factories} file with the following content:
|
||||
* {@code org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory}
|
||||
* {@link BeanInfoFactory} implementation that evaluates whether bean classes have
|
||||
* "non-standard" JavaBeans setter methods and are thus candidates for introspection
|
||||
* by Spring's (package-visible) {@code ExtendedBeanInfo} implementation.
|
||||
*
|
||||
* <p>Ordered at {@link Ordered#LOWEST_PRECEDENCE} to allow other user-defined
|
||||
* {@link BeanInfoFactory} types to take precedence.
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.2
|
||||
* @see StandardBeanInfoFactory
|
||||
* @see BeanInfoFactory
|
||||
* @see CachedIntrospectionResults
|
||||
*/
|
||||
public class ExtendedBeanInfoFactory extends StandardBeanInfoFactory {
|
||||
public class ExtendedBeanInfoFactory implements BeanInfoFactory, Ordered {
|
||||
|
||||
/**
|
||||
* Return an {@link ExtendedBeanInfo} for the given bean class, if applicable.
|
||||
*/
|
||||
@Override
|
||||
@NonNull
|
||||
@Nullable
|
||||
public BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionException {
|
||||
BeanInfo beanInfo = super.getBeanInfo(beanClass);
|
||||
return (supports(beanClass) ? new ExtendedBeanInfo(beanInfo) : beanInfo);
|
||||
return (supports(beanClass) ? new ExtendedBeanInfo(Introspector.getBeanInfo(beanClass)) : null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,4 +61,9 @@ public class ExtendedBeanInfoFactory extends StandardBeanInfoFactory {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.LOWEST_PRECEDENCE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -19,16 +19,10 @@ package org.springframework.beans;
|
||||
import java.beans.IntrospectionException;
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Common delegate methods for Spring's internal {@link PropertyDescriptor} implementations.
|
||||
@@ -38,80 +32,6 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
abstract class PropertyDescriptorUtils {
|
||||
|
||||
public static final PropertyDescriptor[] EMPTY_PROPERTY_DESCRIPTOR_ARRAY = {};
|
||||
|
||||
|
||||
/**
|
||||
* Simple introspection algorithm for basic set/get/is accessor methods,
|
||||
* building corresponding JavaBeans property descriptors for them.
|
||||
* <p>This just supports the basic JavaBeans conventions, without indexed
|
||||
* properties or any customizers, and without other BeanInfo metadata.
|
||||
* For standard JavaBeans introspection, use the JavaBeans Introspector.
|
||||
* @param beanClass the target class to introspect
|
||||
* @return a collection of property descriptors
|
||||
* @throws IntrospectionException from introspecting the given bean class
|
||||
* @since 5.3.24
|
||||
* @see SimpleBeanInfoFactory
|
||||
* @see java.beans.Introspector#getBeanInfo(Class)
|
||||
*/
|
||||
public static Collection<? extends PropertyDescriptor> determineBasicProperties(Class<?> beanClass)
|
||||
throws IntrospectionException {
|
||||
|
||||
Map<String, BasicPropertyDescriptor> pdMap = new TreeMap<>();
|
||||
|
||||
for (Method method : beanClass.getMethods()) {
|
||||
String methodName = method.getName();
|
||||
|
||||
boolean setter;
|
||||
int nameIndex;
|
||||
if (methodName.startsWith("set") && method.getParameterCount() == 1) {
|
||||
setter = true;
|
||||
nameIndex = 3;
|
||||
}
|
||||
else if (methodName.startsWith("get") && method.getParameterCount() == 0 && method.getReturnType() != Void.TYPE) {
|
||||
setter = false;
|
||||
nameIndex = 3;
|
||||
}
|
||||
else if (methodName.startsWith("is") && method.getParameterCount() == 0 && method.getReturnType() == boolean.class) {
|
||||
setter = false;
|
||||
nameIndex = 2;
|
||||
}
|
||||
else {
|
||||
continue;
|
||||
}
|
||||
|
||||
String propertyName = StringUtils.uncapitalizeAsProperty(methodName.substring(nameIndex));
|
||||
if (propertyName.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BasicPropertyDescriptor pd = pdMap.get(propertyName);
|
||||
if (pd != null) {
|
||||
if (setter) {
|
||||
if (pd.getWriteMethod() == null ||
|
||||
pd.getWriteMethod().getParameterTypes()[0].isAssignableFrom(method.getParameterTypes()[0])) {
|
||||
pd.setWriteMethod(method);
|
||||
}
|
||||
else {
|
||||
pd.addWriteMethod(method);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (pd.getReadMethod() == null ||
|
||||
(pd.getReadMethod().getReturnType() == method.getReturnType() && method.getName().startsWith("is"))) {
|
||||
pd.setReadMethod(method);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
pd = new BasicPropertyDescriptor(propertyName, (!setter ? method : null), (setter ? method : null));
|
||||
pdMap.put(propertyName, pd);
|
||||
}
|
||||
}
|
||||
|
||||
return pdMap.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link java.beans.FeatureDescriptor}.
|
||||
*/
|
||||
@@ -253,72 +173,4 @@ abstract class PropertyDescriptorUtils {
|
||||
pd.isBound() == otherPd.isBound() && pd.isConstrained() == otherPd.isConstrained());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* PropertyDescriptor for {@link #determineBasicProperties(Class)},
|
||||
* not performing any early type determination for
|
||||
* {@link #setReadMethod}/{@link #setWriteMethod}.
|
||||
* @since 5.3.24
|
||||
*/
|
||||
private static class BasicPropertyDescriptor extends PropertyDescriptor {
|
||||
|
||||
@Nullable
|
||||
private Method readMethod;
|
||||
|
||||
@Nullable
|
||||
private Method writeMethod;
|
||||
|
||||
private final List<Method> alternativeWriteMethods = new ArrayList<>();
|
||||
|
||||
public BasicPropertyDescriptor(String propertyName, @Nullable Method readMethod, @Nullable Method writeMethod)
|
||||
throws IntrospectionException {
|
||||
|
||||
super(propertyName, readMethod, writeMethod);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadMethod(@Nullable Method readMethod) {
|
||||
this.readMethod = readMethod;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Method getReadMethod() {
|
||||
return this.readMethod;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWriteMethod(@Nullable Method writeMethod) {
|
||||
this.writeMethod = writeMethod;
|
||||
}
|
||||
|
||||
public void addWriteMethod(Method writeMethod) {
|
||||
if (this.writeMethod != null) {
|
||||
this.alternativeWriteMethods.add(this.writeMethod);
|
||||
this.writeMethod = null;
|
||||
}
|
||||
this.alternativeWriteMethods.add(writeMethod);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Method getWriteMethod() {
|
||||
if (this.writeMethod == null && !this.alternativeWriteMethods.isEmpty()) {
|
||||
if (this.readMethod == null) {
|
||||
return this.alternativeWriteMethods.get(0);
|
||||
}
|
||||
else {
|
||||
for (Method method : this.alternativeWriteMethods) {
|
||||
if (this.readMethod.getReturnType().isAssignableFrom(method.getParameterTypes()[0])) {
|
||||
this.writeMethod = method;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.writeMethod;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.beans;
|
||||
|
||||
import java.beans.BeanDescriptor;
|
||||
import java.beans.BeanInfo;
|
||||
import java.beans.IntrospectionException;
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.beans.SimpleBeanInfo;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* {@link BeanInfoFactory} implementation that bypasses the standard {@link java.beans.Introspector}
|
||||
* for faster introspection, reduced to basic property determination (as commonly needed in Spring).
|
||||
*
|
||||
* <p>Used by default in 6.0 through direct invocation from {@link CachedIntrospectionResults}.
|
||||
* Potentially configured via a {@code META-INF/spring.factories} file with the following content,
|
||||
* overriding other custom {@code org.springframework.beans.BeanInfoFactory} declarations:
|
||||
* {@code org.springframework.beans.BeanInfoFactory=org.springframework.beans.SimpleBeanInfoFactory}
|
||||
*
|
||||
* <p>Ordered at {@code Ordered.LOWEST_PRECEDENCE - 1} to override {@link ExtendedBeanInfoFactory}
|
||||
* (registered by default in 5.3) if necessary while still allowing other user-defined
|
||||
* {@link BeanInfoFactory} types to take precedence.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 5.3.24
|
||||
* @see ExtendedBeanInfoFactory
|
||||
* @see CachedIntrospectionResults
|
||||
*/
|
||||
class SimpleBeanInfoFactory implements BeanInfoFactory, Ordered {
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionException {
|
||||
Collection<? extends PropertyDescriptor> pds =
|
||||
PropertyDescriptorUtils.determineBasicProperties(beanClass);
|
||||
|
||||
return new SimpleBeanInfo() {
|
||||
@Override
|
||||
public BeanDescriptor getBeanDescriptor() {
|
||||
return new BeanDescriptor(beanClass);
|
||||
}
|
||||
@Override
|
||||
public PropertyDescriptor[] getPropertyDescriptors() {
|
||||
return pds.toArray(PropertyDescriptorUtils.EMPTY_PROPERTY_DESCRIPTOR_ARRAY);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.LOWEST_PRECEDENCE - 1;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.beans;
|
||||
|
||||
import java.beans.BeanInfo;
|
||||
import java.beans.IntrospectionException;
|
||||
import java.beans.Introspector;
|
||||
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.SpringProperties;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* {@link BeanInfoFactory} implementation that performs standard
|
||||
* {@link java.beans.Introspector} inspection.
|
||||
*
|
||||
* <p>To be configured via a {@code META-INF/spring.factories} file with the following content:
|
||||
* {@code org.springframework.beans.BeanInfoFactory=org.springframework.beans.StandardBeanInfoFactory}
|
||||
*
|
||||
* <p>Ordered at {@link Ordered#LOWEST_PRECEDENCE} to allow other user-defined
|
||||
* {@link BeanInfoFactory} types to take precedence.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 6.0
|
||||
* @see ExtendedBeanInfoFactory
|
||||
* @see CachedIntrospectionResults
|
||||
* @see Introspector#getBeanInfo(Class)
|
||||
*/
|
||||
public class StandardBeanInfoFactory implements BeanInfoFactory, Ordered {
|
||||
|
||||
/**
|
||||
* System property that instructs Spring to use the {@link Introspector#IGNORE_ALL_BEANINFO}
|
||||
* mode when calling the JavaBeans {@link Introspector}: "spring.beaninfo.ignore", with a
|
||||
* value of "true" skipping the search for {@code BeanInfo} classes (typically for scenarios
|
||||
* where no such classes are being defined for beans in the application in the first place).
|
||||
* <p>The default is "false", considering all {@code BeanInfo} metadata classes, like for
|
||||
* standard {@link Introspector#getBeanInfo(Class)} calls. Consider switching this flag to
|
||||
* "true" if you experience repeated ClassLoader access for non-existing {@code BeanInfo}
|
||||
* classes, in case such access is expensive on startup or on lazy loading.
|
||||
* <p>Note that such an effect may also indicate a scenario where caching doesn't work
|
||||
* effectively: Prefer an arrangement where the Spring jars live in the same ClassLoader
|
||||
* as the application classes, which allows for clean caching along with the application's
|
||||
* lifecycle in any case. For a web application, consider declaring a local
|
||||
* {@link org.springframework.web.util.IntrospectorCleanupListener} in {@code web.xml}
|
||||
* in case of a multi-ClassLoader layout, which will allow for effective caching as well.
|
||||
* @see Introspector#getBeanInfo(Class, int)
|
||||
*/
|
||||
public static final String IGNORE_BEANINFO_PROPERTY_NAME = "spring.beaninfo.ignore";
|
||||
|
||||
private static final boolean shouldIntrospectorIgnoreBeaninfoClasses =
|
||||
SpringProperties.getFlag(IGNORE_BEANINFO_PROPERTY_NAME);
|
||||
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionException {
|
||||
return (shouldIntrospectorIgnoreBeaninfoClasses ?
|
||||
Introspector.getBeanInfo(beanClass, Introspector.IGNORE_ALL_BEANINFO) :
|
||||
Introspector.getBeanInfo(beanClass));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.LOWEST_PRECEDENCE;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -112,7 +112,7 @@ public class InjectionPoint {
|
||||
* @since 5.0
|
||||
*/
|
||||
protected final MethodParameter obtainMethodParameter() {
|
||||
Assert.state(this.methodParameter != null, "MethodParameter is not available");
|
||||
Assert.state(this.methodParameter != null, "Neither Field nor MethodParameter");
|
||||
return this.methodParameter;
|
||||
}
|
||||
|
||||
|
||||
+7
-35
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,7 +18,6 @@ package org.springframework.beans.factory;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.core.ResolvableType;
|
||||
@@ -323,8 +322,7 @@ public interface ListableBeanFactory extends BeanFactory {
|
||||
* (at class, interface or factory method level of the specified bean)
|
||||
* @return the names of all matching beans
|
||||
* @since 4.0
|
||||
* @see #getBeansWithAnnotation(Class)
|
||||
* @see #findAnnotationOnBean(String, Class)
|
||||
* @see #findAnnotationOnBean
|
||||
*/
|
||||
String[] getBeanNamesForAnnotation(Class<? extends Annotation> annotationType);
|
||||
|
||||
@@ -339,9 +337,7 @@ public interface ListableBeanFactory extends BeanFactory {
|
||||
* keys and the corresponding bean instances as values
|
||||
* @throws BeansException if a bean could not be created
|
||||
* @since 3.0
|
||||
* @see #findAnnotationOnBean(String, Class)
|
||||
* @see #findAnnotationOnBean(String, Class, boolean)
|
||||
* @see #findAllAnnotationsOnBean(String, Class, boolean)
|
||||
* @see #findAnnotationOnBean
|
||||
*/
|
||||
Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType) throws BeansException;
|
||||
|
||||
@@ -355,10 +351,8 @@ public interface ListableBeanFactory extends BeanFactory {
|
||||
* @return the annotation of the given type if found, or {@code null} otherwise
|
||||
* @throws NoSuchBeanDefinitionException if there is no bean with the given name
|
||||
* @since 3.0
|
||||
* @see #findAnnotationOnBean(String, Class, boolean)
|
||||
* @see #findAllAnnotationsOnBean(String, Class, boolean)
|
||||
* @see #getBeanNamesForAnnotation(Class)
|
||||
* @see #getBeansWithAnnotation(Class)
|
||||
* @see #getBeanNamesForAnnotation
|
||||
* @see #getBeansWithAnnotation
|
||||
* @see #getType(String)
|
||||
*/
|
||||
@Nullable
|
||||
@@ -377,10 +371,8 @@ public interface ListableBeanFactory extends BeanFactory {
|
||||
* @return the annotation of the given type if found, or {@code null} otherwise
|
||||
* @throws NoSuchBeanDefinitionException if there is no bean with the given name
|
||||
* @since 5.3.14
|
||||
* @see #findAnnotationOnBean(String, Class)
|
||||
* @see #findAllAnnotationsOnBean(String, Class, boolean)
|
||||
* @see #getBeanNamesForAnnotation(Class)
|
||||
* @see #getBeansWithAnnotation(Class)
|
||||
* @see #getBeanNamesForAnnotation
|
||||
* @see #getBeansWithAnnotation
|
||||
* @see #getType(String, boolean)
|
||||
*/
|
||||
@Nullable
|
||||
@@ -388,24 +380,4 @@ public interface ListableBeanFactory extends BeanFactory {
|
||||
String beanName, Class<A> annotationType, boolean allowFactoryBeanInit)
|
||||
throws NoSuchBeanDefinitionException;
|
||||
|
||||
/**
|
||||
* Find all {@link Annotation} instances of {@code annotationType} on the specified
|
||||
* bean, traversing its interfaces and superclasses if no annotation can be found on
|
||||
* the given class itself, as well as checking the bean's factory method (if any).
|
||||
* @param beanName the name of the bean to look for annotations on
|
||||
* @param annotationType the type of annotation to look for
|
||||
* (at class, interface or factory method level of the specified bean)
|
||||
* @param allowFactoryBeanInit whether a {@code FactoryBean} may get initialized
|
||||
* just for the purpose of determining its object type
|
||||
* @return the set of annotations of the given type found (potentially empty)
|
||||
* @throws NoSuchBeanDefinitionException if there is no bean with the given name
|
||||
* @since 6.0
|
||||
* @see #getBeanNamesForAnnotation(Class)
|
||||
* @see #findAnnotationOnBean(String, Class, boolean)
|
||||
* @see #getType(String, boolean)
|
||||
*/
|
||||
<A extends Annotation> Set<A> findAllAnnotationsOnBean(
|
||||
String beanName, Class<A> annotationType, boolean allowFactoryBeanInit)
|
||||
throws NoSuchBeanDefinitionException;
|
||||
|
||||
}
|
||||
|
||||
+50
-14
@@ -25,6 +25,7 @@ import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
@@ -45,7 +46,6 @@ import org.springframework.aot.generate.GeneratedMethod;
|
||||
import org.springframework.aot.generate.GenerationContext;
|
||||
import org.springframework.aot.hint.ExecutableMode;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.support.ClassHintUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.PropertyValues;
|
||||
@@ -53,6 +53,7 @@ import org.springframework.beans.TypeConverter;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.InjectionPoint;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.UnsatisfiedDependencyException;
|
||||
@@ -76,6 +77,7 @@ import org.springframework.core.BridgeMethodResolver;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.PriorityOrdered;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.annotation.MergedAnnotation;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
@@ -611,10 +613,39 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
* @return whether the annotation indicates that a dependency is required
|
||||
*/
|
||||
protected boolean determineRequiredStatus(MergedAnnotation<?> ann) {
|
||||
return (ann.getValue(this.requiredParameterName).isEmpty() ||
|
||||
return determineRequiredStatus(ann.<AnnotationAttributes> asMap(
|
||||
mergedAnnotation -> new AnnotationAttributes(mergedAnnotation.getType())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the annotated field or method requires its dependency.
|
||||
* <p>A 'required' dependency means that autowiring should fail when no beans
|
||||
* are found. Otherwise, the autowiring process will simply bypass the field
|
||||
* or method when no beans are found.
|
||||
* @param ann the Autowired annotation
|
||||
* @return whether the annotation indicates that a dependency is required
|
||||
* @deprecated since 5.2, in favor of {@link #determineRequiredStatus(MergedAnnotation)}
|
||||
*/
|
||||
@Deprecated
|
||||
protected boolean determineRequiredStatus(AnnotationAttributes ann) {
|
||||
return (!ann.containsKey(this.requiredParameterName) ||
|
||||
this.requiredParameterValue == ann.getBoolean(this.requiredParameterName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain all beans of the given type as autowire candidates.
|
||||
* @param type the type of the bean
|
||||
* @return the target beans, or an empty Collection if no bean of this type is found
|
||||
* @throws BeansException if bean retrieval failed
|
||||
*/
|
||||
protected <T> Map<String, T> findAutowireCandidates(Class<T> type) throws BeansException {
|
||||
if (this.beanFactory == null) {
|
||||
throw new IllegalStateException("No BeanFactory configured - " +
|
||||
"override the getBeanOfType method or specify the 'beanFactory' property");
|
||||
}
|
||||
return BeanFactoryUtils.beansOfTypeIncludingAncestors(this.beanFactory, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the specified bean as dependent on the autowired beans.
|
||||
*/
|
||||
@@ -654,10 +685,11 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
|
||||
protected final boolean required;
|
||||
|
||||
protected AutowiredElement(Member member, @Nullable PropertyDescriptor pd, boolean required) {
|
||||
protected AutowiredElement(Member member, PropertyDescriptor pd, boolean required) {
|
||||
super(member, pd);
|
||||
this.required = required;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -894,8 +926,10 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
this.candidateResolver = candidateResolver;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void applyTo(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) {
|
||||
public void applyTo(GenerationContext generationContext,
|
||||
BeanRegistrationCode beanRegistrationCode) {
|
||||
GeneratedClass generatedClass = generationContext.getGeneratedClasses()
|
||||
.addForFeatureComponent("Autowiring", this.target, type -> {
|
||||
type.addJavadoc("Autowiring for {@link $T}.", this.target);
|
||||
@@ -969,13 +1003,15 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
(!required) ? "forMethod" : "forRequiredMethod");
|
||||
code.add("($S", method.getName());
|
||||
if (method.getParameterCount() > 0) {
|
||||
code.add(", $L", generateParameterTypesCode(method.getParameterTypes()));
|
||||
code.add(", $L",
|
||||
generateParameterTypesCode(method.getParameterTypes()));
|
||||
}
|
||||
code.add(")");
|
||||
AccessControl accessControl = AccessControl.forMember(method);
|
||||
if (!accessControl.isAccessibleFrom(targetClassName)) {
|
||||
hints.reflection().registerMethod(method, ExecutableMode.INVOKE);
|
||||
code.add(".resolveAndInvoke($L, $L)", REGISTERED_BEAN_PARAMETER, INSTANCE_PARAMETER);
|
||||
code.add(".resolveAndInvoke($L, $L)", REGISTERED_BEAN_PARAMETER,
|
||||
INSTANCE_PARAMETER);
|
||||
}
|
||||
else {
|
||||
hints.reflection().registerMethod(method, ExecutableMode.INTROSPECT);
|
||||
@@ -1002,14 +1038,16 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
boolean required = autowiredElement.required;
|
||||
Member member = autowiredElement.getMember();
|
||||
if (member instanceof Field field) {
|
||||
DependencyDescriptor dependencyDescriptor = new DependencyDescriptor(field, required);
|
||||
DependencyDescriptor dependencyDescriptor = new DependencyDescriptor(
|
||||
field, required);
|
||||
registerProxyIfNecessary(runtimeHints, dependencyDescriptor);
|
||||
}
|
||||
if (member instanceof Method method) {
|
||||
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||
for (int i = 0; i < parameterTypes.length; i++) {
|
||||
MethodParameter methodParam = new MethodParameter(method, i);
|
||||
DependencyDescriptor dependencyDescriptor = new DependencyDescriptor(methodParam, required);
|
||||
DependencyDescriptor dependencyDescriptor = new DependencyDescriptor(
|
||||
methodParam, required);
|
||||
registerProxyIfNecessary(runtimeHints, dependencyDescriptor);
|
||||
}
|
||||
}
|
||||
@@ -1017,12 +1055,10 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
}
|
||||
|
||||
private void registerProxyIfNecessary(RuntimeHints runtimeHints, DependencyDescriptor dependencyDescriptor) {
|
||||
if (this.candidateResolver != null) {
|
||||
Class<?> proxyClass =
|
||||
this.candidateResolver.getLazyResolutionProxyClass(dependencyDescriptor, null);
|
||||
if (proxyClass != null) {
|
||||
ClassHintUtils.registerProxyIfNecessary(proxyClass, runtimeHints);
|
||||
}
|
||||
Class<?> proxyType = this.candidateResolver
|
||||
.getLazyResolutionProxyClass(dependencyDescriptor, null);
|
||||
if (proxyType != null && Proxy.isProxyClass(proxyType)) {
|
||||
runtimeHints.proxies().registerJdkProxy(proxyType.getInterfaces());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -179,7 +179,7 @@ public final class AotServices<T> implements Iterable<T> {
|
||||
public Source getSource(T service) {
|
||||
Source source = this.sources.get(service);
|
||||
Assert.state(source != null,
|
||||
() -> "Unable to find service " + ObjectUtils.identityToString(source));
|
||||
"Unable to find service " + ObjectUtils.identityToString(source));
|
||||
return source;
|
||||
}
|
||||
|
||||
|
||||
+8
-5
@@ -28,11 +28,14 @@ import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Code generator to apply {@link AutowiredArguments}.
|
||||
*
|
||||
* <p>Generates code in the form: {@code args.get(0), args.get(1)} or
|
||||
* {@code args.get(0, String.class), args.get(1, Integer.class)}
|
||||
*
|
||||
* <p>The simpler form is only used if the target method or constructor is
|
||||
* <p>
|
||||
* Generates code in the form:<pre class="code">{@code
|
||||
* args.get(0), args.get(1)
|
||||
* }</pre> or <pre class="code">{@code
|
||||
* args.get(0, String.class), args.get(1, Integer.class)
|
||||
* }</pre>
|
||||
* <p>
|
||||
* The simpler form is only used if the target method or constructor is
|
||||
* unambiguous.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
|
||||
+2
-2
@@ -39,8 +39,8 @@ import org.springframework.util.function.ThrowingConsumer;
|
||||
* AOT-processed applications as a targeted alternative to the
|
||||
* {@link org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
|
||||
* AutowiredAnnotationBeanPostProcessor}.
|
||||
*
|
||||
* <p>When resolving arguments in a native image, the {@link Field} being used must
|
||||
* <p>
|
||||
* When resolving arguments in a native image, the {@link Field} being used must
|
||||
* be marked with an {@link ExecutableMode#INTROSPECT introspection} hint so
|
||||
* that field annotations can be read. Full {@link ExecutableMode#INVOKE
|
||||
* invocation} hints are only required if the
|
||||
|
||||
+5
-4
@@ -42,8 +42,8 @@ import org.springframework.util.function.ThrowingConsumer;
|
||||
* AOT-processed applications as a targeted alternative to the
|
||||
* {@link org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
|
||||
* AutowiredAnnotationBeanPostProcessor}.
|
||||
*
|
||||
* <p>When resolving arguments in a native image, the {@link Method} being used
|
||||
* <p>
|
||||
* When resolving arguments in a native image, the {@link Method} being used
|
||||
* must be marked with an {@link ExecutableMode#INTROSPECT introspection} hint
|
||||
* so that field annotations can be read. Full {@link ExecutableMode#INVOKE
|
||||
* invocation} hints are only required if the
|
||||
@@ -205,8 +205,9 @@ public final class AutowiredMethodArgumentsResolver extends AutowiredElementReso
|
||||
private Method getMethod(RegisteredBean registeredBean) {
|
||||
Method method = ReflectionUtils.findMethod(registeredBean.getBeanClass(),
|
||||
this.methodName, this.parameterTypes);
|
||||
Assert.notNull(method, () ->
|
||||
"Method '%s' with parameter types [%s] declared on %s could not be found.".formatted(
|
||||
Assert.notNull(method,
|
||||
() -> String.format(
|
||||
"Method '%s' with parameter types [%s] declared on %s",
|
||||
this.methodName, toCommaSeparatedNames(this.parameterTypes),
|
||||
registeredBean.getBeanClass().getName()));
|
||||
return method;
|
||||
|
||||
+14
-15
@@ -57,7 +57,7 @@ class BeanDefinitionMethodGenerator {
|
||||
private final Executable constructorOrFactoryMethod;
|
||||
|
||||
@Nullable
|
||||
private final String currentPropertyName;
|
||||
private final String innerBeanPropertyName;
|
||||
|
||||
private final List<BeanRegistrationAotContribution> aotContributions;
|
||||
|
||||
@@ -66,18 +66,18 @@ class BeanDefinitionMethodGenerator {
|
||||
* Create a new {@link BeanDefinitionMethodGenerator} instance.
|
||||
* @param methodGeneratorFactory the method generator factory
|
||||
* @param registeredBean the registered bean
|
||||
* @param currentPropertyName the current property name
|
||||
* @param innerBeanPropertyName the inner bean property name
|
||||
* @param aotContributions the AOT contributions
|
||||
*/
|
||||
BeanDefinitionMethodGenerator(
|
||||
BeanDefinitionMethodGeneratorFactory methodGeneratorFactory,
|
||||
RegisteredBean registeredBean, @Nullable String currentPropertyName,
|
||||
RegisteredBean registeredBean, @Nullable String innerBeanPropertyName,
|
||||
List<BeanRegistrationAotContribution> aotContributions) {
|
||||
|
||||
this.methodGeneratorFactory = methodGeneratorFactory;
|
||||
this.registeredBean = registeredBean;
|
||||
this.constructorOrFactoryMethod = registeredBean.resolveConstructorOrFactoryMethod();
|
||||
this.currentPropertyName = currentPropertyName;
|
||||
this.innerBeanPropertyName = innerBeanPropertyName;
|
||||
this.aotContributions = aotContributions;
|
||||
}
|
||||
|
||||
@@ -134,12 +134,10 @@ class BeanDefinitionMethodGenerator {
|
||||
type.addJavadoc("Bean definitions for {@link $T}", topLevelClassName);
|
||||
type.addModifiers(Modifier.PUBLIC);
|
||||
});
|
||||
|
||||
List<String> names = target.simpleNames();
|
||||
if (names.size() == 1) {
|
||||
return generatedClass;
|
||||
}
|
||||
|
||||
List<String> namesToProcess = names.subList(1, names.size());
|
||||
ClassName currentTargetClassName = topLevelClassName;
|
||||
GeneratedClass tmp = generatedClass;
|
||||
@@ -150,7 +148,8 @@ class BeanDefinitionMethodGenerator {
|
||||
return tmp;
|
||||
}
|
||||
|
||||
private static GeneratedClass createInnerClass(GeneratedClass generatedClass, String name, ClassName target) {
|
||||
private static GeneratedClass createInnerClass(GeneratedClass generatedClass,
|
||||
String name, ClassName target) {
|
||||
return generatedClass.getOrAdd(name, type -> {
|
||||
type.addJavadoc("Bean definitions for {@link $T}", target);
|
||||
type.addModifiers(Modifier.PUBLIC, Modifier.STATIC);
|
||||
@@ -168,16 +167,16 @@ class BeanDefinitionMethodGenerator {
|
||||
return codeFragments;
|
||||
}
|
||||
|
||||
private GeneratedMethod generateBeanDefinitionMethod(GenerationContext generationContext,
|
||||
ClassName className, GeneratedMethods generatedMethods,
|
||||
BeanRegistrationCodeFragments codeFragments, Modifier modifier) {
|
||||
private GeneratedMethod generateBeanDefinitionMethod(
|
||||
GenerationContext generationContext, ClassName className,
|
||||
GeneratedMethods generatedMethods, BeanRegistrationCodeFragments codeFragments,
|
||||
Modifier modifier) {
|
||||
|
||||
BeanRegistrationCodeGenerator codeGenerator = new BeanRegistrationCodeGenerator(
|
||||
className, generatedMethods, this.registeredBean,
|
||||
this.constructorOrFactoryMethod, codeFragments);
|
||||
|
||||
this.aotContributions.forEach(aotContribution -> aotContribution.applyTo(generationContext, codeGenerator));
|
||||
|
||||
this.aotContributions.forEach(aotContribution -> aotContribution
|
||||
.applyTo(generationContext, codeGenerator));
|
||||
return generatedMethods.add("getBeanDefinition", method -> {
|
||||
method.addJavadoc("Get the $L definition for '$L'",
|
||||
(!this.registeredBean.isInnerBean()) ? "bean" : "inner-bean",
|
||||
@@ -189,8 +188,8 @@ class BeanDefinitionMethodGenerator {
|
||||
}
|
||||
|
||||
private String getName() {
|
||||
if (this.currentPropertyName != null) {
|
||||
return this.currentPropertyName;
|
||||
if (this.innerBeanPropertyName != null) {
|
||||
return this.innerBeanPropertyName;
|
||||
}
|
||||
if (!this.registeredBean.isGeneratedBeanName()) {
|
||||
return getSimpleBeanName(this.registeredBean.getBeanName());
|
||||
|
||||
+20
-31
@@ -35,10 +35,9 @@ import org.springframework.util.ObjectUtils;
|
||||
* {@link RegisteredBean}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
* @see BeanDefinitionMethodGenerator
|
||||
* @see #getBeanDefinitionMethodGenerator(RegisteredBean)
|
||||
* @see #getBeanDefinitionMethodGenerator(RegisteredBean, String)
|
||||
*/
|
||||
class BeanDefinitionMethodGeneratorFactory {
|
||||
|
||||
@@ -78,29 +77,6 @@ class BeanDefinitionMethodGeneratorFactory {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a {@link BeanDefinitionMethodGenerator} for the given
|
||||
* {@link RegisteredBean} defined with the specified property name, or
|
||||
* {@code null} if the registered bean is excluded by a
|
||||
* {@link BeanRegistrationExcludeFilter}. The resulting
|
||||
* {@link BeanDefinitionMethodGenerator} will include all
|
||||
* {@link BeanRegistrationAotProcessor} provided contributions.
|
||||
* @param registeredBean the registered bean
|
||||
* @param currentPropertyName the property name that this bean belongs to
|
||||
* @return a new {@link BeanDefinitionMethodGenerator} instance or {@code null}
|
||||
*/
|
||||
@Nullable
|
||||
BeanDefinitionMethodGenerator getBeanDefinitionMethodGenerator(
|
||||
RegisteredBean registeredBean, @Nullable String currentPropertyName) {
|
||||
|
||||
if (isExcluded(registeredBean)) {
|
||||
return null;
|
||||
}
|
||||
List<BeanRegistrationAotContribution> contributions = getAotContributions(registeredBean);
|
||||
return new BeanDefinitionMethodGenerator(this, registeredBean,
|
||||
currentPropertyName, contributions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link BeanDefinitionMethodGenerator} for the given
|
||||
* {@link RegisteredBean} or {@code null} if the registered bean is excluded
|
||||
@@ -108,11 +84,21 @@ class BeanDefinitionMethodGeneratorFactory {
|
||||
* {@link BeanDefinitionMethodGenerator} will include all
|
||||
* {@link BeanRegistrationAotProcessor} provided contributions.
|
||||
* @param registeredBean the registered bean
|
||||
* @return a new {@link BeanDefinitionMethodGenerator} instance or {@code null}
|
||||
* @param innerBeanPropertyName the inner bean property name or {@code null}
|
||||
* @return a new {@link BeanDefinitionMethodGenerator} instance or
|
||||
* {@code null}
|
||||
*/
|
||||
@Nullable
|
||||
BeanDefinitionMethodGenerator getBeanDefinitionMethodGenerator(RegisteredBean registeredBean) {
|
||||
return getBeanDefinitionMethodGenerator(registeredBean, null);
|
||||
BeanDefinitionMethodGenerator getBeanDefinitionMethodGenerator(
|
||||
RegisteredBean registeredBean, @Nullable String innerBeanPropertyName) {
|
||||
|
||||
if (isExcluded(registeredBean)) {
|
||||
return null;
|
||||
}
|
||||
List<BeanRegistrationAotContribution> contributions = getAotContributions(
|
||||
registeredBean);
|
||||
return new BeanDefinitionMethodGenerator(this, registeredBean,
|
||||
innerBeanPropertyName, contributions);
|
||||
}
|
||||
|
||||
private boolean isExcluded(RegisteredBean registeredBean) {
|
||||
@@ -139,16 +125,19 @@ class BeanDefinitionMethodGeneratorFactory {
|
||||
}
|
||||
if (BeanRegistrationAotProcessor.class.isAssignableFrom(beanClass)) {
|
||||
BeanRegistrationAotProcessor processor = this.aotProcessors.findByBeanName(registeredBean.getBeanName());
|
||||
return (processor == null || processor.isBeanExcludedFromAotProcessing());
|
||||
return (processor == null) || processor.isBeanExcludedFromAotProcessing();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<BeanRegistrationAotContribution> getAotContributions(RegisteredBean registeredBean) {
|
||||
private List<BeanRegistrationAotContribution> getAotContributions(
|
||||
RegisteredBean registeredBean) {
|
||||
|
||||
String beanName = registeredBean.getBeanName();
|
||||
List<BeanRegistrationAotContribution> contributions = new ArrayList<>();
|
||||
for (BeanRegistrationAotProcessor aotProcessor : this.aotProcessors) {
|
||||
BeanRegistrationAotContribution contribution = aotProcessor.processAheadOfTime(registeredBean);
|
||||
BeanRegistrationAotContribution contribution = aotProcessor
|
||||
.processAheadOfTime(registeredBean);
|
||||
if (contribution != null) {
|
||||
logger.trace(LogMessage.format(
|
||||
"Adding bean registration AOT contribution %S from %S to '%S'",
|
||||
|
||||
+44
-42
@@ -16,9 +16,11 @@
|
||||
|
||||
package org.springframework.beans.factory.aot;
|
||||
|
||||
import java.beans.BeanInfo;
|
||||
import java.beans.IntrospectionException;
|
||||
import java.beans.Introspector;
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
@@ -32,7 +34,8 @@ import java.util.function.Predicate;
|
||||
import org.springframework.aot.generate.GeneratedMethods;
|
||||
import org.springframework.aot.hint.ExecutableMode;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.BeanInfoFactory;
|
||||
import org.springframework.beans.ExtendedBeanInfoFactory;
|
||||
import org.springframework.beans.MutablePropertyValues;
|
||||
import org.springframework.beans.PropertyValue;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
@@ -78,10 +81,14 @@ class BeanDefinitionPropertiesCodeGenerator {
|
||||
|
||||
private static final String BEAN_DEFINITION_VARIABLE = BeanRegistrationCodeFragments.BEAN_DEFINITION_VARIABLE;
|
||||
|
||||
private static final BeanInfoFactory beanInfoFactory = new ExtendedBeanInfoFactory();
|
||||
|
||||
private final RuntimeHints hints;
|
||||
|
||||
private final Predicate<String> attributeFilter;
|
||||
|
||||
private final BiFunction<String, Object, CodeBlock> customValueCodeGenerator;
|
||||
|
||||
private final BeanDefinitionPropertyValueCodeGenerator valueCodeGenerator;
|
||||
|
||||
|
||||
@@ -91,8 +98,9 @@ class BeanDefinitionPropertiesCodeGenerator {
|
||||
|
||||
this.hints = hints;
|
||||
this.attributeFilter = attributeFilter;
|
||||
this.valueCodeGenerator = new BeanDefinitionPropertyValueCodeGenerator(generatedMethods,
|
||||
(object, type) -> customValueCodeGenerator.apply(PropertyNamesStack.peek(), object));
|
||||
this.customValueCodeGenerator = customValueCodeGenerator;
|
||||
this.valueCodeGenerator = new BeanDefinitionPropertyValueCodeGenerator(
|
||||
generatedMethods);
|
||||
}
|
||||
|
||||
|
||||
@@ -148,7 +156,12 @@ class BeanDefinitionPropertiesCodeGenerator {
|
||||
.getConstructorArgumentValues().getIndexedArgumentValues();
|
||||
if (!argumentValues.isEmpty()) {
|
||||
argumentValues.forEach((index, valueHolder) -> {
|
||||
CodeBlock valueCode = generateValue(valueHolder.getName(), valueHolder.getValue());
|
||||
String name = valueHolder.getName();
|
||||
Object value = valueHolder.getValue();
|
||||
CodeBlock valueCode = this.customValueCodeGenerator.apply(name, value);
|
||||
if (valueCode == null) {
|
||||
valueCode = this.valueCodeGenerator.generateCode(value);
|
||||
}
|
||||
code.addStatement(
|
||||
"$L.getConstructorArgumentValues().addIndexedArgumentValue($L, $L)",
|
||||
BEAN_DEFINITION_VARIABLE, index, valueCode);
|
||||
@@ -163,13 +176,18 @@ class BeanDefinitionPropertiesCodeGenerator {
|
||||
if (!propertyValues.isEmpty()) {
|
||||
for (PropertyValue propertyValue : propertyValues) {
|
||||
String name = propertyValue.getName();
|
||||
CodeBlock valueCode = generateValue(name, propertyValue.getValue());
|
||||
Object value = propertyValue.getValue();
|
||||
CodeBlock valueCode = this.customValueCodeGenerator.apply(name, value);
|
||||
if (valueCode == null) {
|
||||
valueCode = this.valueCodeGenerator.generateCode(value);
|
||||
}
|
||||
code.addStatement("$L.getPropertyValues().addPropertyValue($S, $L)",
|
||||
BEAN_DEFINITION_VARIABLE, propertyValue.getName(), valueCode);
|
||||
}
|
||||
Class<?> infrastructureType = getInfrastructureType(beanDefinition);
|
||||
if (infrastructureType != Object.class) {
|
||||
Map<String, Method> writeMethods = getWriteMethods(infrastructureType);
|
||||
BeanInfo beanInfo = (infrastructureType != Object.class) ? getBeanInfo(infrastructureType) : null;
|
||||
if (beanInfo != null) {
|
||||
Map<String, Method> writeMethods = getWriteMethods(beanInfo);
|
||||
for (PropertyValue propertyValue : propertyValues) {
|
||||
Method writeMethod = writeMethods.get(propertyValue.getName());
|
||||
if (writeMethod != null) {
|
||||
@@ -180,16 +198,6 @@ class BeanDefinitionPropertiesCodeGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
private CodeBlock generateValue(@Nullable String name, @Nullable Object value) {
|
||||
try {
|
||||
PropertyNamesStack.push(name);
|
||||
return this.valueCodeGenerator.generateCode(value);
|
||||
}
|
||||
finally {
|
||||
PropertyNamesStack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
private Class<?> getInfrastructureType(RootBeanDefinition beanDefinition) {
|
||||
if (beanDefinition.hasBeanClass()) {
|
||||
Class<?> beanClass = beanDefinition.getBeanClass();
|
||||
@@ -200,10 +208,25 @@ class BeanDefinitionPropertiesCodeGenerator {
|
||||
return ClassUtils.getUserClass(beanDefinition.getResolvableType().toClass());
|
||||
}
|
||||
|
||||
private Map<String, Method> getWriteMethods(Class<?> clazz) {
|
||||
@Nullable
|
||||
private BeanInfo getBeanInfo(Class<?> beanType) {
|
||||
try {
|
||||
BeanInfo beanInfo = beanInfoFactory.getBeanInfo(beanType);
|
||||
if (beanInfo != null) {
|
||||
return beanInfo;
|
||||
}
|
||||
return Introspector.getBeanInfo(beanType, Introspector.IGNORE_ALL_BEANINFO);
|
||||
}
|
||||
catch (IntrospectionException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Method> getWriteMethods(BeanInfo beanInfo) {
|
||||
Map<String, Method> writeMethods = new HashMap<>();
|
||||
for (PropertyDescriptor propertyDescriptor : BeanUtils.getPropertyDescriptors(clazz)) {
|
||||
writeMethods.put(propertyDescriptor.getName(), propertyDescriptor.getWriteMethod());
|
||||
for (PropertyDescriptor propertyDescriptor : beanInfo.getPropertyDescriptors()) {
|
||||
writeMethods.put(propertyDescriptor.getName(),
|
||||
propertyDescriptor.getWriteMethod());
|
||||
}
|
||||
return Collections.unmodifiableMap(writeMethods);
|
||||
}
|
||||
@@ -281,25 +304,4 @@ class BeanDefinitionPropertiesCodeGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
static class PropertyNamesStack {
|
||||
|
||||
private static final ThreadLocal<ArrayDeque<String>> threadLocal = ThreadLocal.withInitial(ArrayDeque::new);
|
||||
|
||||
static void push(@Nullable String name) {
|
||||
String valueToSet = (name != null) ? name : "";
|
||||
threadLocal.get().push(valueToSet);
|
||||
}
|
||||
|
||||
static void pop() {
|
||||
threadLocal.get().pop();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static String peek() {
|
||||
String value = threadLocal.get().peek();
|
||||
return ("".equals(value) ? null : value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+17
-25
@@ -17,7 +17,6 @@
|
||||
package org.springframework.beans.factory.aot;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -30,7 +29,6 @@ import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.aot.generate.GeneratedMethod;
|
||||
@@ -63,32 +61,26 @@ class BeanDefinitionPropertyValueCodeGenerator {
|
||||
|
||||
private final GeneratedMethods generatedMethods;
|
||||
|
||||
private final List<Delegate> delegates;
|
||||
private final List<Delegate> delegates = List.of(
|
||||
new PrimitiveDelegate(),
|
||||
new StringDelegate(),
|
||||
new CharsetDelegate(),
|
||||
new EnumDelegate(),
|
||||
new ClassDelegate(),
|
||||
new ResolvableTypeDelegate(),
|
||||
new ArrayDelegate(),
|
||||
new ManagedListDelegate(),
|
||||
new ManagedSetDelegate(),
|
||||
new ManagedMapDelegate(),
|
||||
new ListDelegate(),
|
||||
new SetDelegate(),
|
||||
new MapDelegate(),
|
||||
new BeanReferenceDelegate()
|
||||
);
|
||||
|
||||
|
||||
BeanDefinitionPropertyValueCodeGenerator(GeneratedMethods generatedMethods,
|
||||
@Nullable BiFunction<Object, ResolvableType, CodeBlock> customValueGenerator) {
|
||||
BeanDefinitionPropertyValueCodeGenerator(GeneratedMethods generatedMethods) {
|
||||
this.generatedMethods = generatedMethods;
|
||||
this.delegates = new ArrayList<>();
|
||||
if (customValueGenerator != null) {
|
||||
this.delegates.add(customValueGenerator::apply);
|
||||
}
|
||||
this.delegates.addAll(List.of(
|
||||
new PrimitiveDelegate(),
|
||||
new StringDelegate(),
|
||||
new CharsetDelegate(),
|
||||
new EnumDelegate(),
|
||||
new ClassDelegate(),
|
||||
new ResolvableTypeDelegate(),
|
||||
new ArrayDelegate(),
|
||||
new ManagedListDelegate(),
|
||||
new ManagedSetDelegate(),
|
||||
new ManagedMapDelegate(),
|
||||
new ListDelegate(),
|
||||
new SetDelegate(),
|
||||
new MapDelegate(),
|
||||
new BeanReferenceDelegate()
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
|
||||
+62
-44
@@ -61,8 +61,8 @@ import org.springframework.util.function.ThrowingSupplier;
|
||||
* handles resolution of {@link AutowiredArguments} if necessary. Typically used
|
||||
* in AOT-processed applications as a targeted alternative to the reflection
|
||||
* based injection.
|
||||
*
|
||||
* <p>If no {@code generator} is provided, reflection is used to instantiate the
|
||||
* <p>
|
||||
* If no {@code generator} is provided, reflection is used to instantiate the
|
||||
* bean instance, and full {@link ExecutableMode#INVOKE invocation} hints are
|
||||
* contributed. Multiple generator callback styles are supported:
|
||||
* <ul>
|
||||
@@ -75,7 +75,7 @@ import org.springframework.util.function.ThrowingSupplier;
|
||||
* <li>A supplier when a method reference can be used</li>
|
||||
* </ul>
|
||||
* Generator callbacks handle checked exceptions so that the caller does not
|
||||
* have to deal with them.
|
||||
* have to deal with it.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
@@ -113,8 +113,10 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
|
||||
Class<?>... parameterTypes) {
|
||||
|
||||
Assert.notNull(parameterTypes, "'parameterTypes' must not be null");
|
||||
Assert.noNullElements(parameterTypes, "'parameterTypes' must not contain null elements");
|
||||
return new BeanInstanceSupplier<>(new ConstructorLookup(parameterTypes), null, null);
|
||||
Assert.noNullElements(parameterTypes,
|
||||
"'parameterTypes' must not contain null elements");
|
||||
return new BeanInstanceSupplier<>(
|
||||
new ConstructorLookup(parameterTypes), null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,7 +134,8 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
|
||||
Assert.notNull(declaringClass, "'declaringClass' must not be null");
|
||||
Assert.hasText(methodName, "'methodName' must not be empty");
|
||||
Assert.notNull(parameterTypes, "'parameterTypes' must not be null");
|
||||
Assert.noNullElements(parameterTypes, "'parameterTypes' must not contain null elements");
|
||||
Assert.noNullElements(parameterTypes,
|
||||
"'parameterTypes' must not contain null elements");
|
||||
return new BeanInstanceSupplier<>(
|
||||
new FactoryMethodLookup(declaringClass, methodName, parameterTypes),
|
||||
null, null);
|
||||
@@ -155,7 +158,7 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
|
||||
public BeanInstanceSupplier<T> withGenerator(
|
||||
ThrowingBiFunction<RegisteredBean, AutowiredArguments, T> generator) {
|
||||
Assert.notNull(generator, "'generator' must not be null");
|
||||
return new BeanInstanceSupplier<>(this.lookup, generator, this.shortcuts);
|
||||
return new BeanInstanceSupplier<T>(this.lookup, generator, this.shortcuts);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,8 +172,8 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
|
||||
public BeanInstanceSupplier<T> withGenerator(
|
||||
ThrowingFunction<RegisteredBean, T> generator) {
|
||||
Assert.notNull(generator, "'generator' must not be null");
|
||||
return new BeanInstanceSupplier<>(this.lookup,
|
||||
(registeredBean, args) -> generator.apply(registeredBean), this.shortcuts);
|
||||
return new BeanInstanceSupplier<>(this.lookup, (registeredBean, args) ->
|
||||
generator.apply(registeredBean), this.shortcuts);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,8 +186,8 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
|
||||
*/
|
||||
public BeanInstanceSupplier<T> withGenerator(ThrowingSupplier<T> generator) {
|
||||
Assert.notNull(generator, "'generator' must not be null");
|
||||
return new BeanInstanceSupplier<>(this.lookup,
|
||||
(registeredBean, args) -> generator.get(), this.shortcuts);
|
||||
return new BeanInstanceSupplier<>(this.lookup, (registeredBean, args) ->
|
||||
generator.get(), this.shortcuts);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -196,7 +199,7 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
|
||||
* that uses the shortcuts
|
||||
*/
|
||||
public BeanInstanceSupplier<T> withShortcuts(String... beanNames) {
|
||||
return new BeanInstanceSupplier<>(this.lookup, this.generator, beanNames);
|
||||
return new BeanInstanceSupplier<T>(this.lookup, this.generator, beanNames);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -205,10 +208,11 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
|
||||
Executable executable = this.lookup.get(registeredBean);
|
||||
AutowiredArguments arguments = resolveArguments(registeredBean, executable);
|
||||
if (this.generator != null) {
|
||||
return invokeBeanSupplier(executable, () -> this.generator.apply(registeredBean, arguments));
|
||||
return invokeBeanSupplier(executable, () ->
|
||||
this.generator.apply(registeredBean, arguments));
|
||||
}
|
||||
return invokeBeanSupplier(executable,
|
||||
() -> instantiate(registeredBean.getBeanFactory(), executable, arguments.toArray()));
|
||||
return invokeBeanSupplier(executable, () ->
|
||||
instantiate(registeredBean.getBeanFactory(), executable, arguments.toArray()));
|
||||
}
|
||||
|
||||
private T invokeBeanSupplier(Executable executable, ThrowingSupplier<T> beanSupplier) {
|
||||
@@ -243,15 +247,19 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
|
||||
return resolveArguments(registeredBean, this.lookup.get(registeredBean));
|
||||
}
|
||||
|
||||
private AutowiredArguments resolveArguments(RegisteredBean registeredBean,Executable executable) {
|
||||
Assert.isInstanceOf(AbstractAutowireCapableBeanFactory.class, registeredBean.getBeanFactory());
|
||||
private AutowiredArguments resolveArguments(RegisteredBean registeredBean,
|
||||
Executable executable) {
|
||||
|
||||
Assert.isInstanceOf(AbstractAutowireCapableBeanFactory.class,
|
||||
registeredBean.getBeanFactory());
|
||||
String beanName = registeredBean.getBeanName();
|
||||
Class<?> beanClass = registeredBean.getBeanClass();
|
||||
AbstractAutowireCapableBeanFactory beanFactory =
|
||||
(AbstractAutowireCapableBeanFactory) registeredBean.getBeanFactory();
|
||||
RootBeanDefinition mergedBeanDefinition = registeredBean.getMergedBeanDefinition();
|
||||
int startIndex = (executable instanceof Constructor<?> constructor &&
|
||||
ClassUtils.isInnerClass(constructor.getDeclaringClass())) ? 1 : 0;
|
||||
AbstractAutowireCapableBeanFactory beanFactory = (AbstractAutowireCapableBeanFactory) registeredBean
|
||||
.getBeanFactory();
|
||||
RootBeanDefinition mergedBeanDefinition = registeredBean
|
||||
.getMergedBeanDefinition();
|
||||
int startIndex = (executable instanceof Constructor<?> constructor
|
||||
&& ClassUtils.isInnerClass(constructor.getDeclaringClass())) ? 1 : 0;
|
||||
int parameterCount = executable.getParameterCount();
|
||||
Object[] resolved = new Object[parameterCount - startIndex];
|
||||
Assert.isTrue(this.shortcuts == null || this.shortcuts.length == resolved.length,
|
||||
@@ -261,8 +269,10 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
|
||||
beanName, mergedBeanDefinition);
|
||||
for (int i = startIndex; i < parameterCount; i++) {
|
||||
MethodParameter parameter = getMethodParameter(executable, i);
|
||||
DependencyDescriptor dependencyDescriptor = new DependencyDescriptor(parameter, true);
|
||||
String shortcut = (this.shortcuts != null) ? this.shortcuts[i - startIndex] : null;
|
||||
DependencyDescriptor dependencyDescriptor = new DependencyDescriptor(
|
||||
parameter, true);
|
||||
String shortcut = (this.shortcuts != null) ? this.shortcuts[i - startIndex]
|
||||
: null;
|
||||
if (shortcut != null) {
|
||||
dependencyDescriptor = new ShortcutDependencyDescriptor(
|
||||
dependencyDescriptor, shortcut, beanClass);
|
||||
@@ -293,10 +303,13 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
|
||||
ConstructorArgumentValues resolved = new ConstructorArgumentValues();
|
||||
if (mergedBeanDefinition.hasConstructorArgumentValues()) {
|
||||
BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(
|
||||
beanFactory, beanName, mergedBeanDefinition, beanFactory.getTypeConverter());
|
||||
ConstructorArgumentValues values = mergedBeanDefinition.getConstructorArgumentValues();
|
||||
beanFactory, beanName, mergedBeanDefinition,
|
||||
beanFactory.getTypeConverter());
|
||||
ConstructorArgumentValues values = mergedBeanDefinition
|
||||
.getConstructorArgumentValues();
|
||||
values.getIndexedArgumentValues().forEach((index, valueHolder) -> {
|
||||
ValueHolder resolvedValue = resolveArgumentValue(valueResolver, valueHolder);
|
||||
ValueHolder resolvedValue = resolveArgumentValue(valueResolver,
|
||||
valueHolder);
|
||||
resolved.addIndexedArgumentValue(index, resolvedValue);
|
||||
});
|
||||
}
|
||||
@@ -320,14 +333,15 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
|
||||
@Nullable
|
||||
private Object resolveArgument(AbstractAutowireCapableBeanFactory beanFactory,
|
||||
String beanName, Set<String> autowiredBeans, MethodParameter parameter,
|
||||
DependencyDescriptor dependencyDescriptor, @Nullable ValueHolder argumentValue) {
|
||||
DependencyDescriptor dependencyDescriptor,
|
||||
@Nullable ValueHolder argumentValue) {
|
||||
|
||||
TypeConverter typeConverter = beanFactory.getTypeConverter();
|
||||
Class<?> parameterType = parameter.getParameterType();
|
||||
if (argumentValue != null) {
|
||||
return (!argumentValue.isConverted()) ?
|
||||
typeConverter.convertIfNecessary(argumentValue.getValue(), parameterType) :
|
||||
argumentValue.getConvertedValue();
|
||||
return (!argumentValue.isConverted()) ? typeConverter
|
||||
.convertIfNecessary(argumentValue.getValue(), parameterType)
|
||||
: argumentValue.getConvertedValue();
|
||||
}
|
||||
try {
|
||||
try {
|
||||
@@ -373,7 +387,9 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
|
||||
"Unsupported executable " + executable.getClass().getName());
|
||||
}
|
||||
|
||||
private Object instantiate(Constructor<?> constructor, Object[] arguments) throws Exception {
|
||||
private Object instantiate(Constructor<?> constructor, Object[] arguments)
|
||||
throws Exception {
|
||||
|
||||
Class<?> declaringClass = constructor.getDeclaringClass();
|
||||
if (ClassUtils.isInnerClass(declaringClass)) {
|
||||
Object enclosingInstance = createInstance(declaringClass.getEnclosingClass());
|
||||
@@ -412,10 +428,6 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
|
||||
}
|
||||
|
||||
|
||||
private static String toCommaSeparatedNames(Class<?>... parameterTypes) {
|
||||
return Arrays.stream(parameterTypes).map(Class::getName).collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs lookup of the {@link Executable}.
|
||||
*/
|
||||
@@ -423,6 +435,11 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
|
||||
|
||||
abstract Executable get(RegisteredBean registeredBean);
|
||||
|
||||
final String toCommaSeparatedNames(Class<?>... parameterTypes) {
|
||||
return Arrays.stream(parameterTypes).map(Class::getName)
|
||||
.collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -443,20 +460,20 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
|
||||
public Executable get(RegisteredBean registeredBean) {
|
||||
Class<?> beanClass = registeredBean.getBeanClass();
|
||||
try {
|
||||
Class<?>[] actualParameterTypes = (!ClassUtils.isInnerClass(beanClass)) ?
|
||||
this.parameterTypes : ObjectUtils.addObjectToArray(
|
||||
this.parameterTypes, beanClass.getEnclosingClass(), 0);
|
||||
Class<?>[] actualParameterTypes = (!ClassUtils.isInnerClass(beanClass))
|
||||
? this.parameterTypes : ObjectUtils.addObjectToArray(
|
||||
this.parameterTypes, beanClass.getEnclosingClass(), 0);
|
||||
return beanClass.getDeclaredConstructor(actualParameterTypes);
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
throw new IllegalArgumentException(
|
||||
"%s cannot be found on %s".formatted(this, beanClass.getName()), ex);
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"%s cannot be found on %s", this, beanClass.getName()), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Constructor with parameter types [%s]".formatted(
|
||||
return String.format("Constructor with parameter types [%s]",
|
||||
toCommaSeparatedNames(this.parameterTypes));
|
||||
}
|
||||
|
||||
@@ -491,13 +508,14 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
|
||||
Method get() {
|
||||
Method method = ReflectionUtils.findMethod(this.declaringClass,
|
||||
this.methodName, this.parameterTypes);
|
||||
Assert.notNull(method, () -> "%s cannot be found".formatted(this));
|
||||
Assert.notNull(method, () -> String.format("%s cannot be found", this));
|
||||
return method;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Factory method '%s' with parameter types [%s] declared on %s".formatted(
|
||||
return String.format(
|
||||
"Factory method '%s' with parameter types [%s] declared on %s",
|
||||
this.methodName, toCommaSeparatedNames(this.parameterTypes),
|
||||
this.declaringClass);
|
||||
}
|
||||
|
||||
+15
-7
@@ -33,7 +33,7 @@ import org.springframework.util.Assert;
|
||||
/**
|
||||
* A {@link BeanRegistrationCodeFragments} decorator implementation. Typically
|
||||
* used when part of the default code fragments have to customized, by extending
|
||||
* this class and using it as part of
|
||||
* this class and use it as part of
|
||||
* {@link BeanRegistrationAotContribution#withCustomCodeFragments(UnaryOperator)}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
@@ -42,6 +42,7 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class BeanRegistrationCodeFragmentsDecorator implements BeanRegistrationCodeFragments {
|
||||
|
||||
|
||||
private final BeanRegistrationCodeFragments delegate;
|
||||
|
||||
|
||||
@@ -51,7 +52,9 @@ public class BeanRegistrationCodeFragmentsDecorator implements BeanRegistrationC
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassName getTarget(RegisteredBean registeredBean, Executable constructorOrFactoryMethod) {
|
||||
public ClassName getTarget(RegisteredBean registeredBean,
|
||||
Executable constructorOrFactoryMethod) {
|
||||
|
||||
return this.delegate.getTarget(registeredBean, constructorOrFactoryMethod);
|
||||
}
|
||||
|
||||
@@ -61,19 +64,23 @@ public class BeanRegistrationCodeFragmentsDecorator implements BeanRegistrationC
|
||||
|
||||
return this.delegate.generateNewBeanDefinitionCode(generationContext,
|
||||
beanType, beanRegistrationCode);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeBlock generateSetBeanDefinitionPropertiesCode(GenerationContext generationContext,
|
||||
public CodeBlock generateSetBeanDefinitionPropertiesCode(
|
||||
GenerationContext generationContext,
|
||||
BeanRegistrationCode beanRegistrationCode, RootBeanDefinition beanDefinition,
|
||||
Predicate<String> attributeFilter) {
|
||||
|
||||
return this.delegate.generateSetBeanDefinitionPropertiesCode(
|
||||
generationContext, beanRegistrationCode, beanDefinition, attributeFilter);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeBlock generateSetBeanInstanceSupplierCode(GenerationContext generationContext,
|
||||
public CodeBlock generateSetBeanInstanceSupplierCode(
|
||||
GenerationContext generationContext,
|
||||
BeanRegistrationCode beanRegistrationCode, CodeBlock instanceSupplierCode,
|
||||
List<MethodReference> postProcessors) {
|
||||
|
||||
@@ -83,8 +90,8 @@ public class BeanRegistrationCodeFragmentsDecorator implements BeanRegistrationC
|
||||
|
||||
@Override
|
||||
public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext,
|
||||
BeanRegistrationCode beanRegistrationCode, Executable constructorOrFactoryMethod,
|
||||
boolean allowDirectSupplierShortcut) {
|
||||
BeanRegistrationCode beanRegistrationCode,
|
||||
Executable constructorOrFactoryMethod, boolean allowDirectSupplierShortcut) {
|
||||
|
||||
return this.delegate.generateInstanceSupplierCode(generationContext,
|
||||
beanRegistrationCode, constructorOrFactoryMethod, allowDirectSupplierShortcut);
|
||||
@@ -94,7 +101,8 @@ public class BeanRegistrationCodeFragmentsDecorator implements BeanRegistrationC
|
||||
public CodeBlock generateReturnCode(GenerationContext generationContext,
|
||||
BeanRegistrationCode beanRegistrationCode) {
|
||||
|
||||
return this.delegate.generateReturnCode(generationContext, beanRegistrationCode);
|
||||
return this.delegate.generateReturnCode(generationContext,
|
||||
beanRegistrationCode);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+12
-36
@@ -33,11 +33,9 @@ import org.springframework.javapoet.MethodSpec;
|
||||
|
||||
/**
|
||||
* AOT contribution from a {@link BeanRegistrationsAotProcessor} used to
|
||||
* register bean definitions and aliases.
|
||||
* register bean definitions.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Sebastien Deleuze
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
* @see BeanRegistrationsAotProcessor
|
||||
*/
|
||||
@@ -46,9 +44,12 @@ class BeanRegistrationsAotContribution
|
||||
|
||||
private static final String BEAN_FACTORY_PARAMETER_NAME = "beanFactory";
|
||||
|
||||
private final Map<String, Registration> registrations;
|
||||
private final Map<String, BeanDefinitionMethodGenerator> registrations;
|
||||
|
||||
|
||||
BeanRegistrationsAotContribution(
|
||||
Map<String, BeanDefinitionMethodGenerator> registrations) {
|
||||
|
||||
BeanRegistrationsAotContribution(Map<String, Registration> registrations) {
|
||||
this.registrations = registrations;
|
||||
}
|
||||
|
||||
@@ -63,15 +64,12 @@ class BeanRegistrationsAotContribution
|
||||
type.addModifiers(Modifier.PUBLIC);
|
||||
});
|
||||
BeanRegistrationsCodeGenerator codeGenerator = new BeanRegistrationsCodeGenerator(generatedClass);
|
||||
GeneratedMethod generatedBeanDefinitionsMethod = codeGenerator.getMethods().add("registerBeanDefinitions", method ->
|
||||
generateRegisterBeanDefinitionsMethod(method, generationContext, codeGenerator));
|
||||
beanFactoryInitializationCode.addInitializer(generatedBeanDefinitionsMethod.toMethodReference());
|
||||
GeneratedMethod generatedAliasesMethod = codeGenerator.getMethods().add("registerAliases",
|
||||
this::generateRegisterAliasesMethod);
|
||||
beanFactoryInitializationCode.addInitializer(generatedAliasesMethod.toMethodReference());
|
||||
GeneratedMethod generatedMethod = codeGenerator.getMethods().add("registerBeanDefinitions", method ->
|
||||
generateRegisterMethod(method, generationContext, codeGenerator));
|
||||
beanFactoryInitializationCode.addInitializer(generatedMethod.toMethodReference());
|
||||
}
|
||||
|
||||
private void generateRegisterBeanDefinitionsMethod(MethodSpec.Builder method,
|
||||
private void generateRegisterMethod(MethodSpec.Builder method,
|
||||
GenerationContext generationContext,
|
||||
BeanRegistrationsCode beanRegistrationsCode) {
|
||||
|
||||
@@ -80,8 +78,8 @@ class BeanRegistrationsAotContribution
|
||||
method.addParameter(DefaultListableBeanFactory.class,
|
||||
BEAN_FACTORY_PARAMETER_NAME);
|
||||
CodeBlock.Builder code = CodeBlock.builder();
|
||||
this.registrations.forEach((beanName, registration) -> {
|
||||
MethodReference beanDefinitionMethod = registration.methodGenerator
|
||||
this.registrations.forEach((beanName, beanDefinitionMethodGenerator) -> {
|
||||
MethodReference beanDefinitionMethod = beanDefinitionMethodGenerator
|
||||
.generateBeanDefinitionMethod(generationContext,
|
||||
beanRegistrationsCode);
|
||||
CodeBlock methodInvocation = beanDefinitionMethod.toInvokeCodeBlock(
|
||||
@@ -93,28 +91,6 @@ class BeanRegistrationsAotContribution
|
||||
method.addCode(code.build());
|
||||
}
|
||||
|
||||
private void generateRegisterAliasesMethod(MethodSpec.Builder method) {
|
||||
method.addJavadoc("Register the aliases.");
|
||||
method.addModifiers(Modifier.PUBLIC);
|
||||
method.addParameter(DefaultListableBeanFactory.class,
|
||||
BEAN_FACTORY_PARAMETER_NAME);
|
||||
CodeBlock.Builder code = CodeBlock.builder();
|
||||
this.registrations.forEach((beanName, registration) -> {
|
||||
for (String alias : registration.aliases) {
|
||||
code.addStatement("$L.registerAlias($S, $S)",
|
||||
BEAN_FACTORY_PARAMETER_NAME, beanName, alias);
|
||||
}
|
||||
});
|
||||
method.addCode(code.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gather the necessary information to register a particular bean.
|
||||
* @param methodGenerator the {@link BeanDefinitionMethodGenerator} to use
|
||||
* @param aliases the bean aliases, if any
|
||||
*/
|
||||
record Registration(BeanDefinitionMethodGenerator methodGenerator, String[] aliases) {}
|
||||
|
||||
|
||||
/**
|
||||
* {@link BeanRegistrationsCode} with generation support.
|
||||
|
||||
+4
-12
@@ -19,39 +19,31 @@ package org.springframework.beans.factory.aot;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationsAotContribution.Registration;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.RegisteredBean;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link BeanFactoryInitializationAotProcessor} that contributes code to
|
||||
* register beans.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Sebastien Deleuze
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
class BeanRegistrationsAotProcessor implements BeanFactoryInitializationAotProcessor {
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public BeanRegistrationsAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
|
||||
BeanDefinitionMethodGeneratorFactory beanDefinitionMethodGeneratorFactory =
|
||||
new BeanDefinitionMethodGeneratorFactory(beanFactory);
|
||||
Map<String, Registration> registrations = new LinkedHashMap<>();
|
||||
|
||||
Map<String, BeanDefinitionMethodGenerator> registrations = new LinkedHashMap<>();
|
||||
for (String beanName : beanFactory.getBeanDefinitionNames()) {
|
||||
RegisteredBean registeredBean = RegisteredBean.of(beanFactory, beanName);
|
||||
BeanDefinitionMethodGenerator beanDefinitionMethodGenerator =
|
||||
beanDefinitionMethodGeneratorFactory.getBeanDefinitionMethodGenerator(registeredBean);
|
||||
BeanDefinitionMethodGenerator beanDefinitionMethodGenerator = beanDefinitionMethodGeneratorFactory
|
||||
.getBeanDefinitionMethodGenerator(registeredBean, null);
|
||||
if (beanDefinitionMethodGenerator != null) {
|
||||
registrations.put(beanName, new Registration(beanDefinitionMethodGenerator,
|
||||
beanFactory.getAliases(beanName)));
|
||||
registrations.put(beanName, beanDefinitionMethodGenerator);
|
||||
}
|
||||
}
|
||||
|
||||
if (registrations.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
+34
-47
@@ -46,16 +46,16 @@ import org.springframework.util.function.ThrowingSupplier;
|
||||
* Internal code generator to create an {@link InstanceSupplier}, usually in
|
||||
* the form of a {@link BeanInstanceSupplier} that retains the executable
|
||||
* that is used to instantiate the bean.
|
||||
*
|
||||
* <p>Generated code is usually a method reference that generate the
|
||||
* {@link BeanInstanceSupplier}, but some shortcut can be used as well such as:
|
||||
* <p>
|
||||
* Generated code is usually a method reference that generate the
|
||||
* {@link BeanInstanceSupplier}, but some shortcut can be used as well such
|
||||
* as:
|
||||
* <pre class="code">
|
||||
* {@code InstanceSupplier.of(TheGeneratedClass::getMyBeanInstance);}
|
||||
* </pre>
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
* @author Juergen Hoeller
|
||||
* @since 6.0
|
||||
*/
|
||||
class InstanceSupplierCodeGenerator {
|
||||
@@ -90,7 +90,9 @@ class InstanceSupplierCodeGenerator {
|
||||
}
|
||||
|
||||
|
||||
CodeBlock generateCode(RegisteredBean registeredBean, Executable constructorOrFactoryMethod) {
|
||||
CodeBlock generateCode(RegisteredBean registeredBean,
|
||||
Executable constructorOrFactoryMethod) {
|
||||
|
||||
if (constructorOrFactoryMethod instanceof Constructor<?> constructor) {
|
||||
return generateCodeForConstructor(registeredBean, constructor);
|
||||
}
|
||||
@@ -106,7 +108,6 @@ class InstanceSupplierCodeGenerator {
|
||||
Class<?> beanClass = registeredBean.getBeanClass();
|
||||
Class<?> declaringClass = constructor.getDeclaringClass();
|
||||
boolean dependsOnBean = ClassUtils.isInnerClass(declaringClass);
|
||||
|
||||
Visibility accessVisibility = getAccessVisibility(registeredBean, constructor);
|
||||
if (accessVisibility != Visibility.PRIVATE) {
|
||||
return generateCodeForAccessibleConstructor(beanName, beanClass, constructor,
|
||||
@@ -120,7 +121,6 @@ class InstanceSupplierCodeGenerator {
|
||||
|
||||
this.generationContext.getRuntimeHints().reflection().registerConstructor(
|
||||
constructor, ExecutableMode.INTROSPECT);
|
||||
|
||||
if (!dependsOnBean && constructor.getParameterCount() == 0) {
|
||||
if (!this.allowDirectSupplierShortcut) {
|
||||
return CodeBlock.of("$T.using($T::new)", InstanceSupplier.class, declaringClass);
|
||||
@@ -130,7 +130,6 @@ class InstanceSupplierCodeGenerator {
|
||||
}
|
||||
return CodeBlock.of("$T.of($T::new)", ThrowingSupplier.class, declaringClass);
|
||||
}
|
||||
|
||||
GeneratedMethod generatedMethod = generateGetInstanceSupplierMethod(method ->
|
||||
buildGetInstanceMethodForConstructor(method, beanName, beanClass, constructor,
|
||||
declaringClass, dependsOnBean, PRIVATE_STATIC));
|
||||
@@ -142,7 +141,6 @@ class InstanceSupplierCodeGenerator {
|
||||
|
||||
this.generationContext.getRuntimeHints().reflection()
|
||||
.registerConstructor(constructor, ExecutableMode.INVOKE);
|
||||
|
||||
GeneratedMethod generatedMethod = generateGetInstanceSupplierMethod(method -> {
|
||||
method.addJavadoc("Get the bean instance supplier for '$L'.", beanName);
|
||||
method.addModifiers(PRIVATE_STATIC);
|
||||
@@ -150,7 +148,6 @@ class InstanceSupplierCodeGenerator {
|
||||
int parameterOffset = (!dependsOnBean) ? 0 : 1;
|
||||
method.addStatement(generateResolverForConstructor(beanClass, constructor, parameterOffset));
|
||||
});
|
||||
|
||||
return generateReturnStatement(generatedMethod);
|
||||
}
|
||||
|
||||
@@ -161,17 +158,14 @@ class InstanceSupplierCodeGenerator {
|
||||
method.addJavadoc("Get the bean instance supplier for '$L'.", beanName);
|
||||
method.addModifiers(modifiers);
|
||||
method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, beanClass));
|
||||
|
||||
int parameterOffset = (!dependsOnBean) ? 0 : 1;
|
||||
CodeBlock.Builder code = CodeBlock.builder();
|
||||
code.add(generateResolverForConstructor(beanClass, constructor, parameterOffset));
|
||||
boolean hasArguments = constructor.getParameterCount() > 0;
|
||||
|
||||
CodeBlock arguments = hasArguments ?
|
||||
new AutowiredArgumentsCodeGenerator(declaringClass, constructor)
|
||||
.generateCode(constructor.getParameterTypes(), parameterOffset)
|
||||
: NO_ARGS;
|
||||
|
||||
CodeBlock newInstance = generateNewInstanceCodeForConstructor(dependsOnBean, declaringClass, arguments);
|
||||
code.add(generateWithGeneratorCode(hasArguments, newInstance));
|
||||
method.addStatement(code.build());
|
||||
@@ -190,99 +184,92 @@ class InstanceSupplierCodeGenerator {
|
||||
if (!dependsOnBean) {
|
||||
return CodeBlock.of("new $T($L)", declaringClass, args);
|
||||
}
|
||||
|
||||
return CodeBlock.of("$L.getBeanFactory().getBean($T.class).new $L($L)",
|
||||
REGISTERED_BEAN_PARAMETER_NAME, declaringClass.getEnclosingClass(),
|
||||
declaringClass.getSimpleName(), args);
|
||||
}
|
||||
|
||||
private CodeBlock generateCodeForFactoryMethod(RegisteredBean registeredBean, Method factoryMethod) {
|
||||
String beanName = registeredBean.getBeanName();
|
||||
Class<?> declaringClass = ClassUtils.getUserClass(factoryMethod.getDeclaringClass());
|
||||
boolean dependsOnBean = !Modifier.isStatic(factoryMethod.getModifiers());
|
||||
private CodeBlock generateCodeForFactoryMethod(RegisteredBean registeredBean,
|
||||
Method factoryMethod) {
|
||||
|
||||
String beanName = registeredBean.getBeanName();
|
||||
Class<?> beanClass = registeredBean.getBeanClass();
|
||||
Class<?> declaringClass = ClassUtils
|
||||
.getUserClass(factoryMethod.getDeclaringClass());
|
||||
boolean dependsOnBean = !Modifier.isStatic(factoryMethod.getModifiers());
|
||||
Visibility accessVisibility = getAccessVisibility(registeredBean, factoryMethod);
|
||||
if (accessVisibility != Visibility.PRIVATE) {
|
||||
return generateCodeForAccessibleFactoryMethod(
|
||||
beanName, factoryMethod, declaringClass, dependsOnBean);
|
||||
beanName, beanClass, factoryMethod, declaringClass, dependsOnBean);
|
||||
}
|
||||
return generateCodeForInaccessibleFactoryMethod(beanName, factoryMethod, declaringClass);
|
||||
return generateCodeForInaccessibleFactoryMethod(beanName, beanClass, factoryMethod, declaringClass);
|
||||
}
|
||||
|
||||
private CodeBlock generateCodeForAccessibleFactoryMethod(String beanName,
|
||||
Method factoryMethod, Class<?> declaringClass, boolean dependsOnBean) {
|
||||
Class<?> beanClass, Method factoryMethod, Class<?> declaringClass, boolean dependsOnBean) {
|
||||
|
||||
this.generationContext.getRuntimeHints().reflection().registerMethod(
|
||||
factoryMethod, ExecutableMode.INTROSPECT);
|
||||
|
||||
if (!dependsOnBean && factoryMethod.getParameterCount() == 0) {
|
||||
Class<?> suppliedType = ClassUtils.resolvePrimitiveIfNecessary(factoryMethod.getReturnType());
|
||||
CodeBlock.Builder code = CodeBlock.builder();
|
||||
code.add("$T.<$T>forFactoryMethod($T.class, $S)", BeanInstanceSupplier.class,
|
||||
suppliedType, declaringClass, factoryMethod.getName());
|
||||
beanClass, declaringClass, factoryMethod.getName());
|
||||
code.add(".withGenerator($T::$L)", declaringClass, factoryMethod.getName());
|
||||
return code.build();
|
||||
}
|
||||
|
||||
GeneratedMethod getInstanceMethod = generateGetInstanceSupplierMethod(method ->
|
||||
buildGetInstanceMethodForFactoryMethod(method, beanName, factoryMethod,
|
||||
buildGetInstanceMethodForFactoryMethod(method, beanName, beanClass, factoryMethod,
|
||||
declaringClass, dependsOnBean, PRIVATE_STATIC));
|
||||
return generateReturnStatement(getInstanceMethod);
|
||||
}
|
||||
|
||||
private CodeBlock generateCodeForInaccessibleFactoryMethod(
|
||||
String beanName, Method factoryMethod, Class<?> declaringClass) {
|
||||
private CodeBlock generateCodeForInaccessibleFactoryMethod(String beanName, Class<?> beanClass,
|
||||
Method factoryMethod, Class<?> declaringClass) {
|
||||
|
||||
this.generationContext.getRuntimeHints().reflection().registerMethod(factoryMethod, ExecutableMode.INVOKE);
|
||||
GeneratedMethod getInstanceMethod = generateGetInstanceSupplierMethod(method -> {
|
||||
Class<?> suppliedType = ClassUtils.resolvePrimitiveIfNecessary(factoryMethod.getReturnType());
|
||||
method.addJavadoc("Get the bean instance supplier for '$L'.", beanName);
|
||||
method.addModifiers(PRIVATE_STATIC);
|
||||
method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, suppliedType));
|
||||
method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, beanClass));
|
||||
method.addStatement(generateInstanceSupplierForFactoryMethod(
|
||||
factoryMethod, suppliedType, declaringClass, factoryMethod.getName()));
|
||||
beanClass, factoryMethod, declaringClass, factoryMethod.getName()));
|
||||
});
|
||||
return generateReturnStatement(getInstanceMethod);
|
||||
}
|
||||
|
||||
private void buildGetInstanceMethodForFactoryMethod(MethodSpec.Builder method,
|
||||
String beanName, Method factoryMethod, Class<?> declaringClass,
|
||||
String beanName, Class<?> beanClass, Method factoryMethod, Class<?> declaringClass,
|
||||
boolean dependsOnBean, javax.lang.model.element.Modifier... modifiers) {
|
||||
|
||||
String factoryMethodName = factoryMethod.getName();
|
||||
Class<?> suppliedType = ClassUtils.resolvePrimitiveIfNecessary(factoryMethod.getReturnType());
|
||||
|
||||
method.addJavadoc("Get the bean instance supplier for '$L'.", beanName);
|
||||
method.addModifiers(modifiers);
|
||||
method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, suppliedType));
|
||||
|
||||
method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, beanClass));
|
||||
CodeBlock.Builder code = CodeBlock.builder();
|
||||
code.add(generateInstanceSupplierForFactoryMethod(
|
||||
factoryMethod, suppliedType, declaringClass, factoryMethodName));
|
||||
|
||||
beanClass, factoryMethod, declaringClass, factoryMethodName));
|
||||
boolean hasArguments = factoryMethod.getParameterCount() > 0;
|
||||
CodeBlock arguments = hasArguments ?
|
||||
new AutowiredArgumentsCodeGenerator(declaringClass, factoryMethod)
|
||||
.generateCode(factoryMethod.getParameterTypes())
|
||||
: NO_ARGS;
|
||||
|
||||
CodeBlock newInstance = generateNewInstanceCodeForMethod(
|
||||
dependsOnBean, declaringClass, factoryMethodName, arguments);
|
||||
code.add(generateWithGeneratorCode(hasArguments, newInstance));
|
||||
method.addStatement(code.build());
|
||||
}
|
||||
|
||||
private CodeBlock generateInstanceSupplierForFactoryMethod(Method factoryMethod,
|
||||
Class<?> suppliedType, Class<?> declaringClass, String factoryMethodName) {
|
||||
private CodeBlock generateInstanceSupplierForFactoryMethod(Class<?> beanClass,
|
||||
Method factoryMethod, Class<?> declaringClass, String factoryMethodName) {
|
||||
|
||||
if (factoryMethod.getParameterCount() == 0) {
|
||||
return CodeBlock.of("return $T.<$T>forFactoryMethod($T.class, $S)",
|
||||
BeanInstanceSupplier.class, suppliedType, declaringClass, factoryMethodName);
|
||||
BeanInstanceSupplier.class, beanClass, declaringClass,
|
||||
factoryMethodName);
|
||||
}
|
||||
|
||||
CodeBlock parameterTypes = generateParameterTypesCode(factoryMethod.getParameterTypes(), 0);
|
||||
return CodeBlock.of("return $T.<$T>forFactoryMethod($T.class, $S, $L)",
|
||||
BeanInstanceSupplier.class, suppliedType, declaringClass, factoryMethodName, parameterTypes);
|
||||
BeanInstanceSupplier.class, beanClass, declaringClass, factoryMethodName, parameterTypes);
|
||||
}
|
||||
|
||||
private CodeBlock generateNewInstanceCodeForMethod(boolean dependsOnBean,
|
||||
@@ -301,9 +288,9 @@ class InstanceSupplierCodeGenerator {
|
||||
}
|
||||
|
||||
private CodeBlock generateWithGeneratorCode(boolean hasArguments, CodeBlock newInstance) {
|
||||
CodeBlock lambdaArguments = (hasArguments ?
|
||||
CodeBlock.of("($L, $L)", REGISTERED_BEAN_PARAMETER_NAME, ARGS_PARAMETER_NAME) :
|
||||
CodeBlock.of("($L)", REGISTERED_BEAN_PARAMETER_NAME));
|
||||
CodeBlock lambdaArguments = (hasArguments
|
||||
? CodeBlock.of("($L, $L)", REGISTERED_BEAN_PARAMETER_NAME, ARGS_PARAMETER_NAME)
|
||||
: CodeBlock.of("($L)", REGISTERED_BEAN_PARAMETER_NAME));
|
||||
Builder code = CodeBlock.builder();
|
||||
code.add("\n");
|
||||
code.indent().indent();
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ final class ResolvableTypeCodeGenerator {
|
||||
return CodeBlock.of("$T.NONE", ResolvableType.class);
|
||||
}
|
||||
Class<?> type = ClassUtils.getUserClass(resolvableType.toClass());
|
||||
if (resolvableType.hasGenerics() && !resolvableType.hasUnresolvableGenerics()) {
|
||||
if (resolvableType.hasGenerics()) {
|
||||
return generateCodeWithGenerics(resolvableType, type);
|
||||
}
|
||||
if (allowClassResult) {
|
||||
|
||||
+1
-20
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -360,25 +360,6 @@ public class ConstructorArgumentValues {
|
||||
return valueHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether at least one argument value refers to a name.
|
||||
* @since 6.0.3
|
||||
* @see ValueHolder#getName()
|
||||
*/
|
||||
public boolean containsNamedArgument() {
|
||||
for (ValueHolder valueHolder : this.indexedArgumentValues.values()) {
|
||||
if (valueHolder.getName() != null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (ValueHolder valueHolder : this.genericArgumentValues) {
|
||||
if (valueHolder.getName() != null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of argument values held in this instance,
|
||||
* counting both indexed and generic argument values.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user