mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
101 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f85d5bd84a | |||
| 89dd247b97 | |||
| 39dd1e4049 | |||
| 34f4ad3b71 | |||
| ea596aa211 | |||
| 58da30cd30 | |||
| cd33b4e35a | |||
| 20dea0dae2 | |||
| fee17e11ba | |||
| 65e1337d35 | |||
| a4c2f291d9 | |||
| f26483d272 | |||
| 481d036f7a | |||
| 2a2ef443a5 | |||
| 617833bec9 | |||
| 4d633c2ea8 | |||
| 010e8a303b | |||
| 78549d4963 | |||
| e4e6910122 | |||
| 2270df515b | |||
| 70886e32c0 | |||
| e509385eae | |||
| b7aafda872 | |||
| a89a88d17c | |||
| c6b6ccdc89 | |||
| 09b8feadc2 | |||
| d03ea0bf19 | |||
| a5a1ef6b30 | |||
| 1b60b86bb2 | |||
| 89ce63f1f3 | |||
| 7536980be9 | |||
| c1229b03a7 | |||
| a4135bac5c | |||
| bf08e0c1b4 | |||
| 9202c0ad41 | |||
| 22b6d66a28 | |||
| 9376e6322d | |||
| 0eb937a866 | |||
| 6250b64766 | |||
| 5b1278d03c | |||
| 4f02be263f | |||
| 05d9b52b19 | |||
| 59a125d06f | |||
| aebc48ee8d | |||
| f51be0a17e | |||
| 1d2b5a15c3 | |||
| 4baad16437 | |||
| abcc1dfc6c | |||
| 47c5cd208c | |||
| 5a24e94d2e | |||
| f90bdbef42 | |||
| f17527a48b | |||
| 64b0283042 | |||
| 25cedcfb99 | |||
| 610626aec6 | |||
| d8afe7a8ad | |||
| 32c80d5ae6 | |||
| 0241a02e66 | |||
| cbda46984c | |||
| 7263771552 | |||
| fab3633c75 | |||
| 26706f0039 | |||
| 3cf2cd70ab | |||
| 0ac04a7f86 | |||
| 370a4ee8b3 | |||
| 55361fa4f6 | |||
| 546ca9b834 | |||
| 09b476ac18 | |||
| 387e34d881 | |||
| 4a10bc3288 | |||
| 580adfcbed | |||
| d151931f86 | |||
| 16d8d69cf9 | |||
| 95ac0eae4a | |||
| cc73ccefef | |||
| 4d34444a69 | |||
| d4ddbd537b | |||
| 0b9b9b4602 | |||
| 40596d444c | |||
| cb5b9dcaed | |||
| c99e7f8616 | |||
| 8a8c8fe00e | |||
| f45e7b9b9b | |||
| ec1f5ca600 | |||
| 62efdfb89c | |||
| c3da43291b | |||
| 5aa576f5c6 | |||
| 0268180799 | |||
| da4547a27e | |||
| 97eddb769a | |||
| 0e0397a385 | |||
| fec4f9b8cb | |||
| 4e20cdeb43 | |||
| 7f27ba3902 | |||
| b3a45670f9 | |||
| 8ec27438e7 | |||
| bf3278596c | |||
| 3971632415 | |||
| 1e54916119 | |||
| 6e20af41bf | |||
| c68c62b6c6 |
@@ -35,9 +35,7 @@ jobs:
|
||||
env:
|
||||
CI: 'true'
|
||||
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
|
||||
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
|
||||
GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
|
||||
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
|
||||
DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
|
||||
run: ./gradlew -PdeploymentRepository=$(pwd)/deployment-repository build publishAllPublicationsToDeploymentRepository
|
||||
- name: Deploy
|
||||
uses: spring-io/artifactory-deploy-action@v0.0.1
|
||||
|
||||
@@ -66,9 +66,7 @@ jobs:
|
||||
env:
|
||||
CI: 'true'
|
||||
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
|
||||
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
|
||||
GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
|
||||
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
|
||||
DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
|
||||
run: ./gradlew check antora
|
||||
- name: Send notification
|
||||
uses: ./.github/actions/send-notification
|
||||
|
||||
@@ -51,3 +51,5 @@ atlassian-ide-plugin.xml
|
||||
.vscode/
|
||||
|
||||
cached-antora-playbook.yml
|
||||
|
||||
node_modules
|
||||
|
||||
@@ -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.10-librca
|
||||
java=17.0.11-librca
|
||||
|
||||
@@ -89,8 +89,6 @@ configure([rootProject] + javaProjects) { project ->
|
||||
ext.javadocLinks = [
|
||||
"https://docs.oracle.com/en/java/javase/17/docs/api/",
|
||||
"https://jakarta.ee/specifications/platform/9/apidocs/",
|
||||
"https://docs.oracle.com/cd/E13222_01/wls/docs90/javadocs/", // CommonJ and weblogic.* packages
|
||||
"https://docs.jboss.org/jbossas/javadoc/4.0.5/connector/", // org.jboss.resource.*
|
||||
"https://docs.jboss.org/hibernate/orm/5.6/javadocs/",
|
||||
"https://eclipse.dev/aspectj/doc/released/aspectj5rt-api",
|
||||
"https://www.quartz-scheduler.org/api/2.3.0/",
|
||||
|
||||
@@ -50,7 +50,7 @@ public class CheckstyleConventions {
|
||||
project.getPlugins().apply(CheckstylePlugin.class);
|
||||
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g"));
|
||||
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
|
||||
checkstyle.setToolVersion("10.15.0");
|
||||
checkstyle.setToolVersion("10.16.0");
|
||||
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
|
||||
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
|
||||
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
|
||||
|
||||
@@ -9,7 +9,7 @@ case "$1" in
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/21.0.2%2B14/bellsoft-jdk21.0.2+14-linux-amd64.tar.gz"
|
||||
;;
|
||||
java23)
|
||||
echo "https://download.java.net/java/early_access/jdk23/10/GPL/openjdk-23-ea+10_linux-x64_bin.tar.gz"
|
||||
echo "https://download.java.net/java/early_access/jdk23/17/GPL/openjdk-23-ea+17_linux-x64_bin.tar.gz"
|
||||
;;
|
||||
*)
|
||||
echo $"Unknown java version"
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ curl https://raw.githubusercontent.com/spring-io/concourse-java-scripts/v0.0.4/c
|
||||
|
||||
mkdir -p /opt/openjdk
|
||||
pushd /opt/openjdk > /dev/null
|
||||
for jdk in java17 java21 java22
|
||||
for jdk in java17 java21 java23
|
||||
do
|
||||
JDK_URL=$( /get-jdk-url.sh $jdk )
|
||||
mkdir $jdk
|
||||
|
||||
+1
-3
@@ -5,9 +5,7 @@ anchors:
|
||||
password: ((github-ci-release-token))
|
||||
branch: ((branch))
|
||||
gradle-enterprise-task-params: &gradle-enterprise-task-params
|
||||
GRADLE_ENTERPRISE_ACCESS_KEY: ((gradle_enterprise_secret_access_key))
|
||||
GRADLE_ENTERPRISE_CACHE_USERNAME: ((gradle_enterprise_cache_user.username))
|
||||
GRADLE_ENTERPRISE_CACHE_PASSWORD: ((gradle_enterprise_cache_user.password))
|
||||
DEVELOCITY_ACCESS_KEY: ((gradle_enterprise_secret_access_key))
|
||||
sonatype-task-params: &sonatype-task-params
|
||||
SONATYPE_USERNAME: ((sonatype-username))
|
||||
SONATYPE_PASSWORD: ((sonatype-password))
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
antora:
|
||||
extensions:
|
||||
- require: '@springio/antora-extensions'
|
||||
root_component_name: 'framework'
|
||||
site:
|
||||
title: Spring Framework
|
||||
url: https://docs.spring.io/spring-framework/reference
|
||||
robots: allow
|
||||
git:
|
||||
ensure_git_suffix: false
|
||||
content:
|
||||
sources:
|
||||
- url: https://github.com/spring-projects/spring-framework
|
||||
# Refname matching:
|
||||
# https://docs.antora.org/antora/latest/playbook/content-refname-matching/
|
||||
branches: ['main', '{6..9}.+({0..9}).x']
|
||||
tags: ['v{6..9}.+({0..9}).+({0..9})?(-{RC,M}*)', '!(v6.0.{0..8})', '!(v6.0.0-{RC,M}{0..9})']
|
||||
start_path: framework-docs
|
||||
asciidoc:
|
||||
extensions:
|
||||
- '@asciidoctor/tabs'
|
||||
- '@springio/asciidoctor-extensions'
|
||||
- '@springio/asciidoctor-extensions/include-code-extension'
|
||||
attributes:
|
||||
page-stackoverflow-url: https://stackoverflow.com/questions/tagged/spring
|
||||
page-pagination: ''
|
||||
hide-uri-scheme: '@'
|
||||
tabs-sync-option: '@'
|
||||
include-java: 'example$docs-src/main/java/org/springframework/docs'
|
||||
urls:
|
||||
latest_version_segment_strategy: redirect:to
|
||||
latest_version_segment: ''
|
||||
redirect_facility: httpd
|
||||
runtime:
|
||||
log:
|
||||
failure_level: warn
|
||||
ui:
|
||||
bundle:
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.15/ui-bundle.zip
|
||||
@@ -10,27 +10,10 @@ apply from: "${rootDir}/gradle/ide.gradle"
|
||||
apply from: "${rootDir}/gradle/publications.gradle"
|
||||
|
||||
antora {
|
||||
version = '3.2.0-alpha.2'
|
||||
playbook = 'cached-antora-playbook.yml'
|
||||
playbookProvider {
|
||||
repository = 'spring-projects/spring-framework'
|
||||
branch = 'docs-build'
|
||||
path = 'lib/antora/templates/per-branch-antora-playbook.yml'
|
||||
checkLocalBranch = true
|
||||
}
|
||||
options = ['--clean', '--stacktrace']
|
||||
options = [clean: true, fetch: !project.gradle.startParameter.offline, stacktrace: true]
|
||||
environment = [
|
||||
'ALGOLIA_API_KEY': '82c7ead946afbac3cf98c32446154691',
|
||||
'ALGOLIA_APP_ID': '244V8V9FGG',
|
||||
'ALGOLIA_INDEX_NAME': 'framework-docs'
|
||||
]
|
||||
dependencies = [
|
||||
'@antora/atlas-extension': '1.0.0-alpha.1',
|
||||
'@antora/collector-extension': '1.0.0-alpha.3',
|
||||
'@asciidoctor/tabs': '1.0.0-beta.3',
|
||||
'@opendevise/antora-release-line-extension': '1.0.0',
|
||||
'@springio/antora-extensions': '1.8.2',
|
||||
'@springio/asciidoctor-extensions': '1.0.0-alpha.9'
|
||||
'BUILD_REFNAME': 'HEAD',
|
||||
'BUILD_VERSION': project.version,
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,10 @@ you can do so. However, you should consider the following issues:
|
||||
since the CGLIB proxy instance is created through Objenesis. Only if your JVM does
|
||||
not allow for constructor bypassing, you might see double invocations and
|
||||
corresponding debug log entries from Spring's AOP support.
|
||||
* Your CGLIB proxy usage may face limitations with the JDK 9+ platform module system.
|
||||
As a typical case, you cannot create a CGLIB proxy for a class from the `java.lang`
|
||||
package when deploying on the module path. Such cases require a JVM bootstrap flag
|
||||
`--add-opens=java.base/java.lang=ALL-UNNAMED` which is not available for modules.
|
||||
|
||||
To force the use of CGLIB proxies, set the value of the `proxy-target-class` attribute
|
||||
of the `<aop:config>` element to true, as follows:
|
||||
|
||||
@@ -326,6 +326,19 @@ However, this is not a best practice and flagging the preferred constructor with
|
||||
|
||||
In case you are working on a code base that you cannot modify, you can set the {spring-framework-api}/beans/factory/support/AbstractBeanDefinition.html#PREFERRED_CONSTRUCTORS_ATTRIBUTE[`preferredConstructors` attribute] on the related bean definition to indicate which constructor should be used.
|
||||
|
||||
[[aot.bestpractices.custom-arguments]]
|
||||
=== Avoid Creating Bean with Custom Arguments
|
||||
|
||||
Spring AOT detects what needs to be done to create a bean and translates that in generated code using an instance supplier.
|
||||
The container also supports creating a bean with {spring-framework-api}++/beans/factory/BeanFactory.html#getBean(java.lang.String,java.lang.Object...)++[custom arguments] that leads to several issues with AOT:
|
||||
|
||||
. The custom arguments require dynamic introspection of a matching constructor or factory method.
|
||||
Those arguments cannot be detected by AOT, so the necessary reflection hints will have to be provided manually.
|
||||
. By-passing the instance supplier means that all other optimizations after creation are skipped as well.
|
||||
For instance, autowiring on fields and methods will be skipped as they are handled in the instance supplier.
|
||||
|
||||
Rather than having prototype-scoped beans created with custom arguments, we recommend a manual factory pattern where a bean is responsible for the creation of the instance.
|
||||
|
||||
[[aot.bestpractices.factory-bean]]
|
||||
=== FactoryBean
|
||||
|
||||
|
||||
@@ -1,33 +1,16 @@
|
||||
[[beans-annotation-config]]
|
||||
= Annotation-based Container Configuration
|
||||
|
||||
.Are annotations better than XML for configuring Spring?
|
||||
****
|
||||
The introduction of annotation-based configuration raised the question of whether this
|
||||
approach is "`better`" than XML. The short answer is "`it depends.`" The long answer is
|
||||
that each approach has its pros and cons, and, usually, it is up to the developer to
|
||||
decide which strategy suits them better. Due to the way they are defined, annotations
|
||||
provide a lot of context in their declaration, leading to shorter and more concise
|
||||
configuration. However, XML excels at wiring up components without touching their source
|
||||
code or recompiling them. Some developers prefer having the wiring close to the source
|
||||
while others argue that annotated classes are no longer POJOs and, furthermore, that the
|
||||
configuration becomes decentralized and harder to control.
|
||||
Spring provides comprehensive support for annotation-based configuration, operating on
|
||||
metadata in the component class itself by using annotations on the relevant class,
|
||||
method, or field declaration. As mentioned in
|
||||
xref:core/beans/factory-extension.adoc#beans-factory-extension-bpp-examples-aabpp[Example: The `AutowiredAnnotationBeanPostProcessor`],
|
||||
Spring uses `BeanPostProcessors` in conjunction with annotations to make the core IOC
|
||||
container aware of specific annotations.
|
||||
|
||||
No matter the choice, Spring can accommodate both styles and even mix them together.
|
||||
It is worth pointing out that through its xref:core/beans/java.adoc[JavaConfig] option, Spring lets
|
||||
annotations be used in a non-invasive way, without touching the target components'
|
||||
source code and that, in terms of tooling, all configuration styles are supported by
|
||||
{spring-site-tools}[Spring Tools] for Eclipse, Visual Studio Code, and Theia.
|
||||
****
|
||||
|
||||
An alternative to XML setup is provided by annotation-based configuration, which relies
|
||||
on bytecode metadata for wiring up components instead of XML declarations. Instead of
|
||||
using XML to describe a bean wiring, the developer moves the configuration into the
|
||||
component class itself by using annotations on the relevant class, method, or field
|
||||
declaration. As mentioned in xref:core/beans/factory-extension.adoc#beans-factory-extension-bpp-examples-aabpp[Example: The `AutowiredAnnotationBeanPostProcessor`], using a
|
||||
`BeanPostProcessor` in conjunction with annotations is a common means of extending the
|
||||
Spring IoC container. For example, the xref:core/beans/annotation-config/autowired.adoc[`@Autowired`]
|
||||
annotation provides the same capabilities as described in xref:core/beans/dependencies/factory-autowire.adoc[Autowiring Collaborators] but
|
||||
For example, the xref:core/beans/annotation-config/autowired.adoc[`@Autowired`]
|
||||
annotation provides the same capabilities as described in
|
||||
xref:core/beans/dependencies/factory-autowire.adoc[Autowiring Collaborators] but
|
||||
with more fine-grained control and wider applicability. In addition, Spring provides
|
||||
support for JSR-250 annotations, such as `@PostConstruct` and `@PreDestroy`, as well as
|
||||
support for JSR-330 (Dependency Injection for Java) annotations contained in the
|
||||
@@ -36,13 +19,16 @@ can be found in the xref:core/beans/standard-annotations.adoc[relevant section].
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Annotation injection is performed before XML injection. Thus, the XML configuration
|
||||
overrides the annotations for properties wired through both approaches.
|
||||
Annotation injection is performed before external property injection. Thus, external
|
||||
configuration (e.g. XML-specified bean properties) effectively overrides the annotations
|
||||
for properties when wired through mixed approaches.
|
||||
====
|
||||
|
||||
As always, you can register the post-processors as individual bean definitions, but they
|
||||
can also be implicitly registered by including the following tag in an XML-based Spring
|
||||
configuration (notice the inclusion of the `context` namespace):
|
||||
Technically, you can register the post-processors as individual bean definitions, but they
|
||||
are implicitly registered in an `AnnotationConfigApplicationContext` already.
|
||||
|
||||
In an XML-based Spring setup, you may include the following configuration tag to enable
|
||||
mixing and matching with annotation-based configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
|
||||
@@ -186,6 +186,15 @@ implementation type, consider declaring the most specific return type on your fa
|
||||
method (at least as specific as required by the injection points referring to your bean).
|
||||
====
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
As of 4.3, `@Autowired` also considers self references for injection (that is, references
|
||||
back to the bean that is currently injected). Note that self injection is a fallback.
|
||||
In practice, you should use self references as a last resort only (for example, for
|
||||
calling other methods on the same instance through the bean's transactional proxy).
|
||||
Consider factoring out the affected methods to a separate delegate bean in such a scenario.
|
||||
====
|
||||
|
||||
You can also instruct Spring to provide all beans of a particular type from the
|
||||
`ApplicationContext` by adding the `@Autowired` annotation to a field or method that
|
||||
expects an array of that type, as the following example shows:
|
||||
@@ -268,6 +277,12 @@ use the same bean class). `@Order` values may influence priorities at injection
|
||||
but be aware that they do not influence singleton startup order, which is an
|
||||
orthogonal concern determined by dependency relationships and `@DependsOn` declarations.
|
||||
|
||||
Note that `@Order` annotations on configuration classes just influence the evaluation
|
||||
order within the overall set of configuration classes on startup. Such configuration-level
|
||||
order values do not affect the contained `@Bean` methods at all. For bean-level ordering,
|
||||
each `@Bean` method needs to have its own `@Order` annotation which applies within a
|
||||
set of multiple matches for the specific bean type (as returned by the factory method).
|
||||
|
||||
Note that the standard `jakarta.annotation.Priority` annotation is not available at the
|
||||
`@Bean` level, since it cannot be declared on methods. Its semantics can be modeled
|
||||
through `@Order` values in combination with `@Primary` on a single bean for each type.
|
||||
|
||||
@@ -2,36 +2,28 @@
|
||||
= Container Overview
|
||||
|
||||
The `org.springframework.context.ApplicationContext` interface represents the Spring IoC
|
||||
container and is responsible for instantiating, configuring, and assembling the
|
||||
beans. The container gets its instructions on what objects to
|
||||
instantiate, configure, and assemble by reading configuration metadata. The
|
||||
configuration metadata is represented in XML, Java annotations, or Java code. It lets
|
||||
you express the objects that compose your application and the rich interdependencies
|
||||
between those objects.
|
||||
container and is responsible for instantiating, configuring, and assembling the beans.
|
||||
The container gets its instructions on the components to instantiate, configure, and
|
||||
assemble by reading configuration metadata. The configuration metadata can be represented
|
||||
as annotated component classes, configuration classes with factory methods, or external
|
||||
XML files or Groovy scripts. With either format, you may compose your application and the
|
||||
rich interdependencies between those components.
|
||||
|
||||
Several implementations of the `ApplicationContext` interface are supplied
|
||||
with Spring. In stand-alone applications, it is common to create an
|
||||
instance of
|
||||
{spring-framework-api}/context/support/ClassPathXmlApplicationContext.html[`ClassPathXmlApplicationContext`]
|
||||
or {spring-framework-api}/context/support/FileSystemXmlApplicationContext.html[`FileSystemXmlApplicationContext`].
|
||||
While XML has been the traditional format for defining configuration metadata, you can
|
||||
instruct the container to use Java annotations or code as the metadata format by
|
||||
providing a small amount of XML configuration to declaratively enable support for these
|
||||
additional metadata formats.
|
||||
Several implementations of the `ApplicationContext` interface are part of core Spring.
|
||||
In stand-alone applications, it is common to create an instance of
|
||||
{spring-framework-api}/context/annotation/AnnotationConfigApplicationContext.html[`AnnotationConfigApplicationContext`]
|
||||
or {spring-framework-api}/context/support/ClassPathXmlApplicationContext.html[`ClassPathXmlApplicationContext`].
|
||||
|
||||
In most application scenarios, explicit user code is not required to instantiate one or
|
||||
more instances of a Spring IoC container. For example, in a web application scenario, a
|
||||
simple eight (or so) lines of boilerplate web descriptor XML in the `web.xml` file
|
||||
of the application typically suffices (see
|
||||
more instances of a Spring IoC container. For example, in a plain web application scenario,
|
||||
a simple boilerplate web descriptor XML in the `web.xml` file of the application suffices (see
|
||||
xref:core/beans/context-introduction.adoc#context-create[Convenient ApplicationContext Instantiation for Web Applications]).
|
||||
If you use the {spring-site-tools}[Spring Tools for Eclipse] (an Eclipse-powered
|
||||
development environment), you can easily create this boilerplate configuration with a
|
||||
few mouse clicks or keystrokes.
|
||||
In a Spring Boot scenario, the application context is implicitly bootstrapped for you
|
||||
based on common setup conventions.
|
||||
|
||||
The following diagram shows a high-level view of how Spring works. Your application classes
|
||||
are combined with configuration metadata so that, after the `ApplicationContext` is
|
||||
created and initialized, you have a fully configured and executable system or
|
||||
application.
|
||||
created and initialized, you have a fully configured and executable system or application.
|
||||
|
||||
.The Spring IoC container
|
||||
image::container-magic.png[]
|
||||
@@ -43,33 +35,25 @@ image::container-magic.png[]
|
||||
|
||||
As the preceding diagram shows, the Spring IoC container consumes a form of
|
||||
configuration metadata. This configuration metadata represents how you, as an
|
||||
application developer, tell the Spring container to instantiate, configure, and assemble
|
||||
the objects in your application.
|
||||
application developer, tell the Spring container to instantiate, configure,
|
||||
and assemble the components in your application.
|
||||
|
||||
Configuration metadata is traditionally supplied in a simple and intuitive XML format,
|
||||
which is what most of this chapter uses to convey key concepts and features of the
|
||||
Spring IoC container.
|
||||
|
||||
NOTE: XML-based metadata is not the only allowed form of configuration metadata.
|
||||
The Spring IoC container itself is totally decoupled from the format in which this
|
||||
configuration metadata is actually written. These days, many developers choose
|
||||
xref:core/beans/java.adoc[Java-based configuration] for their Spring applications.
|
||||
|
||||
For information about using other forms of metadata with the Spring container, see:
|
||||
xref:core/beans/java.adoc[Java-based configuration] for their Spring applications:
|
||||
|
||||
* xref:core/beans/annotation-config.adoc[Annotation-based configuration]: define beans using
|
||||
annotation-based configuration metadata.
|
||||
annotation-based configuration metadata on your application's component classes.
|
||||
* xref:core/beans/java.adoc[Java-based configuration]: define beans external to your application
|
||||
classes by using Java rather than XML files. To use these features, see the
|
||||
classes by using Java-based configuration classes. To use these features, see the
|
||||
{spring-framework-api}/context/annotation/Configuration.html[`@Configuration`],
|
||||
{spring-framework-api}/context/annotation/Bean.html[`@Bean`],
|
||||
{spring-framework-api}/context/annotation/Import.html[`@Import`],
|
||||
and {spring-framework-api}/context/annotation/DependsOn.html[`@DependsOn`] annotations.
|
||||
|
||||
Spring configuration consists of at least one and typically more than one bean
|
||||
definition that the container must manage. XML-based configuration metadata configures these
|
||||
beans as `<bean/>` elements inside a top-level `<beans/>` element. Java
|
||||
configuration typically uses `@Bean`-annotated methods within a `@Configuration` class.
|
||||
Spring configuration consists of at least one and typically more than one bean definition
|
||||
that the container must manage. Java configuration typically uses `@Bean`-annotated
|
||||
methods within a `@Configuration` class, each corresponding to one bean definition.
|
||||
|
||||
These bean definitions correspond to the actual objects that make up your application.
|
||||
Typically, you define service layer objects, persistence layer objects such as
|
||||
@@ -79,7 +63,14 @@ Typically, one does not configure fine-grained domain objects in the container,
|
||||
it is usually the responsibility of repositories and business logic to create and load
|
||||
domain objects.
|
||||
|
||||
The following example shows the basic structure of XML-based configuration metadata:
|
||||
|
||||
|
||||
[[beans-factory-xml]]
|
||||
=== XML as an External Configuration DSL
|
||||
|
||||
XML-based configuration metadata configures these beans as `<bean/>` elements inside
|
||||
a top-level `<beans/>` element. The following example shows the basic structure of
|
||||
XML-based configuration metadata:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@@ -110,14 +101,9 @@ The value of the `id` attribute can be used to refer to collaborating objects. T
|
||||
for referring to collaborating objects is not shown in this example. See
|
||||
xref:core/beans/dependencies.adoc[Dependencies] for more information.
|
||||
|
||||
|
||||
|
||||
[[beans-factory-instantiation]]
|
||||
== Instantiating a Container
|
||||
|
||||
The location path or paths
|
||||
supplied to an `ApplicationContext` constructor are resource strings that let
|
||||
the container load configuration metadata from a variety of external resources, such
|
||||
For instantiating a container, the location path or paths to the XML resource files
|
||||
need to be supplied to a `ClassPathXmlApplicationContext` constructor that let the
|
||||
container load configuration metadata from a variety of external resources, such
|
||||
as the local file system, the Java `CLASSPATH`, and so on.
|
||||
|
||||
[tabs]
|
||||
@@ -209,9 +195,9 @@ xref:core/beans/dependencies.adoc[Dependencies].
|
||||
It can be useful to have bean definitions span multiple XML files. Often, each individual
|
||||
XML configuration file represents a logical layer or module in your architecture.
|
||||
|
||||
You can use the application context constructor to load bean definitions from all these
|
||||
You can use the `ClassPathXmlApplicationContext` constructor to load bean definitions from
|
||||
XML fragments. This constructor takes multiple `Resource` locations, as was shown in the
|
||||
xref:core/beans/basics.adoc#beans-factory-instantiation[previous section]. Alternatively,
|
||||
xref:core/beans/basics.adoc#beans-factory-xml[previous section]. Alternatively,
|
||||
use one or more occurrences of the `<import/>` element to load bean definitions from
|
||||
another file or files. The following example shows how to do so:
|
||||
|
||||
@@ -259,7 +245,7 @@ configuration features beyond plain bean definitions are available in a selectio
|
||||
of XML namespaces provided by Spring -- for example, the `context` and `util` namespaces.
|
||||
|
||||
|
||||
[[groovy-bean-definition-dsl]]
|
||||
[[beans-factory-groovy]]
|
||||
=== The Groovy Bean Definition DSL
|
||||
|
||||
As a further example for externalized configuration metadata, bean definitions can also
|
||||
@@ -420,4 +406,3 @@ a dependency on a specific bean through metadata (such as an autowiring annotati
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -234,6 +234,10 @@ For details about the mechanism for supplying arguments to the constructor (if r
|
||||
and setting object instance properties after the object is constructed, see
|
||||
xref:core/beans/dependencies/factory-collaborators.adoc[Injecting Dependencies].
|
||||
|
||||
NOTE: In the case of constructor arguments, the container can select a corresponding
|
||||
constructor among several overloaded constructors. That said, to avoid ambiguities,
|
||||
it is recommended to keep your constructor signatures as straightforward as possible.
|
||||
|
||||
|
||||
[[beans-factory-class-static-factory-method]]
|
||||
=== Instantiation with a Static Factory Method
|
||||
@@ -294,6 +298,24 @@ For details about the mechanism for supplying (optional) arguments to the factor
|
||||
and setting object instance properties after the object is returned from the factory,
|
||||
see xref:core/beans/dependencies/factory-properties-detailed.adoc[Dependencies and Configuration in Detail].
|
||||
|
||||
NOTE: In the case of factory method arguments, the container can select a corresponding
|
||||
method among several overloaded methods of the same name. That said, to avoid ambiguities,
|
||||
it is recommended to keep your factory method signatures as straightforward as possible.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
A typical problematic case with factory method overloading is Mockito with its many
|
||||
overloads of the `mock` method. Choose the most specific variant of `mock` possible:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="clientService" class="org.mockito.Mockito" factory-method="mock">
|
||||
<constructor-arg type="java.lang.Class" value="examples.ClientService"/>
|
||||
<constructor-arg type="java.lang.String" value="clientService"/>
|
||||
</bean>
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
[[beans-factory-class-instance-factory-method]]
|
||||
=== Instantiation by Using an Instance Factory Method
|
||||
@@ -416,8 +438,8 @@ Kotlin::
|
||||
======
|
||||
|
||||
This approach shows that the factory bean itself can be managed and configured through
|
||||
dependency injection (DI). See xref:core/beans/dependencies/factory-properties-detailed.adoc[Dependencies and Configuration in Detail]
|
||||
.
|
||||
dependency injection (DI).
|
||||
See xref:core/beans/dependencies/factory-properties-detailed.adoc[Dependencies and Configuration in Detail].
|
||||
|
||||
NOTE: In Spring documentation, "factory bean" refers to a bean that is configured in the
|
||||
Spring container and that creates objects through an
|
||||
@@ -444,5 +466,3 @@ cases into account and returns the type of object that a `BeanFactory.getBean` c
|
||||
going to return for the same bean name.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -55,33 +55,34 @@ The preceding `AppConfig` class is equivalent to the following Spring `<beans/>`
|
||||
</beans>
|
||||
----
|
||||
|
||||
.Full @Configuration vs "`lite`" @Bean mode?
|
||||
.@Configuration classes with or without local calls between @Bean methods?
|
||||
****
|
||||
When `@Bean` methods are declared within classes that are not annotated with
|
||||
`@Configuration`, they are referred to as being processed in a "`lite`" mode. Bean methods
|
||||
declared on a bean that is not annotated with `@Configuration` are considered to be "`lite`",
|
||||
with a different primary purpose of the containing class and a `@Bean` method
|
||||
being a sort of bonus there. For example, service components may expose management views
|
||||
to the container through an additional `@Bean` method on each applicable component class.
|
||||
In such scenarios, `@Bean` methods are a general-purpose factory method mechanism.
|
||||
|
||||
Unlike full `@Configuration`, lite `@Bean` methods cannot declare inter-bean dependencies.
|
||||
Instead, they operate on their containing component's internal state and, optionally, on
|
||||
arguments that they may declare. Such a `@Bean` method should therefore not invoke other
|
||||
`@Bean` methods. Each such method is literally only a factory method for a particular
|
||||
bean reference, without any special runtime semantics. The positive side-effect here is
|
||||
that no CGLIB subclassing has to be applied at runtime, so there are no limitations in
|
||||
terms of class design (that is, the containing class may be `final` and so forth).
|
||||
|
||||
In common scenarios, `@Bean` methods are to be declared within `@Configuration` classes,
|
||||
ensuring that "`full`" mode is always used and that cross-method references therefore
|
||||
get redirected to the container's lifecycle management. This prevents the same
|
||||
`@Bean` method from accidentally being invoked through a regular Java call, which helps
|
||||
to reduce subtle bugs that can be hard to track down when operating in "`lite`" mode.
|
||||
ensuring that full configuration class processing applies and that cross-method
|
||||
references therefore get redirected to the container's lifecycle management.
|
||||
This prevents the same `@Bean` method from accidentally being invoked through a regular
|
||||
Java method call, which helps to reduce subtle bugs that can be hard to track down.
|
||||
|
||||
When `@Bean` methods are declared within classes that are not annotated with
|
||||
`@Configuration` - or when `@Configuration(proxyBeanMethods=false)` is declared -,
|
||||
they are referred to as being processed in a "lite" mode. In such scenarios,
|
||||
`@Bean` methods are effectively a general-purpose factory method mechanism without
|
||||
special runtime processing (that is, without generating a CGLIB subclass for it).
|
||||
A custom Java call to such a method will not get intercepted by the container and
|
||||
therefore behaves just like a regular method call, creating a new instance every time
|
||||
rather than reusing an existing singleton (or scoped) instance for the given bean.
|
||||
|
||||
As a consequence, `@Bean` methods on classes without runtime proxying are not meant to
|
||||
declare inter-bean dependencies at all. Instead, they are expected to operate on their
|
||||
containing component's fields and, optionally, on arguments that a factory method may
|
||||
declare in order to receive autowired collaborators. Such a `@Bean` method therefore
|
||||
never needs to invoke other `@Bean` methods; every such call can be expressed through
|
||||
a factory method argument instead. The positive side-effect here is that no CGLIB
|
||||
subclassing has to be applied at runtime, reducing the overhead and the footprint.
|
||||
****
|
||||
|
||||
The `@Bean` and `@Configuration` annotations are discussed in depth in the following sections.
|
||||
First, however, we cover the various ways of creating a spring container by using
|
||||
First, however, we cover the various ways of creating a Spring container by using
|
||||
Java-based configuration.
|
||||
|
||||
|
||||
|
||||
+9
-7
@@ -74,10 +74,11 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
Used at the class level as above, the annotation indicates a default for all methods of
|
||||
the declaring class (as well as its subclasses). Alternatively, each method can be
|
||||
annotated individually. See xref:data-access/transaction/declarative/annotations.adoc#transaction-declarative-annotations-method-visibility[method visibility] for
|
||||
further details on which methods Spring considers transactional. Note that a class-level
|
||||
Used at the class level as above, the annotation indicates a default for all methods
|
||||
of the declaring class (as well as its subclasses). Alternatively, each method can be
|
||||
annotated individually. See
|
||||
xref:data-access/transaction/declarative/annotations.adoc#transaction-declarative-annotations-method-visibility[method visibility]
|
||||
for further details on which methods Spring considers transactional. Note that a class-level
|
||||
annotation does not apply to ancestor classes up the class hierarchy; in such a scenario,
|
||||
inherited methods need to be locally redeclared in order to participate in a
|
||||
subclass-level annotation.
|
||||
@@ -436,9 +437,10 @@ properties of the `@Transactional` annotation:
|
||||
| Optional array of exception name patterns that must not cause rollback.
|
||||
|===
|
||||
|
||||
TIP: See xref:data-access/transaction/declarative/rolling-back.adoc#transaction-declarative-rollback-rules[Rollback rules] for further details
|
||||
on rollback rule semantics, patterns, and warnings regarding possible unintentional
|
||||
matches for pattern-based rollback rules.
|
||||
TIP: See
|
||||
xref:data-access/transaction/declarative/rolling-back.adoc#transaction-declarative-rollback-rules[Rollback rules]
|
||||
for further details on rollback rule semantics, patterns, and warnings
|
||||
regarding possible unintentional matches for pattern-based rollback rules.
|
||||
|
||||
Currently, you cannot have explicit control over the name of a transaction, where 'name'
|
||||
means the transaction name that appears in a transaction monitor and in logging output.
|
||||
|
||||
+27
-1
@@ -24,8 +24,8 @@ Vavr's `Try` method to trigger transaction rollbacks when it returns a 'Failure'
|
||||
This allows you to handle functional-style errors using Try and have the transaction
|
||||
automatically rolled back in case of a failure. For more information on Vavr's Try,
|
||||
refer to the https://docs.vavr.io/#_try[official Vavr documentation].
|
||||
|
||||
Here's an example of how to use Vavr's Try with a transactional method:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
@@ -42,6 +42,32 @@ Java::
|
||||
----
|
||||
======
|
||||
|
||||
As of Spring Framework 6.1, there is also special treatment of `CompletableFuture`
|
||||
(and general `Future`) return values, triggering a rollback for such a handle if it
|
||||
was exceptionally completed at the time of being returned from the original method.
|
||||
This is intended for `@Async` methods where the actual method implementation may
|
||||
need to comply with a `CompletableFuture` signature (auto-adapted to an actual
|
||||
asynchronous handle for a call to the proxy by `@Async` processing at runtime),
|
||||
preferring exposure in the returned handle rather than rethrowing an exception:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
@Transactional @Async
|
||||
public CompletableFuture<String> myTransactionalMethod() {
|
||||
try {
|
||||
return CompletableFuture.completedFuture(delegate.myDataAccessOperation());
|
||||
}
|
||||
catch (DataAccessException ex) {
|
||||
return CompletableFuture.failedFuture(ex);
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Checked exceptions that are thrown from a transactional method do not result in a rollback
|
||||
in the default configuration. You can configure exactly which `Exception` types mark a
|
||||
transaction for rollback, including checked exceptions by specifying _rollback rules_.
|
||||
|
||||
@@ -24,7 +24,7 @@ To create the archive, two additional JVM flags must be specified:
|
||||
* `-Dspring.context.exit=onRefresh`: starts and then immediately exits your Spring
|
||||
application as described above
|
||||
|
||||
To create a CDS archive, your JDK must have a base image. If you add the flags above to
|
||||
To create a CDS archive, your JDK/JRE must have a base image. If you add the flags above to
|
||||
your startup script, you may get a warning that looks like this:
|
||||
|
||||
[source,shell,indent=0,subs="verbatim"]
|
||||
@@ -32,7 +32,8 @@ your startup script, you may get a warning that looks like this:
|
||||
-XX:ArchiveClassesAtExit is unsupported when base CDS archive is not loaded. Run with -Xlog:cds for more info.
|
||||
----
|
||||
|
||||
The base CDS archive can be created by issuing the following command:
|
||||
The base CDS archive is usually provided out-of-the-box, but can also be created if needed by issuing the following
|
||||
command:
|
||||
|
||||
[source,shell,indent=0,subs="verbatim"]
|
||||
----
|
||||
@@ -44,6 +45,9 @@ The base CDS archive can be created by issuing the following command:
|
||||
Once the archive is available, add `-XX:SharedArchiveFile=application.jsa` to your startup
|
||||
script to use it, assuming an `application.jsa` file in the working directory.
|
||||
|
||||
To check if the CDS cache is effective, you can use (for testing purposes only, not in production) `-Xshare:on` which
|
||||
prints an error message and exits if CDS can't be enabled.
|
||||
|
||||
To figure out how effective the cache is, you can enable class loading logs by adding
|
||||
an extra attribute: `-Xlog:class+load:file=cds.log`. This creates a `cds.log` with every
|
||||
attempt to load a class and its source. Classes that are loaded from the cache should have
|
||||
@@ -58,8 +62,11 @@ a "shared objects file" source, as shown in the following example:
|
||||
[0.065s][info][class,load] org.springframework.context.MessageSource source: shared objects file (top)
|
||||
----
|
||||
|
||||
TIP: If you have a large number of classes that are not loaded from the cache, make sure that
|
||||
the JDK and classpath used by the commands that create the archive and start the application
|
||||
are identical. Note also that to effectively cache classes, the classpath should be specified
|
||||
as a list of JARs containing those classes, and avoid the usage of directories and `*`
|
||||
wildcard characters.
|
||||
If CDS can't be enabled or if you have a large number of classes that are not loaded from the cache, make sure that
|
||||
the following conditions are fulfilled when creating and using the archive:
|
||||
|
||||
- The very same JVM must used.
|
||||
- The classpath must be specified as a list of JARs, and avoid the usage of directories and `*` wildcard characters.
|
||||
- The timestamps of the JARs must be preserved.
|
||||
- When using the archive, the classpath must be the same than the one used to create the archive, in the same order.
|
||||
Additional JARs or directories can be specified *at the end* (but won't be cached).
|
||||
|
||||
@@ -71,8 +71,34 @@ This can be done with `method(HttpMethod)` or with the convenience methods `get(
|
||||
Next, the request URI can be specified with the `uri` methods.
|
||||
This step is optional and can be skipped if the `RestClient` is configured with a default URI.
|
||||
The URL is typically specified as a `String`, with optional URI template variables.
|
||||
String URLs are encoded by default, but this can be changed by building a client with a custom `uriBuilderFactory`.
|
||||
The following example configures a GET request to `https://example.com/orders/42`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
int id = 42;
|
||||
restClient.get()
|
||||
.uri("https://example.com/orders/{id}", id)
|
||||
....
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val id = 42
|
||||
restClient.get()
|
||||
.uri("https://example.com/orders/{id}", id)
|
||||
...
|
||||
----
|
||||
======
|
||||
|
||||
A function can also be used for more controls, such as specifying xref:web/webmvc/mvc-uri-building.adoc[request parameters].
|
||||
|
||||
String URLs are encoded by default, but this can be changed by building a client with a custom `uriBuilderFactory`.
|
||||
The URL can also be provided with a function or as a `java.net.URI`, both of which are not encoded.
|
||||
For more details on working with and encoding URIs, see xref:web/webmvc/mvc-uri-building.adoc[URI Links].
|
||||
|
||||
@@ -468,6 +494,9 @@ If no request factory is specified when the `RestClient` was built, it will use
|
||||
Otherwise, if the `java.net.http` module is loaded, it will use Java's `HttpClient`.
|
||||
Finally, it will resort to the simple default.
|
||||
|
||||
TIP: Note that the `SimpleClientHttpRequestFactory` may raise an exception when accessing the status of a response that represents an error (e.g. 401).
|
||||
If this is an issue, use any of the alternative request factories.
|
||||
|
||||
[[rest-webclient]]
|
||||
== `WebClient`
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ existing Java application.
|
||||
|
||||
The Spring Framework provides a dedicated `ApplicationContext` that supports a Groovy-based
|
||||
Bean Definition DSL. For more details, see
|
||||
xref:core/beans/basics.adoc#groovy-bean-definition-dsl[The Groovy Bean Definition DSL].
|
||||
xref:core/beans/basics.adoc#beans-factory-groovy[The Groovy Bean Definition DSL].
|
||||
|
||||
Further support for Groovy, including beans written in Groovy, refreshable script beans,
|
||||
and more is available in xref:languages/dynamic.adoc[Dynamic Language Support].
|
||||
|
||||
@@ -166,7 +166,7 @@ public class SampleConfiguration {
|
||||
@Bean
|
||||
@NotNull
|
||||
public SampleBean sampleBean$demo_kotlin_internal_test() {
|
||||
return new SampleBean();
|
||||
return new SampleBean();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
+2
-1
@@ -111,7 +111,8 @@ a mock service with Mockito:
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="accountService" class="org.mockito.Mockito" factory-method="mock">
|
||||
<constructor-arg value="org.example.AccountService"/>
|
||||
<constructor-arg type="java.lang.Class" value="org.example.AccountService"/>
|
||||
<constructor-arg type="java.lang.String" value="accountService"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[[spring-mvc-test-vs-end-to-end-integration-tests]]
|
||||
= MockMvc vs End-to-End Tests
|
||||
|
||||
MockMVc is built on Servlet API mock implementations from the
|
||||
MockMvc is built on Servlet API mock implementations from the
|
||||
`spring-test` module and does not rely on a running container. Therefore, there are
|
||||
some differences when compared to full end-to-end integration tests with an actual
|
||||
client and a live server running.
|
||||
|
||||
+10
-32
@@ -1,38 +1,16 @@
|
||||
[[spring-mvc-test-vs-streaming-response]]
|
||||
= Streaming Responses
|
||||
|
||||
The best way to test streaming responses such as Server-Sent Events is through the
|
||||
<<WebTestClient>> which can be used as a test client to connect to a `MockMvc` instance
|
||||
to perform tests on Spring MVC controllers without a running server. For example:
|
||||
You can use `WebTestClient` to test xref:testing/webtestclient.adoc#webtestclient-stream[streaming responses]
|
||||
such as Server-Sent Events. However, `MockMvcWebTestClient` doesn't support infinite
|
||||
streams because there is no way to cancel the server stream from the client side.
|
||||
To test infinite streams, you'll need to
|
||||
xref:testing/webtestclient.adoc#webtestclient-server-config[bind to] a running server,
|
||||
or when using Spring Boot,
|
||||
{spring-boot-docs}/spring-boot-features.html#boot-features-testing-spring-boot-applications-testing-with-running-server[test with a running server].
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
WebTestClient client = MockMvcWebTestClient.bindToController(new SseController()).build();
|
||||
|
||||
FluxExchangeResult<Person> exchangeResult = client.get()
|
||||
.uri("/persons")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectHeader().contentType("text/event-stream")
|
||||
.returnResult(Person.class);
|
||||
|
||||
// Use StepVerifier from Project Reactor to test the streaming response
|
||||
|
||||
StepVerifier.create(exchangeResult.getResponseBody())
|
||||
.expectNext(new Person("N0"), new Person("N1"), new Person("N2"))
|
||||
.expectNextCount(4)
|
||||
.consumeNextWith(person -> assertThat(person.getName()).endsWith("7"))
|
||||
.thenCancel()
|
||||
.verify();
|
||||
----
|
||||
======
|
||||
|
||||
`WebTestClient` can also connect to a live server and perform full end-to-end integration
|
||||
tests. This is also supported in Spring Boot where you can
|
||||
{spring-boot-docs}/spring-boot-features.html#boot-features-testing-spring-boot-applications-testing-with-running-server[test a running server].
|
||||
`MockMvcWebTestClient` does support asynchronous responses, and even streaming responses.
|
||||
The limitation is that it can't influence the server to stop, and therefore the server
|
||||
must finish writing the response on its own.
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
= Context Configuration with Groovy Scripts
|
||||
|
||||
To load an `ApplicationContext` for your tests by using Groovy scripts that use the
|
||||
xref:core/beans/basics.adoc#groovy-bean-definition-dsl[Groovy Bean Definition DSL], you can annotate
|
||||
xref:core/beans/basics.adoc#beans-factory-groovy[Groovy Bean Definition DSL], you can annotate
|
||||
your test class with `@ContextConfiguration` and configure the `locations` or `value`
|
||||
attribute with an array that contains the resource locations of Groovy scripts. Resource
|
||||
lookup semantics for Groovy scripts are the same as those described for
|
||||
|
||||
@@ -672,13 +672,13 @@ Kotlin::
|
||||
val result = client.get().uri("/persons/1")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody(Person.class)
|
||||
.returnResult();
|
||||
.expectBody<Person>()
|
||||
.returnResult()
|
||||
|
||||
// For a response without a body
|
||||
val result = client.get().uri("/path")
|
||||
.exchange()
|
||||
.expectBody().isEmpty();
|
||||
.expectBody().isEmpty()
|
||||
----
|
||||
======
|
||||
|
||||
|
||||
@@ -4,10 +4,13 @@
|
||||
`spring-webflux` depends on `reactor-core` and uses it internally to compose asynchronous
|
||||
logic and to provide Reactive Streams support. Generally, WebFlux APIs return `Flux` or
|
||||
`Mono` (since those are used internally) and leniently accept any Reactive Streams
|
||||
`Publisher` implementation as input. The use of `Flux` versus `Mono` is important, because
|
||||
it helps to express cardinality -- for example, whether a single or multiple asynchronous
|
||||
values are expected, and that can be essential for making decisions (for example, when
|
||||
encoding or decoding HTTP messages).
|
||||
`Publisher` implementation as input.
|
||||
When a `Publisher` is provided, it can be treated only as a stream with unknown semantics (0..N).
|
||||
If, however, the semantics are known, you should wrap it with `Flux` or `Mono.from(Publisher)` instead
|
||||
of passing the raw `Publisher`.
|
||||
The use of `Flux` versus `Mono` is important, because it helps to express cardinality --
|
||||
for example, whether a single or multiple asynchronous values are expected,
|
||||
and that can be essential for making decisions (for example, when encoding or decoding HTTP messages).
|
||||
|
||||
For annotated controllers, WebFlux transparently adapts to the reactive library chosen by
|
||||
the application. This is done with the help of the
|
||||
@@ -15,15 +18,3 @@ the application. This is done with the help of the
|
||||
provides pluggable support for reactive library and other asynchronous types. The registry
|
||||
has built-in support for RxJava 3, Kotlin coroutines and SmallRye Mutiny, but you can
|
||||
register others, too.
|
||||
|
||||
For functional APIs (such as <<webflux-fn>>, the `WebClient`, and others), the general rules
|
||||
for WebFlux APIs apply -- `Flux` and `Mono` as return values and a Reactive Streams
|
||||
`Publisher` as input. When a `Publisher`, whether custom or from another reactive library,
|
||||
is provided, it can be treated only as a stream with unknown semantics (0..N). If, however,
|
||||
the semantics are known, you can wrap it with `Flux` or `Mono.from(Publisher)` instead
|
||||
of passing the raw `Publisher`.
|
||||
|
||||
For example, given a `Publisher` that is not a `Mono`, the Jackson JSON message writer
|
||||
expects multiple values. If the media type implies an infinite stream (for example,
|
||||
`application/json+stream`), values are written and flushed individually. Otherwise,
|
||||
values are buffered into a list and rendered as a JSON array.
|
||||
@@ -5,14 +5,14 @@
|
||||
|
||||
A common requirement for REST services is to include details in the body of error
|
||||
responses. The Spring Framework supports the "Problem Details for HTTP APIs"
|
||||
specification, {rfc-site}/rfc7807.html[RFC 7807].
|
||||
specification, {rfc-site}/rfc9457.html[RFC 9457].
|
||||
|
||||
The following are the main abstractions for this support:
|
||||
|
||||
- `ProblemDetail` -- representation for an RFC 7807 problem detail; a simple container
|
||||
- `ProblemDetail` -- representation for an RFC 9457 problem detail; a simple container
|
||||
for both standard fields defined in the spec, and for non-standard ones.
|
||||
- `ErrorResponse` -- contract to expose HTTP error response details including HTTP
|
||||
status, response headers, and a body in the format of RFC 7807; this allows exceptions to
|
||||
status, response headers, and a body in the format of RFC 9457; this allows exceptions to
|
||||
encapsulate and expose the details of how they map to an HTTP response. All Spring WebFlux
|
||||
exceptions implement this.
|
||||
- `ErrorResponseException` -- basic `ErrorResponse` implementation that others
|
||||
@@ -28,7 +28,7 @@ and any `ErrorResponseException`, and renders an error response with a body.
|
||||
[.small]#xref:web/webmvc/mvc-ann-rest-exceptions.adoc#mvc-ann-rest-exceptions-render[See equivalent in the Servlet stack]#
|
||||
|
||||
You can return `ProblemDetail` or `ErrorResponse` from any `@ExceptionHandler` or from
|
||||
any `@RequestMapping` method to render an RFC 7807 response. This is processed as follows:
|
||||
any `@RequestMapping` method to render an RFC 9457 response. This is processed as follows:
|
||||
|
||||
- The `status` property of `ProblemDetail` determines the HTTP status.
|
||||
- The `instance` property of `ProblemDetail` is set from the current URL path, if not
|
||||
@@ -37,7 +37,7 @@ already set.
|
||||
"application/problem+json" over "application/json" when rendering a `ProblemDetail`,
|
||||
and also falls back on it if no compatible media type is found.
|
||||
|
||||
To enable RFC 7807 responses for Spring WebFlux exceptions and for any
|
||||
To enable RFC 9457 responses for Spring WebFlux exceptions and for any
|
||||
`ErrorResponseException`, extend `ResponseEntityExceptionHandler` and declare it as an
|
||||
xref:web/webflux/controller/ann-advice.adoc[@ControllerAdvice] in Spring configuration. The handler
|
||||
has an `@ExceptionHandler` method that handles any `ErrorResponse` exception, which
|
||||
@@ -50,7 +50,7 @@ use a protected method to map any exception to a `ProblemDetail`.
|
||||
== Non-Standard Fields
|
||||
[.small]#xref:web/webmvc/mvc-ann-rest-exceptions.adoc#mvc-ann-rest-exceptions-non-standard[See equivalent in the Servlet stack]#
|
||||
|
||||
You can extend an RFC 7807 response with non-standard fields in one of two ways.
|
||||
You can extend an RFC 9457 response with non-standard fields in one of two ways.
|
||||
|
||||
One, insert into the "properties" `Map` of `ProblemDetail`. When using the Jackson
|
||||
library, the Spring Framework registers `ProblemDetailJacksonMixin` that ensures this
|
||||
|
||||
+14
-3
@@ -4,9 +4,20 @@
|
||||
[.small]#xref:web/webmvc/mvc-controller/ann-methods/return-types.adoc[See equivalent in the Servlet stack]#
|
||||
|
||||
The following table shows the supported controller method return values. Note that reactive
|
||||
types from libraries such as Reactor, RxJava, xref:web-reactive.adoc#webflux-reactive-libraries[or other] are
|
||||
types from libraries such as Reactor, RxJava, xref:web/webflux-reactive-libraries.adoc[or other] are
|
||||
generally supported for all return values.
|
||||
|
||||
For return types like `Flux`, when multiple values are expected, elements are streamed as they come
|
||||
and are not buffered. This is the default behavior, as keeping a potentially large amount of elements in memory
|
||||
is not efficient. If the media type implies an infinite stream (for example,
|
||||
`application/json+stream`), values are written and flushed individually. Otherwise,
|
||||
values are written individually and the flushing happens separately.
|
||||
|
||||
NOTE: If an error happens while an element is encoded to JSON, the response might have been written to and committed already
|
||||
and it is impossible at that point to render a proper error response.
|
||||
In some cases, applications can choose to trade memory efficiency for better handling such errors by buffering elements and encoding them all at once.
|
||||
Controllers can then return a `Flux<List<B>>`; Reactor provides a dedicated operator for that, `Flux#collectList()`.
|
||||
|
||||
[cols="1,2", options="header"]
|
||||
|===
|
||||
| Controller method return value | Description
|
||||
@@ -24,11 +35,11 @@ generally supported for all return values.
|
||||
| For returning a response with headers and no body.
|
||||
|
||||
| `ErrorResponse`
|
||||
| To render an RFC 7807 error response with details in the body,
|
||||
| To render an RFC 9457 error response with details in the body,
|
||||
see xref:web/webflux/ann-rest-exceptions.adoc[Error Responses]
|
||||
|
||||
| `ProblemDetail`
|
||||
| To render an RFC 7807 error response with details in the body,
|
||||
| To render an RFC 9457 error response with details in the body,
|
||||
see xref:web/webflux/ann-rest-exceptions.adoc[Error Responses]
|
||||
|
||||
| `String`
|
||||
|
||||
@@ -3,28 +3,40 @@
|
||||
|
||||
[.small]#xref:web/webmvc/mvc-controller/ann-validation.adoc[See equivalent in the Servlet stack]#
|
||||
|
||||
Spring WebFlux has built-in xref:core/validation/validator.adoc[Validation] support for
|
||||
`@RequestMapping` methods, including the option to use
|
||||
xref:core/validation/beanvalidation.adoc[Java Bean Validation].
|
||||
The validation support works on two levels.
|
||||
Spring WebFlux has built-in xref:core/validation/validator.adoc[Validation] for
|
||||
`@RequestMapping` methods, including xref:core/validation/beanvalidation.adoc[Java Bean Validation].
|
||||
Validation may be applied at one of two levels:
|
||||
|
||||
First, resolvers for
|
||||
xref:web/webflux/controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
|
||||
1. xref:web/webflux/controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
|
||||
xref:web/webflux/controller/ann-methods/requestbody.adoc[@RequestBody], and
|
||||
xref:web/webflux/controller/ann-methods/multipart-forms.adoc[@RequestPart] method
|
||||
parameters perform validation if the parameter has Jakarta's `@Valid` or Spring's
|
||||
`@Validated` annotation, and raise `MethodArgumentNotValidException` if necessary.
|
||||
Alternatively, you can handle the errors in the controller method by adding an
|
||||
`Errors` or `BindingResult` method parameter immediately after the validated one.
|
||||
xref:web/webflux/controller/ann-methods/multipart-forms.adoc[@RequestPart] argument
|
||||
resolvers validate a method argument individually if the method parameter is annotated
|
||||
with Jakarta `@Valid` or Spring's `@Validated`, _AND_ there is no `Errors` or
|
||||
`BindingResult` parameter immediately after, _AND_ method validation is not needed (to be
|
||||
discussed next). The exception raised in this case is `MethodArgumentNotValidException`.
|
||||
|
||||
Second, if {bean-validation-site}[Java Bean Validation] is present _AND_ any method
|
||||
parameter has `@Constraint` annotations, then method validation is applied instead,
|
||||
raising `HandlerMethodValidationException` if necessary. For this case you can still add
|
||||
an `Errors` or `BindingResult` method parameter to handle validation errors within the
|
||||
controller method, but if other method arguments have validation errors then
|
||||
`HandlerMethodValidationException` is raised instead. Method validation can apply
|
||||
to the return value if the method is annotated with `@Valid` or with `@Constraint`
|
||||
annotations.
|
||||
2. When `@Constraint` annotations such as `@Min`, `@NotBlank` and others are declared
|
||||
directly on method parameters, or on the method (for the return value), then method
|
||||
validation must be applied, and that supersedes validation at the method argument level
|
||||
because method validation covers both method parameter constraints and nested constraints
|
||||
via `@Valid`. The exception raised in this case is `HandlerMethodValidationException`.
|
||||
|
||||
Applications must handle both `MethodArgumentNotValidException` and
|
||||
`HandlerMethodValidationException` as either may be raised depending on the controller
|
||||
method signature. The two exceptions, however are designed to be very similar, and can be
|
||||
handled with almost identical code. The main difference is that the former is for a single
|
||||
object while the latter is for a list of method parameters.
|
||||
|
||||
NOTE: `@Valid` is not a constraint annotation, but rather for nested constraints within
|
||||
an Object. Therefore, by itself `@Valid` does not lead to method validation. `@NotNull`
|
||||
on the other hand is a constraint, and adding it to an `@Valid` parameter leads to method
|
||||
validation. For nullability specifically, you may also use the `required` flag of
|
||||
`@RequestBody` or `@ModelAttribute`.
|
||||
|
||||
Method validation may be used in combination with `Errors` or `BindingResult` method
|
||||
parameters. However, the controller method is called only if all validation errors are on
|
||||
method parameters with an `Errors` immediately after. If there are validation errors on
|
||||
any other method parameter then `HandlerMethodValidationException` is raised.
|
||||
|
||||
You can configure a `Validator` globally through the
|
||||
xref:web/webflux/config.adoc#webflux-config-validation[WebMvc config], or locally
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
|
||||
A common requirement for REST services is to include details in the body of error
|
||||
responses. The Spring Framework supports the "Problem Details for HTTP APIs"
|
||||
specification, {rfc-site}/rfc7807[RFC 7807].
|
||||
specification, {rfc-site}/rfc9457[RFC 9457].
|
||||
|
||||
The following are the main abstractions for this support:
|
||||
|
||||
- `ProblemDetail` -- representation for an RFC 7807 problem detail; a simple container
|
||||
- `ProblemDetail` -- representation for an RFC 9457 problem detail; a simple container
|
||||
for both standard fields defined in the spec, and for non-standard ones.
|
||||
- `ErrorResponse` -- contract to expose HTTP error response details including HTTP
|
||||
status, response headers, and a body in the format of RFC 7807; this allows exceptions to
|
||||
status, response headers, and a body in the format of RFC 9457; this allows exceptions to
|
||||
encapsulate and expose the details of how they map to an HTTP response. All Spring MVC
|
||||
exceptions implement this.
|
||||
- `ErrorResponseException` -- basic `ErrorResponse` implementation that others
|
||||
@@ -28,7 +28,7 @@ and any `ErrorResponseException`, and renders an error response with a body.
|
||||
[.small]#xref:web/webflux/ann-rest-exceptions.adoc#webflux-ann-rest-exceptions-render[See equivalent in the Reactive stack]#
|
||||
|
||||
You can return `ProblemDetail` or `ErrorResponse` from any `@ExceptionHandler` or from
|
||||
any `@RequestMapping` method to render an RFC 7807 response. This is processed as follows:
|
||||
any `@RequestMapping` method to render an RFC 9457 response. This is processed as follows:
|
||||
|
||||
- The `status` property of `ProblemDetail` determines the HTTP status.
|
||||
- The `instance` property of `ProblemDetail` is set from the current URL path, if not
|
||||
@@ -37,7 +37,7 @@ already set.
|
||||
"application/problem+json" over "application/json" when rendering a `ProblemDetail`,
|
||||
and also falls back on it if no compatible media type is found.
|
||||
|
||||
To enable RFC 7807 responses for Spring WebFlux exceptions and for any
|
||||
To enable RFC 9457 responses for Spring WebFlux exceptions and for any
|
||||
`ErrorResponseException`, extend `ResponseEntityExceptionHandler` and declare it as an
|
||||
xref:web/webmvc/mvc-controller/ann-advice.adoc[@ControllerAdvice] in Spring configuration. The handler
|
||||
has an `@ExceptionHandler` method that handles any `ErrorResponse` exception, which
|
||||
@@ -50,7 +50,7 @@ use a protected method to map any exception to a `ProblemDetail`.
|
||||
== Non-Standard Fields
|
||||
[.small]#xref:web/webflux/ann-rest-exceptions.adoc#webflux-ann-rest-exceptions-non-standard[See equivalent in the Reactive stack]#
|
||||
|
||||
You can extend an RFC 7807 response with non-standard fields in one of two ways.
|
||||
You can extend an RFC 9457 response with non-standard fields in one of two ways.
|
||||
|
||||
One, insert into the "properties" `Map` of `ProblemDetail`. When using the Jackson
|
||||
library, the Spring Framework registers `ProblemDetailJacksonMixin` that ensures this
|
||||
|
||||
@@ -52,14 +52,10 @@ The following example shows how to achieve the same configuration in XML:
|
||||
</mvc:interceptors>
|
||||
----
|
||||
|
||||
NOTE: Interceptors are not ideally suited as a security layer due to the potential
|
||||
for a mismatch with annotated controller path matching, which can also match trailing
|
||||
slashes and path extensions transparently, along with other path matching options. Many
|
||||
of these options have been deprecated but the potential for a mismatch remains.
|
||||
Generally, we recommend using Spring Security which includes a dedicated
|
||||
https://docs.spring.io/spring-security/reference/servlet/integrations/mvc.html#mvc-requestmatcher[MvcRequestMatcher]
|
||||
to align with Spring MVC path matching and also has a security firewall that blocks many
|
||||
unwanted characters in URL paths.
|
||||
WARNING: Interceptors are not ideally suited as a security layer due to the potential for
|
||||
a mismatch with annotated controller path matching. Generally, we recommend using Spring
|
||||
Security, or alternatively a similar approach integrated with the Servlet filter chain,
|
||||
and applied as early as possible.
|
||||
|
||||
NOTE: The XML config declares interceptors as `MappedInterceptor` beans, and those are in
|
||||
turn detected by any `HandlerMapping` bean, including those from other frameworks.
|
||||
|
||||
+2
-2
@@ -228,11 +228,11 @@ level, xref:web/webmvc/mvc-servlet/exceptionhandlers.adoc[HandlerExceptionResolv
|
||||
See xref:web/webmvc/mvc-controller/ann-methods/responseentity.adoc[ResponseEntity].
|
||||
|
||||
| `ErrorResponse`
|
||||
| To render an RFC 7807 error response with details in the body,
|
||||
| To render an RFC 9457 error response with details in the body,
|
||||
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
|
||||
|
||||
| `ProblemDetail`
|
||||
| To render an RFC 7807 error response with details in the body,
|
||||
| To render an RFC 9457 error response with details in the body,
|
||||
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
|
||||
|
||||
| `String`
|
||||
|
||||
+7
@@ -37,6 +37,13 @@ Kotlin::
|
||||
all controller methods. This is the effect of `@RestController`, which is nothing more
|
||||
than a meta-annotation marked with `@Controller` and `@ResponseBody`.
|
||||
|
||||
A `Resource` object can be returned for file content, copying the `InputStream`
|
||||
content of the provided resource to the response `OutputStream`. Note that the
|
||||
`InputStream` should be lazily retrieved by the `Resource` handle in order to reliably
|
||||
close it after it has been copied to the response. If you are using `InputStreamResource`
|
||||
for such a purpose, make sure to construct it with an on-demand `InputStreamSource`
|
||||
(e.g. through a lambda expression that retrieves the actual `InputStream`).
|
||||
|
||||
You can use `@ResponseBody` with reactive types.
|
||||
See xref:web/webmvc/mvc-ann-async.adoc[Asynchronous Requests] and xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-reactive-types[Reactive Types] for more details.
|
||||
|
||||
|
||||
+10
@@ -32,6 +32,16 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
The body will usually be provided as a value object to be rendered to a corresponding
|
||||
response representation (e.g. JSON) by one of the registered `HttpMessageConverters`.
|
||||
|
||||
A `ResponseEntity<Resource>` can be returned for file content, copying the `InputStream`
|
||||
content of the provided resource to the response `OutputStream`. Note that the
|
||||
`InputStream` should be lazily retrieved by the `Resource` handle in order to reliably
|
||||
close it after it has been copied to the response. If you are using `InputStreamResource`
|
||||
for such a purpose, make sure to construct it with an on-demand `InputStreamSource`
|
||||
(e.g. through a lambda expression that retrieves the actual `InputStream`).
|
||||
|
||||
Spring MVC supports using a single value xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-reactive-types[reactive type]
|
||||
to produce the `ResponseEntity` asynchronously, and/or single and multi-value reactive
|
||||
types for the body. This allows the following types of async responses:
|
||||
|
||||
+2
-2
@@ -23,11 +23,11 @@ supported for all return values.
|
||||
| For returning a response with headers and no body.
|
||||
|
||||
| `ErrorResponse`
|
||||
| To render an RFC 7807 error response with details in the body,
|
||||
| To render an RFC 9457 error response with details in the body,
|
||||
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
|
||||
|
||||
| `ProblemDetail`
|
||||
| To render an RFC 7807 error response with details in the body,
|
||||
| To render an RFC 9457 error response with details in the body,
|
||||
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
|
||||
|
||||
| `String`
|
||||
|
||||
@@ -3,28 +3,40 @@
|
||||
|
||||
[.small]#xref:web/webflux/controller/ann-validation.adoc[See equivalent in the Reactive stack]#
|
||||
|
||||
Spring MVC has built-in xref:core/validation/validator.adoc[Validation] support for
|
||||
`@RequestMapping` methods, including the option to use
|
||||
xref:core/validation/beanvalidation.adoc[Java Bean Validation].
|
||||
The validation support works on two levels.
|
||||
Spring MVC has built-in xref:core/validation/validator.adoc[validation] for
|
||||
`@RequestMapping` methods, including xref:core/validation/beanvalidation.adoc[Java Bean Validation].
|
||||
Validation may be applied at one of two levels:
|
||||
|
||||
First, resolvers for
|
||||
xref:web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
|
||||
1. xref:web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
|
||||
xref:web/webmvc/mvc-controller/ann-methods/requestbody.adoc[@RequestBody], and
|
||||
xref:web/webmvc/mvc-controller/ann-methods/multipart-forms.adoc[@RequestPart] method
|
||||
parameters perform validation if the parameter has Jakarta's `@Valid` or Spring's
|
||||
`@Validated` annotation, and raise `MethodArgumentNotValidException` if necessary.
|
||||
Alternatively, you can handle the errors in the controller method by adding an
|
||||
`Errors` or `BindingResult` method parameter immediately after the validated one.
|
||||
xref:web/webmvc/mvc-controller/ann-methods/multipart-forms.adoc[@RequestPart] argument
|
||||
resolvers validate a method argument individually if the method parameter is annotated
|
||||
with Jakarta `@Valid` or Spring's `@Validated`, _AND_ there is no `Errors` or
|
||||
`BindingResult` parameter immediately after, _AND_ method validation is not needed (to be
|
||||
discussed next). The exception raised in this case is `MethodArgumentNotValidException`.
|
||||
|
||||
Second, if {bean-validation-site}[Java Bean Validation] is present _AND_ any method
|
||||
parameter has `@Constraint` annotations, then method validation is applied instead,
|
||||
raising `HandlerMethodValidationException` if necessary. For this case you can still add
|
||||
an `Errors` or `BindingResult` method parameter to handle validation errors within the
|
||||
controller method, but if other method arguments have validation errors then
|
||||
`HandlerMethodValidationException` is raised instead. Method validation can apply
|
||||
to the return value if the method is annotated with `@Valid` or with `@Constraint`
|
||||
annotations.
|
||||
2. When `@Constraint` annotations such as `@Min`, `@NotBlank` and others are declared
|
||||
directly on method parameters, or on the method (for the return value), then method
|
||||
validation must be applied, and that supersedes validation at the method argument level
|
||||
because method validation covers both method parameter constraints and nested constraints
|
||||
via `@Valid`. The exception raised in this case is `HandlerMethodValidationException`.
|
||||
|
||||
Applications must handle both `MethodArgumentNotValidException` and
|
||||
`HandlerMethodValidationException` as either may be raised depending on the controller
|
||||
method signature. The two exceptions, however are designed to be very similar, and can be
|
||||
handled with almost identical code. The main difference is that the former is for a single
|
||||
object while the latter is for a list of method parameters.
|
||||
|
||||
NOTE: `@Valid` is not a constraint annotation, but rather for nested constraints within
|
||||
an Object. Therefore, by itself `@Valid` does not lead to method validation. `@NotNull`
|
||||
on the other hand is a constraint, and adding it to an `@Valid` parameter leads to method
|
||||
validation. For nullability specifically, you may also use the `required` flag of
|
||||
`@RequestBody` or `@ModelAttribute`.
|
||||
|
||||
Method validation may be used in combination with `Errors` or `BindingResult` method
|
||||
parameters. However, the controller method is called only if all validation errors are on
|
||||
method parameters with an `Errors` immediately after. If there are validation errors on
|
||||
any other method parameter then `HandlerMethodValidationException` is raised.
|
||||
|
||||
You can configure a `Validator` globally through the
|
||||
xref:web/webmvc/mvc-config/validation.adoc[WebMvc config], or locally through an
|
||||
|
||||
+18
-23
@@ -1,34 +1,29 @@
|
||||
[[mvc-handlermapping-interceptor]]
|
||||
= Interception
|
||||
|
||||
All `HandlerMapping` implementations support handler interceptors that are useful when
|
||||
you want to apply specific functionality to certain requests -- for example, checking for
|
||||
a principal. Interceptors must implement `HandlerInterceptor` from the
|
||||
`org.springframework.web.servlet` package with three methods that should provide enough
|
||||
flexibility to do all kinds of pre-processing and post-processing:
|
||||
All `HandlerMapping` implementations support handler interception which is useful when
|
||||
you want to apply functionality across requests. A `HandlerInterceptor` can implement the
|
||||
following:
|
||||
|
||||
* `preHandle(..)`: Before the actual handler is run
|
||||
* `postHandle(..)`: After the handler is run
|
||||
* `afterCompletion(..)`: After the complete request has finished
|
||||
* `preHandle(..)` -- callback before the actual handler is run that returns a boolean.
|
||||
If the method returns `true`, execution continues; if it returns `false`, the rest of the
|
||||
execution chain is bypassed and the handler is not called.
|
||||
* `postHandle(..)` -- callback after the handler is run.
|
||||
* `afterCompletion(..)` -- callback after the complete request has finished.
|
||||
|
||||
The `preHandle(..)` method returns a boolean value. You can use this method to break or
|
||||
continue the processing of the execution chain. When this method returns `true`, the
|
||||
handler execution chain continues. When it returns false, the `DispatcherServlet`
|
||||
assumes the interceptor itself has taken care of requests (and, for example, rendered an
|
||||
appropriate view) and does not continue executing the other interceptors and the actual
|
||||
handler in the execution chain.
|
||||
NOTE: For `@ResponseBody` and `ResponseEntity` controller methods, the response is written
|
||||
and committed within the `HandlerAdapter`, before `postHandle` is called. That means it is
|
||||
too late to change the response, such as to add an extra header. You can implement
|
||||
`ResponseBodyAdvice` and declare it as an
|
||||
xref:web/webmvc/mvc-controller/ann-advice.adoc[Controller Advice] bean or configure it
|
||||
directly on `RequestMappingHandlerAdapter`.
|
||||
|
||||
See xref:web/webmvc/mvc-config/interceptors.adoc[Interceptors] in the section on MVC configuration for examples of how to
|
||||
configure interceptors. You can also register them directly by using setters on individual
|
||||
`HandlerMapping` implementations.
|
||||
|
||||
`postHandle` method is less useful with `@ResponseBody` and `ResponseEntity` methods for
|
||||
which the response is written and committed within the `HandlerAdapter` and before
|
||||
`postHandle`. That means it is too late to make any changes to the response, such as adding
|
||||
an extra header. For such scenarios, you can implement `ResponseBodyAdvice` and either
|
||||
declare it as an xref:web/webmvc/mvc-controller/ann-advice.adoc[Controller Advice] bean or configure it directly on
|
||||
`RequestMappingHandlerAdapter`.
|
||||
|
||||
|
||||
|
||||
WARNING: Interceptors are not ideally suited as a security layer due to the potential for
|
||||
a mismatch with annotated controller path matching. Generally, we recommend using Spring
|
||||
Security, or alternatively a similar approach integrated with the Servlet filter chain,
|
||||
and applied as early as possible.
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"antora": "3.2.0-alpha.4",
|
||||
"@antora/atlas-extension": "1.0.0-alpha.2",
|
||||
"@antora/collector-extension": "1.0.0-alpha.3",
|
||||
"@asciidoctor/tabs": "1.0.0-beta.6",
|
||||
"@springio/antora-extensions": "1.11.1",
|
||||
"@springio/asciidoctor-extensions": "1.0.0-alpha.10"
|
||||
}
|
||||
}
|
||||
@@ -8,20 +8,20 @@ javaPlatform {
|
||||
|
||||
dependencies {
|
||||
api(platform("com.fasterxml.jackson:jackson-bom:2.15.4"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.12.5"))
|
||||
api(platform("io.netty:netty-bom:4.1.108.Final"))
|
||||
api(platform("io.micrometer:micrometer-bom:1.12.6"))
|
||||
api(platform("io.netty:netty-bom:4.1.109.Final"))
|
||||
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
|
||||
api(platform("io.projectreactor:reactor-bom:2023.0.5"))
|
||||
api(platform("io.projectreactor:reactor-bom:2023.0.6"))
|
||||
api(platform("io.rsocket:rsocket-bom:1.1.3"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.20"))
|
||||
api(platform("org.apache.groovy:groovy-bom:4.0.21"))
|
||||
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
|
||||
api(platform("org.assertj:assertj-bom:3.25.3"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:12.0.7"))
|
||||
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.7"))
|
||||
api(platform("org.eclipse.jetty:jetty-bom:12.0.9"))
|
||||
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.9"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3"))
|
||||
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.0"))
|
||||
api(platform("org.junit:junit-bom:5.10.2"))
|
||||
api(platform("org.mockito:mockito-bom:5.11.0"))
|
||||
api(platform("org.mockito:mockito-bom:5.12.0"))
|
||||
|
||||
constraints {
|
||||
api("com.fasterxml:aalto-xml:1.3.2")
|
||||
@@ -55,9 +55,9 @@ dependencies {
|
||||
api("io.r2dbc:r2dbc-spi:1.0.0.RELEASE")
|
||||
api("io.reactivex.rxjava3:rxjava:3.1.8")
|
||||
api("io.smallrye.reactive:mutiny:1.10.0")
|
||||
api("io.undertow:undertow-core:2.3.12.Final")
|
||||
api("io.undertow:undertow-servlet:2.3.12.Final")
|
||||
api("io.undertow:undertow-websockets-jsr:2.3.12.Final")
|
||||
api("io.undertow:undertow-core:2.3.13.Final")
|
||||
api("io.undertow:undertow-servlet:2.3.13.Final")
|
||||
api("io.undertow:undertow-websockets-jsr:2.3.13.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")
|
||||
@@ -103,19 +103,19 @@ dependencies {
|
||||
api("org.apache.httpcomponents.client5:httpclient5:5.3.1")
|
||||
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.2.4")
|
||||
api("org.apache.poi:poi-ooxml:5.2.5")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.20")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.20")
|
||||
api("org.apache.tomcat:tomcat-util:10.1.20")
|
||||
api("org.apache.tomcat:tomcat-websocket:10.1.20")
|
||||
api("org.aspectj:aspectjrt:1.9.22")
|
||||
api("org.aspectj:aspectjtools:1.9.22")
|
||||
api("org.aspectj:aspectjweaver:1.9.22")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.24")
|
||||
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.24")
|
||||
api("org.apache.tomcat:tomcat-util:10.1.24")
|
||||
api("org.apache.tomcat:tomcat-websocket:10.1.24")
|
||||
api("org.aspectj:aspectjrt:1.9.22.1")
|
||||
api("org.aspectj:aspectjtools:1.9.22.1")
|
||||
api("org.aspectj:aspectjweaver:1.9.22.1")
|
||||
api("org.awaitility:awaitility:4.2.0")
|
||||
api("org.bouncycastle:bcpkix-jdk18on:1.72")
|
||||
api("org.codehaus.jettison:jettison:1.5.4")
|
||||
api("org.crac:crac:1.4.0")
|
||||
api("org.dom4j:dom4j:2.1.4")
|
||||
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.3")
|
||||
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.4")
|
||||
api("org.eclipse.persistence:org.eclipse.persistence.jpa:3.0.4")
|
||||
api("org.eclipse:yasson:2.0.4")
|
||||
api("org.ehcache:ehcache:3.10.8")
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
version=6.1.6-SNAPSHOT
|
||||
version=6.1.8
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
|
||||
@@ -6,6 +6,7 @@ tasks.findByName("dokkaHtmlPartial")?.configure {
|
||||
classpath.from(sourceSets["main"].runtimeClasspath)
|
||||
externalDocumentationLink {
|
||||
url.set(new URL("https://docs.spring.io/spring-framework/docs/current/javadoc-api/"))
|
||||
packageListUrl.set(new URL("https://docs.spring.io/spring-framework/docs/current/javadoc-api/element-list"))
|
||||
}
|
||||
externalDocumentationLink {
|
||||
url.set(new URL("https://projectreactor.io/docs/core/release/api/"))
|
||||
@@ -21,6 +22,7 @@ tasks.findByName("dokkaHtmlPartial")?.configure {
|
||||
}
|
||||
externalDocumentationLink {
|
||||
url.set(new URL("https://javadoc.io/doc/jakarta.servlet/jakarta.servlet-api/latest/"))
|
||||
packageListUrl.set(new URL("https://javadoc.io/doc/jakarta.servlet/jakarta.servlet-api/latest/element-list"))
|
||||
}
|
||||
externalDocumentationLink {
|
||||
url.set(new URL("https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/"))
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
plugins {
|
||||
id "com.gradle.enterprise" version "3.16.2"
|
||||
id "io.spring.ge.conventions" version "0.0.15"
|
||||
id "com.gradle.develocity" version "3.17.2"
|
||||
id "io.spring.ge.conventions" version "0.0.17"
|
||||
id "org.gradle.toolchains.foojay-resolver-convention" version "0.7.0"
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ rootProject.children.each {project ->
|
||||
}
|
||||
|
||||
settings.gradle.projectsLoaded {
|
||||
gradleEnterprise {
|
||||
develocity {
|
||||
buildScan {
|
||||
File buildDir = settings.gradle.rootProject
|
||||
.getLayout().getBuildDirectory().getAsFile().get()
|
||||
|
||||
+24
-9
@@ -41,6 +41,7 @@ import org.aspectj.weaver.tools.PointcutParameter;
|
||||
import org.aspectj.weaver.tools.PointcutParser;
|
||||
import org.aspectj.weaver.tools.PointcutPrimitive;
|
||||
import org.aspectj.weaver.tools.ShadowMatch;
|
||||
import org.aspectj.weaver.tools.UnsupportedPointcutPrimitiveException;
|
||||
|
||||
import org.springframework.aop.ClassFilter;
|
||||
import org.springframework.aop.IntroductionAwareMethodMatcher;
|
||||
@@ -115,6 +116,8 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
||||
@Nullable
|
||||
private transient PointcutExpression pointcutExpression;
|
||||
|
||||
private transient boolean pointcutParsingFailed = false;
|
||||
|
||||
private transient Map<Method, ShadowMatch> shadowMatchCache = new ConcurrentHashMap<>(32);
|
||||
|
||||
|
||||
@@ -170,25 +173,30 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
||||
|
||||
@Override
|
||||
public ClassFilter getClassFilter() {
|
||||
obtainPointcutExpression();
|
||||
checkExpression();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodMatcher getMethodMatcher() {
|
||||
obtainPointcutExpression();
|
||||
checkExpression();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check whether this pointcut is ready to match,
|
||||
* lazily building the underlying AspectJ pointcut expression.
|
||||
* Check whether this pointcut is ready to match.
|
||||
*/
|
||||
private PointcutExpression obtainPointcutExpression() {
|
||||
private void checkExpression() {
|
||||
if (getExpression() == null) {
|
||||
throw new IllegalStateException("Must set property 'expression' before attempting to match");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazily build the underlying AspectJ pointcut expression.
|
||||
*/
|
||||
private PointcutExpression obtainPointcutExpression() {
|
||||
if (this.pointcutExpression == null) {
|
||||
this.pointcutClassLoader = determinePointcutClassLoader();
|
||||
this.pointcutExpression = buildPointcutExpression(this.pointcutClassLoader);
|
||||
@@ -265,10 +273,13 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
||||
|
||||
@Override
|
||||
public boolean matches(Class<?> targetClass) {
|
||||
PointcutExpression pointcutExpression = obtainPointcutExpression();
|
||||
if (this.pointcutParsingFailed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
try {
|
||||
return pointcutExpression.couldMatchJoinPointsInType(targetClass);
|
||||
return obtainPointcutExpression().couldMatchJoinPointsInType(targetClass);
|
||||
}
|
||||
catch (ReflectionWorldException ex) {
|
||||
logger.debug("PointcutExpression matching rejected target class - trying fallback expression", ex);
|
||||
@@ -279,6 +290,12 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException | IllegalStateException | UnsupportedPointcutPrimitiveException ex) {
|
||||
this.pointcutParsingFailed = true;
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Pointcut parser rejected expression [" + getExpression() + "]: " + ex);
|
||||
}
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
logger.debug("PointcutExpression matching rejected target class", ex);
|
||||
}
|
||||
@@ -287,7 +304,6 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
||||
|
||||
@Override
|
||||
public boolean matches(Method method, Class<?> targetClass, boolean hasIntroductions) {
|
||||
obtainPointcutExpression();
|
||||
ShadowMatch shadowMatch = getTargetShadowMatch(method, targetClass);
|
||||
|
||||
// Special handling for this, target, @this, @target, @annotation
|
||||
@@ -325,7 +341,6 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
|
||||
|
||||
@Override
|
||||
public boolean matches(Method method, Class<?> targetClass, Object... args) {
|
||||
obtainPointcutExpression();
|
||||
ShadowMatch shadowMatch = getTargetShadowMatch(method, targetClass);
|
||||
|
||||
// Bind Spring AOP proxy to AspectJ "this" and Spring AOP target to AspectJ target,
|
||||
|
||||
+2
-30
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,7 +18,6 @@ package org.springframework.aop.aspectj.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.StringTokenizer;
|
||||
@@ -56,8 +55,6 @@ import org.springframework.lang.Nullable;
|
||||
*/
|
||||
public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFactory {
|
||||
|
||||
private static final String AJC_MAGIC = "ajc$";
|
||||
|
||||
private static final Class<?>[] ASPECTJ_ANNOTATION_CLASSES = new Class<?>[] {
|
||||
Pointcut.class, Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class};
|
||||
|
||||
@@ -68,37 +65,11 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
|
||||
protected final ParameterNameDiscoverer parameterNameDiscoverer = new AspectJAnnotationParameterNameDiscoverer();
|
||||
|
||||
|
||||
/**
|
||||
* We consider something to be an AspectJ aspect suitable for use by the Spring AOP system
|
||||
* if it has the @Aspect annotation, and was not compiled by ajc. The reason for this latter test
|
||||
* is that aspects written in the code-style (AspectJ language) also have the annotation present
|
||||
* when compiled by ajc with the -1.5 flag, yet they cannot be consumed by Spring AOP.
|
||||
*/
|
||||
@Override
|
||||
public boolean isAspect(Class<?> clazz) {
|
||||
return (hasAspectAnnotation(clazz) && !compiledByAjc(clazz));
|
||||
}
|
||||
|
||||
private boolean hasAspectAnnotation(Class<?> clazz) {
|
||||
return (AnnotationUtils.findAnnotation(clazz, Aspect.class) != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* We need to detect this as "code-style" AspectJ aspects should not be
|
||||
* interpreted by Spring AOP.
|
||||
*/
|
||||
static boolean compiledByAjc(Class<?> clazz) {
|
||||
// The AJTypeSystem goes to great lengths to provide a uniform appearance between code-style and
|
||||
// annotation-style aspects. Therefore there is no 'clean' way to tell them apart. Here we rely on
|
||||
// an implementation detail of the AspectJ compiler.
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
if (field.getName().startsWith(AJC_MAGIC)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(Class<?> aspectClass) throws AopConfigException {
|
||||
AjType<?> ajType = AjTypeSystem.getAjType(aspectClass);
|
||||
@@ -115,6 +86,7 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find and return the first AspectJ annotation on the given method
|
||||
* (there <i>should</i> only be one anyway...).
|
||||
|
||||
+15
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.aop.aspectj.annotation;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import org.springframework.aot.generate.GenerationContext;
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
|
||||
@@ -34,6 +36,8 @@ import org.springframework.util.ClassUtils;
|
||||
*/
|
||||
class AspectJAdvisorBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor {
|
||||
|
||||
private static final String AJC_MAGIC = "ajc$";
|
||||
|
||||
private static final boolean aspectjPresent = ClassUtils.isPresent("org.aspectj.lang.annotation.Pointcut",
|
||||
AspectJAdvisorBeanRegistrationAotProcessor.class.getClassLoader());
|
||||
|
||||
@@ -43,13 +47,22 @@ class AspectJAdvisorBeanRegistrationAotProcessor implements BeanRegistrationAotP
|
||||
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
|
||||
if (aspectjPresent) {
|
||||
Class<?> beanClass = registeredBean.getBeanClass();
|
||||
if (AbstractAspectJAdvisorFactory.compiledByAjc(beanClass)) {
|
||||
if (compiledByAjc(beanClass)) {
|
||||
return new AspectJAdvisorContribution(beanClass);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean compiledByAjc(Class<?> clazz) {
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
if (field.getName().startsWith(AJC_MAGIC)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private static class AspectJAdvisorContribution implements BeanRegistrationAotContribution {
|
||||
|
||||
|
||||
+11
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -124,10 +124,16 @@ public class AspectMetadata implements Serializable {
|
||||
* Extract contents from String of form {@code pertarget(contents)}.
|
||||
*/
|
||||
private String findPerClause(Class<?> aspectClass) {
|
||||
String str = aspectClass.getAnnotation(Aspect.class).value();
|
||||
int beginIndex = str.indexOf('(') + 1;
|
||||
int endIndex = str.length() - 1;
|
||||
return str.substring(beginIndex, endIndex);
|
||||
Aspect ann = aspectClass.getAnnotation(Aspect.class);
|
||||
if (ann == null) {
|
||||
return "";
|
||||
}
|
||||
String value = ann.value();
|
||||
int beginIndex = value.indexOf('(');
|
||||
if (beginIndex < 0) {
|
||||
return "";
|
||||
}
|
||||
return value.substring(beginIndex + 1, value.length() - 1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+15
-12
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -50,6 +50,7 @@ import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.converter.ConvertingComparator;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.ReflectionUtils.MethodFilter;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -133,17 +134,19 @@ public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFacto
|
||||
|
||||
List<Advisor> advisors = new ArrayList<>();
|
||||
for (Method method : getAdvisorMethods(aspectClass)) {
|
||||
// Prior to Spring Framework 5.2.7, advisors.size() was supplied as the declarationOrderInAspect
|
||||
// to getAdvisor(...) to represent the "current position" in the declared methods list.
|
||||
// However, since Java 7 the "current position" is not valid since the JDK no longer
|
||||
// returns declared methods in the order in which they are declared in the source code.
|
||||
// Thus, we now hard code the declarationOrderInAspect to 0 for all advice methods
|
||||
// discovered via reflection in order to support reliable advice ordering across JVM launches.
|
||||
// Specifically, a value of 0 aligns with the default value used in
|
||||
// AspectJPrecedenceComparator.getAspectDeclarationOrder(Advisor).
|
||||
Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, 0, aspectName);
|
||||
if (advisor != null) {
|
||||
advisors.add(advisor);
|
||||
if (method.equals(ClassUtils.getMostSpecificMethod(method, aspectClass))) {
|
||||
// Prior to Spring Framework 5.2.7, advisors.size() was supplied as the declarationOrderInAspect
|
||||
// to getAdvisor(...) to represent the "current position" in the declared methods list.
|
||||
// However, since Java 7 the "current position" is not valid since the JDK no longer
|
||||
// returns declared methods in the order in which they are declared in the source code.
|
||||
// Thus, we now hard code the declarationOrderInAspect to 0 for all advice methods
|
||||
// discovered via reflection in order to support reliable advice ordering across JVM launches.
|
||||
// Specifically, a value of 0 aligns with the default value used in
|
||||
// AspectJPrecedenceComparator.getAspectDeclarationOrder(Advisor).
|
||||
Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, 0, aspectName);
|
||||
if (advisor != null) {
|
||||
advisors.add(advisor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -369,8 +369,7 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
private void validateIntroductionAdvisor(IntroductionAdvisor advisor) {
|
||||
advisor.validateInterfaces();
|
||||
// If the advisor passed validation, we can make the change.
|
||||
Class<?>[] ifcs = advisor.getInterfaces();
|
||||
for (Class<?> ifc : ifcs) {
|
||||
for (Class<?> ifc : advisor.getInterfaces()) {
|
||||
addInterface(ifc);
|
||||
}
|
||||
}
|
||||
|
||||
+8
-13
@@ -23,8 +23,6 @@ import java.util.Map;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.aspectj.weaver.tools.PointcutPrimitive;
|
||||
import org.aspectj.weaver.tools.UnsupportedPointcutPrimitiveException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import test.annotation.EmptySpringAnnotation;
|
||||
@@ -41,14 +39,13 @@ import org.springframework.beans.testfixture.beans.TestBean;
|
||||
import org.springframework.beans.testfixture.beans.subpkg.DeepBean;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* @author Rob Harrop
|
||||
* @author Rod Johnson
|
||||
* @author Chris Beams
|
||||
* @author Juergen Hoeller
|
||||
* @author Yanming Zhou
|
||||
*/
|
||||
class AspectJExpressionPointcutTests {
|
||||
@@ -63,7 +60,7 @@ class AspectJExpressionPointcutTests {
|
||||
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws NoSuchMethodException {
|
||||
void setup() throws NoSuchMethodException {
|
||||
getAge = TestBean.class.getMethod("getAge");
|
||||
setAge = TestBean.class.getMethod("setAge", int.class);
|
||||
setSomeNumber = TestBean.class.getMethod("setSomeNumber", Number.class);
|
||||
@@ -174,7 +171,7 @@ class AspectJExpressionPointcutTests {
|
||||
void testFriendlyErrorOnNoLocationClassMatching() {
|
||||
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> pc.matches(ITestBean.class))
|
||||
.isThrownBy(() -> pc.getClassFilter().matches(ITestBean.class))
|
||||
.withMessageContaining("expression");
|
||||
}
|
||||
|
||||
@@ -182,7 +179,7 @@ class AspectJExpressionPointcutTests {
|
||||
void testFriendlyErrorOnNoLocation2ArgMatching() {
|
||||
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> pc.matches(getAge, ITestBean.class))
|
||||
.isThrownBy(() -> pc.getMethodMatcher().matches(getAge, ITestBean.class))
|
||||
.withMessageContaining("expression");
|
||||
}
|
||||
|
||||
@@ -190,13 +187,13 @@ class AspectJExpressionPointcutTests {
|
||||
void testFriendlyErrorOnNoLocation3ArgMatching() {
|
||||
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> pc.matches(getAge, ITestBean.class, (Object[]) null))
|
||||
.isThrownBy(() -> pc.getMethodMatcher().matches(getAge, ITestBean.class, (Object[]) null))
|
||||
.withMessageContaining("expression");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testMatchWithArgs() throws Exception {
|
||||
void testMatchWithArgs() {
|
||||
String expression = "execution(void org.springframework.beans.testfixture.beans.TestBean.setSomeNumber(Number)) && args(Double)";
|
||||
|
||||
Pointcut pointcut = getPointcut(expression);
|
||||
@@ -246,7 +243,7 @@ class AspectJExpressionPointcutTests {
|
||||
@Test
|
||||
void testInvalidExpression() {
|
||||
String expression = "execution(void org.springframework.beans.testfixture.beans.TestBean.setSomeNumber(Number) && args(Double)";
|
||||
assertThatIllegalArgumentException().isThrownBy(getPointcut(expression)::getClassFilter); // call to getClassFilter forces resolution
|
||||
assertThat(getPointcut(expression).getClassFilter().matches(Object.class)).isFalse();
|
||||
}
|
||||
|
||||
private TestBean getAdvisedProxy(String pointcutExpression, CallCountingInterceptor interceptor) {
|
||||
@@ -276,9 +273,7 @@ class AspectJExpressionPointcutTests {
|
||||
@Test
|
||||
void testWithUnsupportedPointcutPrimitive() {
|
||||
String expression = "call(int org.springframework.beans.testfixture.beans.TestBean.getAge())";
|
||||
assertThatExceptionOfType(UnsupportedPointcutPrimitiveException.class)
|
||||
.isThrownBy(() -> getPointcut(expression).getClassFilter()) // call to getClassFilter forces resolution...
|
||||
.satisfies(ex -> assertThat(ex.getUnsupportedPrimitive()).isEqualTo(PointcutPrimitive.CALL));
|
||||
assertThat(getPointcut(expression).getClassFilter().matches(Object.class)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+10
-5
@@ -83,15 +83,15 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
@Test
|
||||
void rejectsPerCflowAspect() {
|
||||
assertThatExceptionOfType(AopConfigException.class)
|
||||
.isThrownBy(() -> getAdvisorFactory().getAdvisors(aspectInstanceFactory(new PerCflowAspect(), "someBean")))
|
||||
.withMessageContaining("PERCFLOW");
|
||||
.isThrownBy(() -> getAdvisorFactory().getAdvisors(aspectInstanceFactory(new PerCflowAspect(), "someBean")))
|
||||
.withMessageContaining("PERCFLOW");
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsPerCflowBelowAspect() {
|
||||
assertThatExceptionOfType(AopConfigException.class)
|
||||
.isThrownBy(() -> getAdvisorFactory().getAdvisors(aspectInstanceFactory(new PerCflowBelowAspect(), "someBean")))
|
||||
.withMessageContaining("PERCFLOWBELOW");
|
||||
.isThrownBy(() -> getAdvisorFactory().getAdvisors(aspectInstanceFactory(new PerCflowBelowAspect(), "someBean")))
|
||||
.withMessageContaining("PERCFLOWBELOW");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -770,9 +770,15 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Aspect
|
||||
static class IncrementingAspect extends DoublingAspect {
|
||||
|
||||
@Override
|
||||
public Object doubleAge(ProceedingJoinPoint pjp) throws Throwable {
|
||||
return ((int) pjp.proceed()) * 2;
|
||||
}
|
||||
|
||||
@Around("execution(* getAge())")
|
||||
public int incrementAge(ProceedingJoinPoint pjp) throws Throwable {
|
||||
return ((int) pjp.proceed()) + 1;
|
||||
@@ -780,7 +786,6 @@ abstract class AbstractAspectJAdvisorFactoryTests {
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Aspect
|
||||
private static class InvocationTrackingAspect {
|
||||
|
||||
|
||||
+2
-1
@@ -22,11 +22,12 @@ import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
|
||||
/**
|
||||
* @author Adrian Colyer
|
||||
* @author Juergen Hoeller
|
||||
*/
|
||||
class AutoProxyWithCodeStyleAspectsTests {
|
||||
|
||||
@Test
|
||||
void noAutoproxyingOfAjcCompiledAspects() {
|
||||
void noAutoProxyingOfAjcCompiledAspects() {
|
||||
new ClassPathXmlApplicationContext("org/springframework/aop/aspectj/autoproxy/ajcAutoproxyTests.xml");
|
||||
}
|
||||
|
||||
|
||||
+5
-2
@@ -20,11 +20,14 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
|
||||
public class SpringConfiguredWithAutoProxyingTests {
|
||||
/**
|
||||
* @author Ramnivas Laddad
|
||||
* @author Juergen Hoeller
|
||||
*/
|
||||
class SpringConfiguredWithAutoProxyingTests {
|
||||
|
||||
@Test
|
||||
void springConfiguredAndAutoProxyUsedTogether() {
|
||||
// instantiation is sufficient to trigger failure if this is going to fail...
|
||||
new ClassPathXmlApplicationContext("org/springframework/beans/factory/aspectj/springConfigured.xml");
|
||||
}
|
||||
|
||||
|
||||
+8
-3
@@ -2,16 +2,21 @@
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:aop="http://www.springframework.org/schema/aop"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-2.0.xsd
|
||||
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
|
||||
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop-2.0.xsd
|
||||
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context-2.5.xsd">
|
||||
|
||||
<aop:aspectj-autoproxy/>
|
||||
|
||||
<bean id="myAspect" class="org.springframework.aop.aspectj.autoproxy.CodeStyleAspect"
|
||||
factory-method="aspectOf">
|
||||
<context:spring-configured/>
|
||||
|
||||
<bean id="myAspect" class="org.springframework.aop.aspectj.autoproxy.CodeStyleAspect" factory-method="aspectOf">
|
||||
<property name="foo" value="bar"/>
|
||||
</bean>
|
||||
|
||||
<bean id="otherBean" class="java.lang.Object"/>
|
||||
|
||||
<bean id="yetAnotherBean" class="java.lang.Object"/>
|
||||
|
||||
</beans>
|
||||
|
||||
+1
-1
@@ -165,7 +165,7 @@ class BeanDefinitionMethodGenerator {
|
||||
this.aotContributions.forEach(aotContribution -> aotContribution.applyTo(generationContext, codeGenerator));
|
||||
|
||||
CodeWarnings codeWarnings = new CodeWarnings();
|
||||
codeWarnings.detectDeprecation(this.registeredBean.getBeanClass());
|
||||
codeWarnings.detectDeprecation(this.registeredBean.getBeanType());
|
||||
return generatedMethods.add("getBeanDefinition", method -> {
|
||||
method.addJavadoc("Get the $L definition for '$L'.",
|
||||
(this.registeredBean.isInnerBean() ? "inner-bean" : "bean"),
|
||||
|
||||
@@ -23,10 +23,12 @@ import java.util.Set;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.javapoet.AnnotationSpec;
|
||||
import org.springframework.javapoet.CodeBlock;
|
||||
import org.springframework.javapoet.MethodSpec;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Helper class to register warnings that the compiler may trigger on
|
||||
@@ -72,6 +74,26 @@ class CodeWarnings {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect the presence of {@link Deprecated} on the signature of the
|
||||
* specified {@link ResolvableType}.
|
||||
* @param resolvableType a type signature
|
||||
* @return {@code this} instance
|
||||
*/
|
||||
public CodeWarnings detectDeprecation(ResolvableType resolvableType) {
|
||||
if (ResolvableType.NONE.equals(resolvableType)) {
|
||||
return this;
|
||||
}
|
||||
Class<?> type = ClassUtils.getUserClass(resolvableType.toClass());
|
||||
detectDeprecation(type);
|
||||
if (resolvableType.hasGenerics() && !resolvableType.hasUnresolvableGenerics()) {
|
||||
for (ResolvableType generic : resolvableType.getGenerics()) {
|
||||
detectDeprecation(generic);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Include {@link SuppressWarnings} on the specified method if necessary.
|
||||
* @param method the method to update
|
||||
|
||||
+17
-16
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,7 +17,6 @@
|
||||
package org.springframework.beans.factory.aot;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Executable;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
@@ -35,6 +34,7 @@ import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanDefinitionHolder;
|
||||
import org.springframework.beans.factory.support.InstanceSupplier;
|
||||
import org.springframework.beans.factory.support.RegisteredBean;
|
||||
import org.springframework.beans.factory.support.RegisteredBean.InstantiationDescriptor;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.javapoet.ClassName;
|
||||
@@ -62,7 +62,7 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
|
||||
|
||||
private final BeanDefinitionMethodGeneratorFactory beanDefinitionMethodGeneratorFactory;
|
||||
|
||||
private final Supplier<Executable> constructorOrFactoryMethod;
|
||||
private final Supplier<InstantiationDescriptor> instantiationDescriptor;
|
||||
|
||||
|
||||
DefaultBeanRegistrationCodeFragments(BeanRegistrationsCode beanRegistrationsCode,
|
||||
@@ -72,17 +72,18 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
|
||||
this.beanRegistrationsCode = beanRegistrationsCode;
|
||||
this.registeredBean = registeredBean;
|
||||
this.beanDefinitionMethodGeneratorFactory = beanDefinitionMethodGeneratorFactory;
|
||||
this.constructorOrFactoryMethod = SingletonSupplier.of(registeredBean::resolveConstructorOrFactoryMethod);
|
||||
this.instantiationDescriptor = SingletonSupplier.of(registeredBean::resolveInstantiationDescriptor);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ClassName getTarget(RegisteredBean registeredBean) {
|
||||
if (hasInstanceSupplier()) {
|
||||
throw new IllegalStateException("Default code generation is not supported for bean definitions "
|
||||
+ "declaring an instance supplier callback: " + registeredBean.getMergedBeanDefinition());
|
||||
String resourceDescription = registeredBean.getMergedBeanDefinition().getResourceDescription();
|
||||
throw new IllegalStateException("Error processing bean with name '" + registeredBean.getBeanName() + "'" +
|
||||
(resourceDescription != null ? " defined in " + resourceDescription : "") + ": instance supplier is not supported");
|
||||
}
|
||||
Class<?> target = extractDeclaringClass(registeredBean.getBeanType(), this.constructorOrFactoryMethod.get());
|
||||
Class<?> target = extractDeclaringClass(registeredBean, this.instantiationDescriptor.get());
|
||||
while (target.getName().startsWith("java.") && registeredBean.isInnerBean()) {
|
||||
RegisteredBean parent = registeredBean.getParent();
|
||||
Assert.state(parent != null, "No parent available for inner bean");
|
||||
@@ -91,14 +92,14 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
|
||||
return (target.isArray() ? ClassName.get(target.getComponentType()) : ClassName.get(target));
|
||||
}
|
||||
|
||||
private Class<?> extractDeclaringClass(ResolvableType beanType, Executable executable) {
|
||||
Class<?> declaringClass = ClassUtils.getUserClass(executable.getDeclaringClass());
|
||||
if (executable instanceof Constructor<?>
|
||||
&& AccessControl.forMember(executable).isPublic()
|
||||
private Class<?> extractDeclaringClass(RegisteredBean registeredBean, InstantiationDescriptor instantiationDescriptor) {
|
||||
Class<?> declaringClass = ClassUtils.getUserClass(instantiationDescriptor.targetClass());
|
||||
if (instantiationDescriptor.executable() instanceof Constructor<?>
|
||||
&& AccessControl.forMember(instantiationDescriptor.executable()).isPublic()
|
||||
&& FactoryBean.class.isAssignableFrom(declaringClass)) {
|
||||
return extractTargetClassFromFactoryBean(declaringClass, beanType);
|
||||
return extractTargetClassFromFactoryBean(declaringClass, registeredBean.getBeanType());
|
||||
}
|
||||
return executable.getDeclaringClass();
|
||||
return declaringClass;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -238,9 +239,9 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
|
||||
throw new IllegalStateException("Default code generation is not supported for bean definitions declaring "
|
||||
+ "an instance supplier callback: " + this.registeredBean.getMergedBeanDefinition());
|
||||
}
|
||||
return new InstanceSupplierCodeGenerator(generationContext,
|
||||
beanRegistrationCode.getClassName(), beanRegistrationCode.getMethods(), allowDirectSupplierShortcut)
|
||||
.generateCode(this.registeredBean, this.constructorOrFactoryMethod.get());
|
||||
return new InstanceSupplierCodeGenerator(generationContext, beanRegistrationCode.getClassName(),
|
||||
beanRegistrationCode.getMethods(), allowDirectSupplierShortcut).generateCode(
|
||||
this.registeredBean, this.instantiationDescriptor.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+39
-23
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -46,6 +46,7 @@ import org.springframework.beans.factory.support.AutowireCandidateResolver;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.InstanceSupplier;
|
||||
import org.springframework.beans.factory.support.RegisteredBean;
|
||||
import org.springframework.beans.factory.support.RegisteredBean.InstantiationDescriptor;
|
||||
import org.springframework.core.KotlinDetector;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ResolvableType;
|
||||
@@ -120,14 +121,29 @@ public class InstanceSupplierCodeGenerator {
|
||||
* @param registeredBean the bean to handle
|
||||
* @param constructorOrFactoryMethod the executable to use to create the bean
|
||||
* @return the generated code
|
||||
* @deprecated in favor of {@link #generateCode(RegisteredBean, InstantiationDescriptor)}
|
||||
*/
|
||||
@Deprecated(since = "6.1.7")
|
||||
public CodeBlock generateCode(RegisteredBean registeredBean, Executable constructorOrFactoryMethod) {
|
||||
return generateCode(registeredBean, new InstantiationDescriptor(
|
||||
constructorOrFactoryMethod, constructorOrFactoryMethod.getDeclaringClass()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the instance supplier code.
|
||||
* @param registeredBean the bean to handle
|
||||
* @param instantiationDescriptor the executable to use to create the bean
|
||||
* @return the generated code
|
||||
* @since 6.1.7
|
||||
*/
|
||||
public CodeBlock generateCode(RegisteredBean registeredBean, InstantiationDescriptor instantiationDescriptor) {
|
||||
Executable constructorOrFactoryMethod = instantiationDescriptor.executable();
|
||||
registerRuntimeHintsIfNecessary(registeredBean, constructorOrFactoryMethod);
|
||||
if (constructorOrFactoryMethod instanceof Constructor<?> constructor) {
|
||||
return generateCodeForConstructor(registeredBean, constructor);
|
||||
}
|
||||
if (constructorOrFactoryMethod instanceof Method method) {
|
||||
return generateCodeForFactoryMethod(registeredBean, method);
|
||||
return generateCodeForFactoryMethod(registeredBean, method, instantiationDescriptor.targetClass());
|
||||
}
|
||||
throw new IllegalStateException(
|
||||
"No suitable executor found for " + registeredBean.getBeanName());
|
||||
@@ -253,21 +269,21 @@ public class InstanceSupplierCodeGenerator {
|
||||
declaringClass.getSimpleName(), args);
|
||||
}
|
||||
|
||||
private CodeBlock generateCodeForFactoryMethod(RegisteredBean registeredBean, Method factoryMethod) {
|
||||
private CodeBlock generateCodeForFactoryMethod(RegisteredBean registeredBean, Method factoryMethod, Class<?> targetClass) {
|
||||
String beanName = registeredBean.getBeanName();
|
||||
Class<?> declaringClass = ClassUtils.getUserClass(factoryMethod.getDeclaringClass());
|
||||
Class<?> targetClassToUse = ClassUtils.getUserClass(targetClass);
|
||||
boolean dependsOnBean = !Modifier.isStatic(factoryMethod.getModifiers());
|
||||
|
||||
Visibility accessVisibility = getAccessVisibility(registeredBean, factoryMethod);
|
||||
if (accessVisibility != Visibility.PRIVATE) {
|
||||
return generateCodeForAccessibleFactoryMethod(
|
||||
beanName, factoryMethod, declaringClass, dependsOnBean);
|
||||
beanName, factoryMethod, targetClassToUse, dependsOnBean);
|
||||
}
|
||||
return generateCodeForInaccessibleFactoryMethod(beanName, factoryMethod, declaringClass);
|
||||
return generateCodeForInaccessibleFactoryMethod(beanName, factoryMethod, targetClassToUse);
|
||||
}
|
||||
|
||||
private CodeBlock generateCodeForAccessibleFactoryMethod(String beanName,
|
||||
Method factoryMethod, Class<?> declaringClass, boolean dependsOnBean) {
|
||||
Method factoryMethod, Class<?> targetClass, boolean dependsOnBean) {
|
||||
|
||||
this.generationContext.getRuntimeHints().reflection().registerMethod(
|
||||
factoryMethod, ExecutableMode.INTROSPECT);
|
||||
@@ -276,20 +292,20 @@ public class InstanceSupplierCodeGenerator {
|
||||
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());
|
||||
suppliedType, targetClass, factoryMethod.getName());
|
||||
code.add(".withGenerator(($L) -> $T.$L())", REGISTERED_BEAN_PARAMETER_NAME,
|
||||
declaringClass, factoryMethod.getName());
|
||||
targetClass, factoryMethod.getName());
|
||||
return code.build();
|
||||
}
|
||||
|
||||
GeneratedMethod getInstanceMethod = generateGetInstanceSupplierMethod(method ->
|
||||
buildGetInstanceMethodForFactoryMethod(method, beanName, factoryMethod,
|
||||
declaringClass, dependsOnBean, PRIVATE_STATIC));
|
||||
targetClass, dependsOnBean, PRIVATE_STATIC));
|
||||
return generateReturnStatement(getInstanceMethod);
|
||||
}
|
||||
|
||||
private CodeBlock generateCodeForInaccessibleFactoryMethod(
|
||||
String beanName, Method factoryMethod, Class<?> declaringClass) {
|
||||
String beanName, Method factoryMethod, Class<?> targetClass) {
|
||||
|
||||
this.generationContext.getRuntimeHints().reflection().registerMethod(factoryMethod, ExecutableMode.INVOKE);
|
||||
GeneratedMethod getInstanceMethod = generateGetInstanceSupplierMethod(method -> {
|
||||
@@ -298,19 +314,19 @@ public class InstanceSupplierCodeGenerator {
|
||||
method.addModifiers(PRIVATE_STATIC);
|
||||
method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, suppliedType));
|
||||
method.addStatement(generateInstanceSupplierForFactoryMethod(
|
||||
factoryMethod, suppliedType, declaringClass, factoryMethod.getName()));
|
||||
factoryMethod, suppliedType, targetClass, factoryMethod.getName()));
|
||||
});
|
||||
return generateReturnStatement(getInstanceMethod);
|
||||
}
|
||||
|
||||
private void buildGetInstanceMethodForFactoryMethod(MethodSpec.Builder method,
|
||||
String beanName, Method factoryMethod, Class<?> declaringClass,
|
||||
String beanName, Method factoryMethod, Class<?> targetClass,
|
||||
boolean dependsOnBean, javax.lang.model.element.Modifier... modifiers) {
|
||||
|
||||
String factoryMethodName = factoryMethod.getName();
|
||||
Class<?> suppliedType = ClassUtils.resolvePrimitiveIfNecessary(factoryMethod.getReturnType());
|
||||
CodeWarnings codeWarnings = new CodeWarnings();
|
||||
codeWarnings.detectDeprecation(declaringClass, factoryMethod, suppliedType)
|
||||
codeWarnings.detectDeprecation(targetClass, factoryMethod, suppliedType)
|
||||
.detectDeprecation(Arrays.stream(factoryMethod.getParameters()).map(Parameter::getType));
|
||||
|
||||
method.addJavadoc("Get the bean instance supplier for '$L'.", beanName);
|
||||
@@ -320,41 +336,41 @@ public class InstanceSupplierCodeGenerator {
|
||||
|
||||
CodeBlock.Builder code = CodeBlock.builder();
|
||||
code.add(generateInstanceSupplierForFactoryMethod(
|
||||
factoryMethod, suppliedType, declaringClass, factoryMethodName));
|
||||
factoryMethod, suppliedType, targetClass, factoryMethodName));
|
||||
|
||||
boolean hasArguments = factoryMethod.getParameterCount() > 0;
|
||||
CodeBlock arguments = hasArguments ?
|
||||
new AutowiredArgumentsCodeGenerator(declaringClass, factoryMethod)
|
||||
new AutowiredArgumentsCodeGenerator(targetClass, factoryMethod)
|
||||
.generateCode(factoryMethod.getParameterTypes())
|
||||
: NO_ARGS;
|
||||
|
||||
CodeBlock newInstance = generateNewInstanceCodeForMethod(
|
||||
dependsOnBean, declaringClass, factoryMethodName, arguments);
|
||||
dependsOnBean, targetClass, factoryMethodName, arguments);
|
||||
code.add(generateWithGeneratorCode(hasArguments, newInstance));
|
||||
method.addStatement(code.build());
|
||||
}
|
||||
|
||||
private CodeBlock generateInstanceSupplierForFactoryMethod(Method factoryMethod,
|
||||
Class<?> suppliedType, Class<?> declaringClass, String factoryMethodName) {
|
||||
Class<?> suppliedType, Class<?> targetClass, String factoryMethodName) {
|
||||
|
||||
if (factoryMethod.getParameterCount() == 0) {
|
||||
return CodeBlock.of("return $T.<$T>forFactoryMethod($T.class, $S)",
|
||||
BeanInstanceSupplier.class, suppliedType, declaringClass, factoryMethodName);
|
||||
BeanInstanceSupplier.class, suppliedType, targetClass, 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, suppliedType, targetClass, factoryMethodName, parameterTypes);
|
||||
}
|
||||
|
||||
private CodeBlock generateNewInstanceCodeForMethod(boolean dependsOnBean,
|
||||
Class<?> declaringClass, String factoryMethodName, CodeBlock args) {
|
||||
Class<?> targetClass, String factoryMethodName, CodeBlock args) {
|
||||
|
||||
if (!dependsOnBean) {
|
||||
return CodeBlock.of("$T.$L($L)", declaringClass, factoryMethodName, args);
|
||||
return CodeBlock.of("$T.$L($L)", targetClass, factoryMethodName, args);
|
||||
}
|
||||
return CodeBlock.of("$L.getBeanFactory().getBean($T.class).$L($L)",
|
||||
REGISTERED_BEAN_PARAMETER_NAME, declaringClass, factoryMethodName, args);
|
||||
REGISTERED_BEAN_PARAMETER_NAME, targetClass, factoryMethodName, args);
|
||||
}
|
||||
|
||||
private CodeBlock generateReturnStatement(GeneratedMethod generatedMethod) {
|
||||
|
||||
+9
-7
@@ -822,11 +822,11 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||
/**
|
||||
* This implementation attempts to query the FactoryBean's generic parameter metadata
|
||||
* if present to determine the object type. If not present, i.e. the FactoryBean is
|
||||
* declared as a raw type, checks the FactoryBean's {@code getObjectType} method
|
||||
* declared as a raw type, it checks the FactoryBean's {@code getObjectType} method
|
||||
* on a plain instance of the FactoryBean, without bean properties applied yet.
|
||||
* If this doesn't return a type yet, and {@code allowInit} is {@code true} a
|
||||
* full creation of the FactoryBean is used as fallback (through delegation to the
|
||||
* superclass's implementation).
|
||||
* If this doesn't return a type yet and {@code allowInit} is {@code true}, full
|
||||
* creation of the FactoryBean is attempted as fallback (through delegation to the
|
||||
* superclass implementation).
|
||||
* <p>The shortcut check for a FactoryBean is only applied in case of a singleton
|
||||
* FactoryBean. If the FactoryBean instance itself is not kept as singleton,
|
||||
* it will be fully created to check the type of its exposed object.
|
||||
@@ -1156,9 +1156,11 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||
"Bean class isn't public, and non-public access not allowed: " + beanClass.getName());
|
||||
}
|
||||
|
||||
Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
|
||||
if (instanceSupplier != null) {
|
||||
return obtainFromSupplier(instanceSupplier, beanName, mbd);
|
||||
if (args == null) {
|
||||
Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
|
||||
if (instanceSupplier != null) {
|
||||
return obtainFromSupplier(instanceSupplier, beanName, mbd);
|
||||
}
|
||||
}
|
||||
|
||||
if (mbd.getFactoryMethodName() != null) {
|
||||
|
||||
+13
-2
@@ -316,6 +316,17 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
|
||||
"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
|
||||
}
|
||||
catch (BeanCreationException ex) {
|
||||
if (requiredType != null) {
|
||||
// Wrap exception with current bean metadata but only if specifically
|
||||
// requested (indicated by required type), not for depends-on cascades.
|
||||
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
|
||||
"Failed to initialize dependency '" + ex.getBeanName() + "' of " +
|
||||
requiredType.getSimpleName() + " bean '" + beanName + "': " +
|
||||
ex.getMessage(), ex);
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1087,7 +1098,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
|
||||
@Override
|
||||
public void setApplicationStartup(ApplicationStartup applicationStartup) {
|
||||
Assert.notNull(applicationStartup, "applicationStartup must not be null");
|
||||
Assert.notNull(applicationStartup, "ApplicationStartup must not be null");
|
||||
this.applicationStartup = applicationStartup;
|
||||
}
|
||||
|
||||
@@ -1686,7 +1697,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
* already. The implementation is allowed to instantiate the target factory bean if
|
||||
* {@code allowInit} is {@code true} and the type cannot be determined another way;
|
||||
* otherwise it is restricted to introspecting signatures and related metadata.
|
||||
* <p>If no {@link FactoryBean#OBJECT_TYPE_ATTRIBUTE} if set on the bean definition
|
||||
* <p>If no {@link FactoryBean#OBJECT_TYPE_ATTRIBUTE} is set on the bean definition
|
||||
* and {@code allowInit} is {@code true}, the default implementation will create
|
||||
* the FactoryBean via {@code getBean} to call its {@code getObjectType} method.
|
||||
* Subclasses are encouraged to optimize this, typically by inspecting the generic
|
||||
|
||||
+43
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,6 +17,8 @@
|
||||
package org.springframework.beans.factory.support;
|
||||
|
||||
import java.lang.reflect.Executable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Supplier;
|
||||
@@ -41,6 +43,8 @@ import org.springframework.util.StringUtils;
|
||||
* In the case of inner-beans, the bean name may have been generated.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
* @author Juergen Hoeller
|
||||
* @since 6.0
|
||||
*/
|
||||
public final class RegisteredBean {
|
||||
@@ -206,12 +210,32 @@ public final class RegisteredBean {
|
||||
/**
|
||||
* Resolve the constructor or factory method to use for this bean.
|
||||
* @return the {@link java.lang.reflect.Constructor} or {@link java.lang.reflect.Method}
|
||||
* @deprecated in favor of {@link #resolveInstantiationDescriptor()}
|
||||
*/
|
||||
@Deprecated(since = "6.1.7")
|
||||
public Executable resolveConstructorOrFactoryMethod() {
|
||||
return new ConstructorResolver((AbstractAutowireCapableBeanFactory) getBeanFactory())
|
||||
.resolveConstructorOrFactoryMethod(getBeanName(), getMergedBeanDefinition());
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the {@linkplain InstantiationDescriptor descriptor} to use to
|
||||
* instantiate this bean. It defines the {@link java.lang.reflect.Constructor}
|
||||
* or {@link java.lang.reflect.Method} to use as well as additional metadata.
|
||||
* @since 6.1.7
|
||||
*/
|
||||
public InstantiationDescriptor resolveInstantiationDescriptor() {
|
||||
Executable executable = resolveConstructorOrFactoryMethod();
|
||||
if (executable instanceof Method method && !Modifier.isStatic(method.getModifiers())) {
|
||||
String factoryBeanName = getMergedBeanDefinition().getFactoryBeanName();
|
||||
if (factoryBeanName != null && this.beanFactory.containsBean(factoryBeanName)) {
|
||||
return new InstantiationDescriptor(executable,
|
||||
this.beanFactory.getMergedBeanDefinition(factoryBeanName).getResolvableType().toClass());
|
||||
}
|
||||
}
|
||||
return new InstantiationDescriptor(executable, executable.getDeclaringClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve an autowired argument.
|
||||
* @param descriptor the descriptor for the dependency (field/method/constructor)
|
||||
@@ -238,6 +262,24 @@ public final class RegisteredBean {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Descriptor for how a bean should be instantiated. While the {@code targetClass}
|
||||
* is usually the declaring class of the {@code executable} (in case of a constructor
|
||||
* or a locally declared factory method), there are cases where retaining the actual
|
||||
* concrete class is necessary (e.g. for an inherited factory method).
|
||||
* @param executable the {@link Executable} ({@link java.lang.reflect.Constructor}
|
||||
* or {@link java.lang.reflect.Method}) to invoke
|
||||
* @param targetClass the target {@link Class} of the executable
|
||||
* @since 6.1.7
|
||||
*/
|
||||
public record InstantiationDescriptor(Executable executable, Class<?> targetClass) {
|
||||
|
||||
public InstantiationDescriptor(Executable executable) {
|
||||
this(executable, executable.getDeclaringClass());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resolver used to obtain inner-bean details.
|
||||
*/
|
||||
|
||||
+40
@@ -1967,6 +1967,42 @@ class DefaultListableBeanFactoryTests {
|
||||
lbf.getBean(TestBean.class, 67));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getBeanByTypeInstanceWithConstructorIgnoresInstanceSupplier() {
|
||||
RootBeanDefinition bd1 = createConstructorDependencyBeanDefinition(99);
|
||||
bd1.setInstanceSupplier(() -> new ConstructorDependency(new TestBean("test")));
|
||||
lbf.registerBeanDefinition("bd1", bd1);
|
||||
|
||||
ConstructorDependency defaultInstance = lbf.getBean(ConstructorDependency.class);
|
||||
assertThat(defaultInstance.beanName).isEqualTo("bd1");
|
||||
assertThat(defaultInstance.spouseAge).isEqualTo(0);
|
||||
|
||||
ConstructorDependency argsInstance = lbf.getBean(ConstructorDependency.class, 42);
|
||||
assertThat(argsInstance.beanName).isEqualTo("bd1");
|
||||
assertThat(argsInstance.spouseAge).isEqualTo(42);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getBeanByTypeInstanceWithFactoryMethodIgnoresInstanceSupplier() {
|
||||
RootBeanDefinition bd1 = new RootBeanDefinition(TestBean.class);
|
||||
bd1.setScope(BeanDefinition.SCOPE_PROTOTYPE);
|
||||
bd1.setFactoryBeanName("config");
|
||||
bd1.setFactoryMethodName("create");
|
||||
bd1.setInstanceSupplier(() -> new TestBean("test"));
|
||||
lbf.registerBeanDefinition("config", new RootBeanDefinition(BeanWithFactoryMethod.class));
|
||||
lbf.registerBeanDefinition("bd1", bd1);
|
||||
|
||||
TestBean defaultInstance = lbf.getBean(TestBean.class);
|
||||
assertThat(defaultInstance.getBeanName()).isEqualTo("bd1");
|
||||
assertThat(defaultInstance.getName()).isEqualTo("test");
|
||||
assertThat(defaultInstance.getAge()).isEqualTo(0);
|
||||
|
||||
TestBean argsInstance = lbf.getBean(TestBean.class, "another", 42);
|
||||
assertThat(argsInstance.getBeanName()).isEqualTo("bd1");
|
||||
assertThat(argsInstance.getName()).isEqualTo("another");
|
||||
assertThat(argsInstance.getAge()).isEqualTo(42);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("rawtypes")
|
||||
void beanProviderSerialization() throws Exception {
|
||||
@@ -3173,6 +3209,10 @@ class DefaultListableBeanFactoryTests {
|
||||
return tb;
|
||||
}
|
||||
|
||||
public TestBean create(String name, int age) {
|
||||
return new TestBean(name, age);
|
||||
}
|
||||
|
||||
public TestBean createWithArgs(String arg) {
|
||||
TestBean tb = new TestBean();
|
||||
tb.setName(arg);
|
||||
|
||||
+22
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -161,7 +161,8 @@ class BeanDefinitionMethodGeneratorTests {
|
||||
|
||||
@Test
|
||||
void generateWithBeanClassAndFactoryMethodNameSetsTargetTypeAndBeanClass() {
|
||||
this.beanFactory.registerSingleton("factory", new SimpleBeanConfiguration());
|
||||
this.beanFactory.registerBeanDefinition("factory",
|
||||
new RootBeanDefinition(SimpleBeanConfiguration.class));
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(SimpleBean.class);
|
||||
beanDefinition.setFactoryBeanName("factory");
|
||||
beanDefinition.setFactoryMethodName("simpleBean");
|
||||
@@ -182,7 +183,8 @@ class BeanDefinitionMethodGeneratorTests {
|
||||
|
||||
@Test
|
||||
void generateWithTargetTypeAndFactoryMethodNameSetsOnlyBeanClass() {
|
||||
this.beanFactory.registerSingleton("factory", new SimpleBeanConfiguration());
|
||||
this.beanFactory.registerBeanDefinition("factory",
|
||||
new RootBeanDefinition(SimpleBeanConfiguration.class));
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition();
|
||||
beanDefinition.setTargetType(SimpleBean.class);
|
||||
beanDefinition.setFactoryBeanName("factory");
|
||||
@@ -690,8 +692,8 @@ class BeanDefinitionMethodGeneratorTests {
|
||||
this.methodGeneratorFactory, registeredBean, null,
|
||||
List.of());
|
||||
assertThatIllegalStateException().isThrownBy(() -> generator.generateBeanDefinitionMethod(
|
||||
this.generationContext, this.beanRegistrationsCode)).withMessageStartingWith(
|
||||
"Default code generation is not supported for bean definitions declaring an instance supplier callback");
|
||||
this.generationContext, this.beanRegistrationsCode)).withMessage(
|
||||
"Error processing bean with name 'testBean': instance supplier is not supported");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -727,8 +729,8 @@ class BeanDefinitionMethodGeneratorTests {
|
||||
this.methodGeneratorFactory, registeredBean, null,
|
||||
List.of(aotContribution));
|
||||
assertThatIllegalStateException().isThrownBy(() -> generator.generateBeanDefinitionMethod(
|
||||
this.generationContext, this.beanRegistrationsCode)).withMessageStartingWith(
|
||||
"Default code generation is not supported for bean definitions declaring an instance supplier callback");
|
||||
this.generationContext, this.beanRegistrationsCode)).withMessage(
|
||||
"Error processing bean with name 'testBean': instance supplier is not supported");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -780,6 +782,19 @@ class BeanDefinitionMethodGeneratorTests {
|
||||
compileAndCheckWarnings(method);
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateBeanDefinitionMethodWithDeprecatedGenericElementInTargetClass() {
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition();
|
||||
beanDefinition.setTargetType(ResolvableType.forClassWithGenerics(GenericBean.class, DeprecatedBean.class));
|
||||
RegisteredBean registeredBean = registerBean(beanDefinition);
|
||||
BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator(
|
||||
methodGeneratorFactory, registeredBean, null,
|
||||
Collections.emptyList());
|
||||
MethodReference method = generator.generateBeanDefinitionMethod(
|
||||
generationContext, beanRegistrationsCode);
|
||||
compileAndCheckWarnings(method);
|
||||
}
|
||||
|
||||
private void compileAndCheckWarnings(MethodReference methodReference) {
|
||||
assertThatNoException().isThrownBy(() -> compile(TEST_COMPILER, methodReference,
|
||||
((instanceSupplier, compiled) -> {})));
|
||||
|
||||
+40
@@ -17,15 +17,21 @@
|
||||
package org.springframework.beans.factory.aot;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import org.springframework.aot.test.generate.TestGenerationContext;
|
||||
import org.springframework.beans.testfixture.beans.GenericBean;
|
||||
import org.springframework.beans.testfixture.beans.factory.aot.DeferredTypeBuilder;
|
||||
import org.springframework.beans.testfixture.beans.factory.generator.deprecation.DeprecatedBean;
|
||||
import org.springframework.beans.testfixture.beans.factory.generator.deprecation.DeprecatedForRemovalBean;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.test.tools.Compiled;
|
||||
import org.springframework.core.test.tools.TestCompiler;
|
||||
import org.springframework.javapoet.MethodSpec;
|
||||
@@ -98,6 +104,40 @@ class CodeWarningsTests {
|
||||
assertThat(this.codeWarnings.getWarnings()).containsExactly("removal");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("resolvableTypesWithDeprecated")
|
||||
void detectDeprecationOnResolvableTypeWithDeprecated(ResolvableType resolvableType) {
|
||||
this.codeWarnings.detectDeprecation(resolvableType);
|
||||
assertThat(this.codeWarnings.getWarnings()).containsExactly("deprecation");
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
static Stream<Arguments> resolvableTypesWithDeprecated() {
|
||||
return Stream.of(
|
||||
Arguments.of(ResolvableType.forClass(DeprecatedBean.class)),
|
||||
Arguments.of(ResolvableType.forClassWithGenerics(GenericBean.class, DeprecatedBean.class)),
|
||||
Arguments.of(ResolvableType.forClassWithGenerics(GenericBean.class,
|
||||
ResolvableType.forClassWithGenerics(GenericBean.class, DeprecatedBean.class)))
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("resolvableTypesWithDeprecatedForRemoval")
|
||||
void detectDeprecationOnResolvableTypeWithDeprecatedForRemoval(ResolvableType resolvableType) {
|
||||
this.codeWarnings.detectDeprecation(resolvableType);
|
||||
assertThat(this.codeWarnings.getWarnings()).containsExactly("removal");
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
static Stream<Arguments> resolvableTypesWithDeprecatedForRemoval() {
|
||||
return Stream.of(
|
||||
Arguments.of(ResolvableType.forClass(DeprecatedForRemovalBean.class)),
|
||||
Arguments.of(ResolvableType.forClassWithGenerics(GenericBean.class, DeprecatedForRemovalBean.class)),
|
||||
Arguments.of(ResolvableType.forClassWithGenerics(GenericBean.class,
|
||||
ResolvableType.forClassWithGenerics(GenericBean.class, DeprecatedForRemovalBean.class)))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringIncludeWarnings() {
|
||||
this.codeWarnings.register("deprecation");
|
||||
|
||||
+50
-7
@@ -31,6 +31,7 @@ import org.springframework.beans.factory.support.RegisteredBean;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.testfixture.beans.factory.DummyFactory;
|
||||
import org.springframework.beans.testfixture.beans.factory.StringFactoryBean;
|
||||
import org.springframework.beans.testfixture.beans.factory.aot.DefaultSimpleBeanContract;
|
||||
import org.springframework.beans.testfixture.beans.factory.aot.GenericFactoryBean;
|
||||
import org.springframework.beans.testfixture.beans.factory.aot.MockBeanRegistrationCode;
|
||||
import org.springframework.beans.testfixture.beans.factory.aot.MockBeanRegistrationsCode;
|
||||
@@ -38,6 +39,7 @@ import org.springframework.beans.testfixture.beans.factory.aot.NumberFactoryBean
|
||||
import org.springframework.beans.testfixture.beans.factory.aot.SimpleBean;
|
||||
import org.springframework.beans.testfixture.beans.factory.aot.SimpleBeanArrayFactoryBean;
|
||||
import org.springframework.beans.testfixture.beans.factory.aot.SimpleBeanConfiguration;
|
||||
import org.springframework.beans.testfixture.beans.factory.aot.SimpleBeanContract;
|
||||
import org.springframework.beans.testfixture.beans.factory.aot.SimpleBeanFactoryBean;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.javapoet.ClassName;
|
||||
@@ -46,6 +48,7 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
@@ -63,6 +66,28 @@ class DefaultBeanRegistrationCodeFragmentsTests {
|
||||
|
||||
private final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
|
||||
@Test
|
||||
public void getTargetWithInstanceSupplier() {
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(SimpleBean.class);
|
||||
beanDefinition.setInstanceSupplier(SimpleBean::new);
|
||||
RegisteredBean registeredBean = registerTestBean(beanDefinition);
|
||||
BeanRegistrationCodeFragments codeFragments = createInstance(registeredBean);
|
||||
assertThatIllegalStateException().isThrownBy(() -> codeFragments.getTarget(registeredBean))
|
||||
.withMessageContaining("Error processing bean with name 'testBean': instance supplier is not supported");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTargetWithInstanceSupplierAndResourceDescription() {
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(SimpleBean.class);
|
||||
beanDefinition.setInstanceSupplier(SimpleBean::new);
|
||||
beanDefinition.setResourceDescription("my test resource");
|
||||
RegisteredBean registeredBean = registerTestBean(beanDefinition);
|
||||
BeanRegistrationCodeFragments codeFragments = createInstance(registeredBean);
|
||||
assertThatIllegalStateException().isThrownBy(() -> codeFragments.getTarget(registeredBean))
|
||||
.withMessageContaining("Error processing bean with name 'testBean' defined in my test resource: "
|
||||
+ "instance supplier is not supported");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTargetOnConstructor() {
|
||||
RegisteredBean registeredBean = registerTestBean(SimpleBean.class,
|
||||
@@ -126,6 +151,21 @@ class DefaultBeanRegistrationCodeFragmentsTests {
|
||||
SimpleBeanConfiguration.class);
|
||||
}
|
||||
|
||||
@Test // gh-32609
|
||||
void getTargetOnMethodFromInterface() {
|
||||
this.beanFactory.registerBeanDefinition("configuration",
|
||||
new RootBeanDefinition(DefaultSimpleBeanContract.class));
|
||||
Method method = ReflectionUtils.findMethod(SimpleBeanContract.class, "simpleBean");
|
||||
assertThat(method).isNotNull();
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(SimpleBean.class);
|
||||
applyConstructorOrFactoryMethod(beanDefinition, method);
|
||||
beanDefinition.setFactoryBeanName("configuration");
|
||||
this.beanFactory.registerBeanDefinition("testBean", beanDefinition);
|
||||
RegisteredBean registeredBean = RegisteredBean.of(this.beanFactory, "testBean");
|
||||
assertTarget(createInstance(registeredBean).getTarget(registeredBean),
|
||||
DefaultSimpleBeanContract.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTargetOnMethodWithInnerBeanInJavaPackage() {
|
||||
RegisteredBean registeredBean = registerTestBean(SimpleBean.class);
|
||||
@@ -190,7 +230,7 @@ class DefaultBeanRegistrationCodeFragmentsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizedGetTargetDoesNotResolveConstructorOrFactoryMethod() {
|
||||
void customizedGetTargetDoesNotResolveInstantiationDescriptor() {
|
||||
RegisteredBean registeredBean = spy(registerTestBean(SimpleBean.class));
|
||||
BeanRegistrationCodeFragments customCodeFragments = createCustomCodeFragments(registeredBean, codeFragments -> new BeanRegistrationCodeFragmentsDecorator(codeFragments) {
|
||||
@Override
|
||||
@@ -199,11 +239,11 @@ class DefaultBeanRegistrationCodeFragmentsTests {
|
||||
}
|
||||
});
|
||||
assertTarget(customCodeFragments.getTarget(registeredBean), String.class);
|
||||
verify(registeredBean, never()).resolveConstructorOrFactoryMethod();
|
||||
verify(registeredBean, never()).resolveInstantiationDescriptor();
|
||||
}
|
||||
|
||||
@Test
|
||||
void customizedGenerateInstanceSupplierCodeDoesNotResolveConstructorOrFactoryMethod() {
|
||||
void customizedGenerateInstanceSupplierCodeDoesNotResolveInstantiationDescriptor() {
|
||||
RegisteredBean registeredBean = spy(registerTestBean(SimpleBean.class));
|
||||
BeanRegistrationCodeFragments customCodeFragments = createCustomCodeFragments(registeredBean, codeFragments -> new BeanRegistrationCodeFragmentsDecorator(codeFragments) {
|
||||
@Override
|
||||
@@ -214,7 +254,7 @@ class DefaultBeanRegistrationCodeFragmentsTests {
|
||||
});
|
||||
assertThat(customCodeFragments.generateInstanceSupplierCode(this.generationContext,
|
||||
new MockBeanRegistrationCode(this.generationContext), false)).hasToString("// Hello");
|
||||
verify(registeredBean, never()).resolveConstructorOrFactoryMethod();
|
||||
verify(registeredBean, never()).resolveInstantiationDescriptor();
|
||||
}
|
||||
|
||||
private BeanRegistrationCodeFragments createCustomCodeFragments(RegisteredBean registeredBean, UnaryOperator<BeanRegistrationCodeFragments> customFragments) {
|
||||
@@ -240,13 +280,16 @@ class DefaultBeanRegistrationCodeFragmentsTests {
|
||||
return RegisteredBean.of(this.beanFactory, "testBean");
|
||||
}
|
||||
|
||||
|
||||
private RegisteredBean registerTestBean(ResolvableType beanType,
|
||||
@Nullable Executable constructorOrFactoryMethod) {
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition();
|
||||
beanDefinition.setTargetType(beanType);
|
||||
this.beanFactory.registerBeanDefinition("testBean",
|
||||
applyConstructorOrFactoryMethod(beanDefinition, constructorOrFactoryMethod));
|
||||
return registerTestBean(applyConstructorOrFactoryMethod(
|
||||
beanDefinition, constructorOrFactoryMethod));
|
||||
}
|
||||
|
||||
private RegisteredBean registerTestBean(RootBeanDefinition beanDefinition) {
|
||||
this.beanFactory.registerBeanDefinition("testBean", beanDefinition);
|
||||
return RegisteredBean.of(this.beanFactory, "testBean");
|
||||
}
|
||||
|
||||
|
||||
+25
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.beans.factory.aot;
|
||||
|
||||
import java.lang.reflect.Executable;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@@ -38,10 +37,14 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.InstanceSupplier;
|
||||
import org.springframework.beans.factory.support.RegisteredBean;
|
||||
import org.springframework.beans.factory.support.RegisteredBean.InstantiationDescriptor;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.testfixture.beans.TestBean;
|
||||
import org.springframework.beans.testfixture.beans.TestBeanWithPrivateConstructor;
|
||||
import org.springframework.beans.testfixture.beans.factory.aot.DefaultSimpleBeanContract;
|
||||
import org.springframework.beans.testfixture.beans.factory.aot.DeferredTypeBuilder;
|
||||
import org.springframework.beans.testfixture.beans.factory.aot.SimpleBean;
|
||||
import org.springframework.beans.testfixture.beans.factory.aot.SimpleBeanContract;
|
||||
import org.springframework.beans.testfixture.beans.factory.generator.InnerComponentConfiguration;
|
||||
import org.springframework.beans.testfixture.beans.factory.generator.InnerComponentConfiguration.EnvironmentAwareComponent;
|
||||
import org.springframework.beans.testfixture.beans.factory.generator.InnerComponentConfiguration.NoDependencyComponent;
|
||||
@@ -185,6 +188,23 @@ class InstanceSupplierCodeGeneratorTests {
|
||||
.satisfies(hasMethodWithMode(ExecutableMode.INTROSPECT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateWhenHasFactoryMethodOnInterface() {
|
||||
BeanDefinition beanDefinition = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(SimpleBean.class)
|
||||
.setFactoryMethodOnBean("simpleBean", "config").getBeanDefinition();
|
||||
this.beanFactory.registerBeanDefinition("config", BeanDefinitionBuilder
|
||||
.rootBeanDefinition(DefaultSimpleBeanContract.class).getBeanDefinition());
|
||||
compile(beanDefinition, (instanceSupplier, compiled) -> {
|
||||
Object bean = getBean(beanDefinition, instanceSupplier);
|
||||
assertThat(bean).isInstanceOf(SimpleBean.class);
|
||||
assertThat(compiled.getSourceFile()).contains(
|
||||
"getBeanFactory().getBean(DefaultSimpleBeanContract.class).simpleBean()");
|
||||
});
|
||||
assertThat(getReflectionHints().getTypeHint(SimpleBeanContract.class))
|
||||
.satisfies(hasMethodWithMode(ExecutableMode.INTROSPECT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateWhenHasPrivateStaticFactoryMethodWithNoArg() {
|
||||
BeanDefinition beanDefinition = BeanDefinitionBuilder
|
||||
@@ -402,9 +422,9 @@ class InstanceSupplierCodeGeneratorTests {
|
||||
InstanceSupplierCodeGenerator generator = new InstanceSupplierCodeGenerator(
|
||||
this.generationContext, generateClass.getName(),
|
||||
generateClass.getMethods(), false);
|
||||
Executable constructorOrFactoryMethod = registeredBean.resolveConstructorOrFactoryMethod();
|
||||
assertThat(constructorOrFactoryMethod).isNotNull();
|
||||
CodeBlock generatedCode = generator.generateCode(registeredBean, constructorOrFactoryMethod);
|
||||
InstantiationDescriptor instantiationDescriptor = registeredBean.resolveInstantiationDescriptor();
|
||||
assertThat(instantiationDescriptor).isNotNull();
|
||||
CodeBlock generatedCode = generator.generateCode(registeredBean, instantiationDescriptor);
|
||||
typeBuilder.set(type -> {
|
||||
type.addModifiers(Modifier.PUBLIC);
|
||||
type.addSuperinterface(ParameterizedTypeName.get(Supplier.class, InstanceSupplier.class));
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -116,9 +116,9 @@ class InstanceSupplierCodeGeneratorKotlinTests {
|
||||
generationContext, generateClass.name,
|
||||
generateClass.methods, false
|
||||
)
|
||||
val constructorOrFactoryMethod = registeredBean.resolveConstructorOrFactoryMethod()
|
||||
Assertions.assertThat(constructorOrFactoryMethod).isNotNull()
|
||||
val generatedCode = generator.generateCode(registeredBean, constructorOrFactoryMethod)
|
||||
val instantiationDescriptor = registeredBean.resolveInstantiationDescriptor()
|
||||
Assertions.assertThat(instantiationDescriptor).isNotNull()
|
||||
val generatedCode = generator.generateCode(registeredBean, instantiationDescriptor)
|
||||
typeBuilder.set { type: TypeSpec.Builder ->
|
||||
type.addModifiers(Modifier.PUBLIC)
|
||||
type.addSuperinterface(
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.beans.testfixture.beans.factory.aot;
|
||||
|
||||
public class DefaultSimpleBeanContract implements SimpleBeanContract {
|
||||
|
||||
public SimpleBean anotherSimpleBean() {
|
||||
return new SimpleBean();
|
||||
}
|
||||
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.beans.testfixture.beans.factory.aot;
|
||||
|
||||
/**
|
||||
* Showcase a factory method that is defined on an interface.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
public interface SimpleBeanContract {
|
||||
|
||||
default SimpleBean simpleBean() {
|
||||
return new SimpleBean();
|
||||
}
|
||||
|
||||
}
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -27,7 +27,7 @@ import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* {@link RuntimeHintsRegistrar} implementation that makes sure {@link SchedulerFactoryBean}
|
||||
* reflection entries are registered.
|
||||
* reflection hints are registered.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @author Stephane Nicoll
|
||||
@@ -37,7 +37,7 @@ class SchedulerFactoryBeanRuntimeHints implements RuntimeHintsRegistrar {
|
||||
|
||||
private static final String SCHEDULER_FACTORY_CLASS_NAME = "org.quartz.impl.StdSchedulerFactory";
|
||||
|
||||
private final ReflectiveRuntimeHintsRegistrar reflectiveRegistrar = new ReflectiveRuntimeHintsRegistrar();
|
||||
private static final ReflectiveRuntimeHintsRegistrar registrar = new ReflectiveRuntimeHintsRegistrar();
|
||||
|
||||
|
||||
@Override
|
||||
@@ -49,7 +49,7 @@ class SchedulerFactoryBeanRuntimeHints implements RuntimeHintsRegistrar {
|
||||
.registerType(TypeReference.of(SCHEDULER_FACTORY_CLASS_NAME), this::typeHint)
|
||||
.registerTypes(TypeReference.listOf(ResourceLoaderClassLoadHelper.class,
|
||||
LocalTaskExecutorThreadPool.class, LocalDataSourceJobStore.class), this::typeHint);
|
||||
this.reflectiveRegistrar.registerRuntimeHints(hints, LocalTaskExecutorThreadPool.class);
|
||||
registrar.registerRuntimeHints(hints, LocalTaskExecutorThreadPool.class);
|
||||
}
|
||||
|
||||
private void typeHint(Builder typeHint) {
|
||||
|
||||
+15
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -28,6 +28,7 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.aop.scope.ScopedProxyFactoryBean;
|
||||
import org.springframework.asm.Opcodes;
|
||||
import org.springframework.asm.Type;
|
||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
@@ -37,6 +38,7 @@ import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.beans.factory.support.SimpleInstantiationStrategy;
|
||||
import org.springframework.cglib.core.ClassGenerator;
|
||||
import org.springframework.cglib.core.ClassLoaderAwareGeneratorStrategy;
|
||||
import org.springframework.cglib.core.CodeGenerationException;
|
||||
import org.springframework.cglib.core.SpringNamingPolicy;
|
||||
import org.springframework.cglib.proxy.Callback;
|
||||
import org.springframework.cglib.proxy.CallbackFilter;
|
||||
@@ -106,12 +108,19 @@ class ConfigurationClassEnhancer {
|
||||
}
|
||||
return configClass;
|
||||
}
|
||||
Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace(String.format("Successfully enhanced %s; enhanced class name is: %s",
|
||||
configClass.getName(), enhancedClass.getName()));
|
||||
try {
|
||||
Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace(String.format("Successfully enhanced %s; enhanced class name is: %s",
|
||||
configClass.getName(), enhancedClass.getName()));
|
||||
}
|
||||
return enhancedClass;
|
||||
}
|
||||
catch (CodeGenerationException ex) {
|
||||
throw new BeanDefinitionStoreException("Could not enhance configuration class [" + configClass.getName() +
|
||||
"]. Consider declaring @Configuration(proxyBeanMethods=false) without inter-bean references " +
|
||||
"between @Bean methods on the configuration class, avoiding the need for CGLIB enhancement.", ex);
|
||||
}
|
||||
return enhancedClass;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -225,7 +234,6 @@ class ConfigurationClassEnhancer {
|
||||
};
|
||||
return new TransformingClassGenerator(cg, transformer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
+24
-20
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -75,6 +75,7 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProce
|
||||
import org.springframework.beans.factory.support.BeanNameGenerator;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.RegisteredBean;
|
||||
import org.springframework.beans.factory.support.RegisteredBean.InstantiationDescriptor;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.context.ApplicationStartupAware;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
@@ -387,11 +388,11 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
||||
});
|
||||
|
||||
// Detect any custom bean name generation strategy supplied through the enclosing application context
|
||||
SingletonBeanRegistry sbr = null;
|
||||
if (registry instanceof SingletonBeanRegistry _sbr) {
|
||||
sbr = _sbr;
|
||||
SingletonBeanRegistry singletonRegistry = null;
|
||||
if (registry instanceof SingletonBeanRegistry sbr) {
|
||||
singletonRegistry = sbr;
|
||||
if (!this.localBeanNameGeneratorSet) {
|
||||
BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
|
||||
BeanNameGenerator generator = (BeanNameGenerator) singletonRegistry.getSingleton(
|
||||
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
|
||||
if (generator != null) {
|
||||
this.componentScanBeanNameGenerator = generator;
|
||||
@@ -452,8 +453,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
||||
while (!candidates.isEmpty());
|
||||
|
||||
// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
|
||||
if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
|
||||
sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
|
||||
if (singletonRegistry != null && !singletonRegistry.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
|
||||
singletonRegistry.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
|
||||
}
|
||||
|
||||
// Store the PropertySourceDescriptors to contribute them Ahead-of-time if necessary
|
||||
@@ -552,6 +553,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public PropertyValues postProcessProperties(@Nullable PropertyValues pvs, Object bean, String beanName) {
|
||||
// Inject the BeanFactory before AutowiredAnnotationBeanPostProcessor's
|
||||
// postProcessProperties method attempts to autowire other configuration beans.
|
||||
@@ -647,9 +649,9 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
||||
}
|
||||
return mappings;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class PropertySourcesAotContribution implements BeanFactoryInitializationAotContribution {
|
||||
|
||||
private static final String ENVIRONMENT_VARIABLE = "environment";
|
||||
@@ -761,17 +763,18 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
||||
return nonNull.get();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class ConfigurationClassProxyBeanRegistrationCodeFragments extends BeanRegistrationCodeFragmentsDecorator {
|
||||
|
||||
private final RegisteredBean registeredBean;
|
||||
|
||||
private final Class<?> proxyClass;
|
||||
|
||||
public ConfigurationClassProxyBeanRegistrationCodeFragments(BeanRegistrationCodeFragments codeFragments,
|
||||
RegisteredBean registeredBean) {
|
||||
public ConfigurationClassProxyBeanRegistrationCodeFragments(
|
||||
BeanRegistrationCodeFragments codeFragments, RegisteredBean registeredBean) {
|
||||
|
||||
super(codeFragments);
|
||||
this.registeredBean = registeredBean;
|
||||
this.proxyClass = registeredBean.getBeanType().toClass();
|
||||
@@ -780,6 +783,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
||||
@Override
|
||||
public CodeBlock generateSetBeanDefinitionPropertiesCode(GenerationContext generationContext,
|
||||
BeanRegistrationCode beanRegistrationCode, RootBeanDefinition beanDefinition, Predicate<String> attributeFilter) {
|
||||
|
||||
CodeBlock.Builder code = CodeBlock.builder();
|
||||
code.add(super.generateSetBeanDefinitionPropertiesCode(generationContext,
|
||||
beanRegistrationCode, beanDefinition, attributeFilter));
|
||||
@@ -790,29 +794,29 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
||||
|
||||
@Override
|
||||
public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext,
|
||||
BeanRegistrationCode beanRegistrationCode,
|
||||
boolean allowDirectSupplierShortcut) {
|
||||
BeanRegistrationCode beanRegistrationCode, boolean allowDirectSupplierShortcut) {
|
||||
|
||||
Executable executableToUse = proxyExecutable(generationContext.getRuntimeHints(),
|
||||
this.registeredBean.resolveConstructorOrFactoryMethod());
|
||||
InstantiationDescriptor instantiationDescriptor = proxyInstantiationDescriptor(
|
||||
generationContext.getRuntimeHints(), this.registeredBean.resolveInstantiationDescriptor());
|
||||
return new InstanceSupplierCodeGenerator(generationContext,
|
||||
beanRegistrationCode.getClassName(), beanRegistrationCode.getMethods(), allowDirectSupplierShortcut)
|
||||
.generateCode(this.registeredBean, executableToUse);
|
||||
.generateCode(this.registeredBean, instantiationDescriptor);
|
||||
}
|
||||
|
||||
private Executable proxyExecutable(RuntimeHints runtimeHints, Executable userExecutable) {
|
||||
private InstantiationDescriptor proxyInstantiationDescriptor(RuntimeHints runtimeHints, InstantiationDescriptor instantiationDescriptor) {
|
||||
Executable userExecutable = instantiationDescriptor.executable();
|
||||
if (userExecutable instanceof Constructor<?> userConstructor) {
|
||||
try {
|
||||
runtimeHints.reflection().registerConstructor(userConstructor, ExecutableMode.INTROSPECT);
|
||||
return this.proxyClass.getConstructor(userExecutable.getParameterTypes());
|
||||
Constructor<?> constructor = this.proxyClass.getConstructor(userExecutable.getParameterTypes());
|
||||
return new InstantiationDescriptor(constructor);
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
throw new IllegalStateException("No matching constructor found on proxy " + this.proxyClass, ex);
|
||||
}
|
||||
}
|
||||
return userExecutable;
|
||||
return instantiationDescriptor;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+7
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -39,7 +39,8 @@ import org.springframework.beans.factory.support.RegisteredBean;
|
||||
*/
|
||||
class ReflectiveProcessorBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor {
|
||||
|
||||
private static final ReflectiveRuntimeHintsRegistrar REGISTRAR = new ReflectiveRuntimeHintsRegistrar();
|
||||
private static final ReflectiveRuntimeHintsRegistrar registrar = new ReflectiveRuntimeHintsRegistrar();
|
||||
|
||||
|
||||
@Override
|
||||
public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
|
||||
@@ -49,7 +50,9 @@ class ReflectiveProcessorBeanFactoryInitializationAotProcessor implements BeanFa
|
||||
return new ReflectiveProcessorBeanFactoryInitializationAotContribution(beanTypes);
|
||||
}
|
||||
|
||||
private static class ReflectiveProcessorBeanFactoryInitializationAotContribution implements BeanFactoryInitializationAotContribution {
|
||||
|
||||
private static class ReflectiveProcessorBeanFactoryInitializationAotContribution
|
||||
implements BeanFactoryInitializationAotContribution {
|
||||
|
||||
private final Class<?>[] types;
|
||||
|
||||
@@ -60,9 +63,8 @@ class ReflectiveProcessorBeanFactoryInitializationAotProcessor implements BeanFa
|
||||
@Override
|
||||
public void applyTo(GenerationContext generationContext, BeanFactoryInitializationCode beanFactoryInitializationCode) {
|
||||
RuntimeHints runtimeHints = generationContext.getRuntimeHints();
|
||||
REGISTRAR.registerRuntimeHints(runtimeHints, this.types);
|
||||
registrar.registerRuntimeHints(runtimeHints, this.types);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -949,7 +949,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
||||
// Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
|
||||
String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
|
||||
for (String weaverAwareName : weaverAwareNames) {
|
||||
getBean(weaverAwareName);
|
||||
beanFactory.getBean(weaverAwareName, LoadTimeWeaverAware.class);
|
||||
}
|
||||
|
||||
// Stop using the temporary ClassLoader for type matching.
|
||||
|
||||
+2
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -126,6 +126,7 @@ public abstract class AbstractRefreshableApplicationContext extends AbstractAppl
|
||||
try {
|
||||
DefaultListableBeanFactory beanFactory = createBeanFactory();
|
||||
beanFactory.setSerializationId(getId());
|
||||
beanFactory.setApplicationStartup(getApplicationStartup());
|
||||
customizeBeanFactory(beanFactory);
|
||||
loadBeanDefinitions(beanFactory);
|
||||
this.beanFactory = beanFactory;
|
||||
|
||||
+32
-10
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,6 +18,7 @@ package org.springframework.context.support;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Supplier;
|
||||
@@ -428,16 +429,37 @@ public class GenericApplicationContext extends AbstractApplicationContext implem
|
||||
PostProcessorRegistrationDelegate.loadBeanPostProcessors(
|
||||
this.beanFactory, SmartInstantiationAwareBeanPostProcessor.class);
|
||||
|
||||
List<String> lazyBeans = new ArrayList<>();
|
||||
|
||||
// First round: non-lazy singleton beans in definition order,
|
||||
// matching preInstantiateSingletons.
|
||||
for (String beanName : this.beanFactory.getBeanDefinitionNames()) {
|
||||
Class<?> beanType = this.beanFactory.getType(beanName);
|
||||
if (beanType != null) {
|
||||
ClassHintUtils.registerProxyIfNecessary(beanType, runtimeHints);
|
||||
for (SmartInstantiationAwareBeanPostProcessor bpp : bpps) {
|
||||
Class<?> newBeanType = bpp.determineBeanType(beanType, beanName);
|
||||
if (newBeanType != beanType) {
|
||||
ClassHintUtils.registerProxyIfNecessary(newBeanType, runtimeHints);
|
||||
beanType = newBeanType;
|
||||
}
|
||||
BeanDefinition bd = getBeanDefinition(beanName);
|
||||
if (bd.isSingleton() && !bd.isLazyInit()) {
|
||||
preDetermineBeanType(beanName, bpps, runtimeHints);
|
||||
}
|
||||
else {
|
||||
lazyBeans.add(beanName);
|
||||
}
|
||||
}
|
||||
|
||||
// Second round: lazy singleton beans and scoped beans.
|
||||
for (String beanName : lazyBeans) {
|
||||
preDetermineBeanType(beanName, bpps, runtimeHints);
|
||||
}
|
||||
}
|
||||
|
||||
private void preDetermineBeanType(String beanName, List<SmartInstantiationAwareBeanPostProcessor> bpps,
|
||||
RuntimeHints runtimeHints) {
|
||||
|
||||
Class<?> beanType = this.beanFactory.getType(beanName);
|
||||
if (beanType != null) {
|
||||
ClassHintUtils.registerProxyIfNecessary(beanType, runtimeHints);
|
||||
for (SmartInstantiationAwareBeanPostProcessor bpp : bpps) {
|
||||
Class<?> newBeanType = bpp.determineBeanType(beanType, beanName);
|
||||
if (newBeanType != beanType) {
|
||||
ClassHintUtils.registerProxyIfNecessary(newBeanType, runtimeHints);
|
||||
beanType = newBeanType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+28
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,8 +22,10 @@ import java.text.SimpleDateFormat;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.EnumMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import org.springframework.format.Formatter;
|
||||
@@ -35,9 +37,14 @@ import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A formatter for {@link java.util.Date} types.
|
||||
*
|
||||
* <p>Supports the configuration of an explicit date time pattern, timezone,
|
||||
* locale, and fallback date time patterns for lenient parsing.
|
||||
*
|
||||
* <p>Common ISO patterns for UTC instants are applied at millisecond precision.
|
||||
* Note that {@link org.springframework.format.datetime.standard.InstantFormatter}
|
||||
* is recommended for flexible UTC parsing into a {@link java.time.Instant} instead.
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @author Juergen Hoeller
|
||||
* @author Phillip Webb
|
||||
@@ -49,15 +56,23 @@ public class DateFormatter implements Formatter<Date> {
|
||||
|
||||
private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
|
||||
|
||||
// We use an EnumMap instead of Map.of(...) since the former provides better performance.
|
||||
private static final Map<ISO, String> ISO_PATTERNS;
|
||||
|
||||
private static final Map<ISO, String> ISO_FALLBACK_PATTERNS;
|
||||
|
||||
static {
|
||||
// We use an EnumMap instead of Map.of(...) since the former provides better performance.
|
||||
Map<ISO, String> formats = new EnumMap<>(ISO.class);
|
||||
formats.put(ISO.DATE, "yyyy-MM-dd");
|
||||
formats.put(ISO.TIME, "HH:mm:ss.SSSXXX");
|
||||
formats.put(ISO.DATE_TIME, "yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
|
||||
ISO_PATTERNS = Collections.unmodifiableMap(formats);
|
||||
|
||||
// Fallback format for the time part without milliseconds.
|
||||
Map<ISO, String> fallbackFormats = new EnumMap<>(ISO.class);
|
||||
fallbackFormats.put(ISO.TIME, "HH:mm:ssXXX");
|
||||
fallbackFormats.put(ISO.DATE_TIME, "yyyy-MM-dd'T'HH:mm:ssXXX");
|
||||
ISO_FALLBACK_PATTERNS = Collections.unmodifiableMap(fallbackFormats);
|
||||
}
|
||||
|
||||
|
||||
@@ -202,8 +217,16 @@ public class DateFormatter implements Formatter<Date> {
|
||||
return getDateFormat(locale).parse(text);
|
||||
}
|
||||
catch (ParseException ex) {
|
||||
Set<String> fallbackPatterns = new LinkedHashSet<>();
|
||||
String isoPattern = ISO_FALLBACK_PATTERNS.get(this.iso);
|
||||
if (isoPattern != null) {
|
||||
fallbackPatterns.add(isoPattern);
|
||||
}
|
||||
if (!ObjectUtils.isEmpty(this.fallbackPatterns)) {
|
||||
for (String pattern : this.fallbackPatterns) {
|
||||
Collections.addAll(fallbackPatterns, this.fallbackPatterns);
|
||||
}
|
||||
if (!fallbackPatterns.isEmpty()) {
|
||||
for (String pattern : fallbackPatterns) {
|
||||
try {
|
||||
DateFormat dateFormat = configureDateFormat(new SimpleDateFormat(pattern, locale));
|
||||
// Align timezone for parsing format with printing format if ISO is set.
|
||||
@@ -221,8 +244,8 @@ public class DateFormatter implements Formatter<Date> {
|
||||
}
|
||||
if (this.source != null) {
|
||||
ParseException parseException = new ParseException(
|
||||
String.format("Unable to parse date time value \"%s\" using configuration from %s", text, this.source),
|
||||
ex.getErrorOffset());
|
||||
String.format("Unable to parse date time value \"%s\" using configuration from %s", text, this.source),
|
||||
ex.getErrorOffset());
|
||||
parseException.initCause(ex);
|
||||
throw parseException;
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -45,12 +45,12 @@ public class InstantFormatter implements Formatter<Instant> {
|
||||
return Instant.ofEpochMilli(Long.parseLong(text));
|
||||
}
|
||||
catch (NumberFormatException ex) {
|
||||
if (text.length() > 0 && Character.isAlphabetic(text.charAt(0))) {
|
||||
if (!text.isEmpty() && Character.isAlphabetic(text.charAt(0))) {
|
||||
// assuming RFC-1123 value a la "Tue, 3 Jun 2008 11:05:30 GMT"
|
||||
return Instant.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(text));
|
||||
}
|
||||
else {
|
||||
// assuming UTC instant a la "2007-12-03T10:15:30.00Z"
|
||||
// assuming UTC instant a la "2007-12-03T10:15:30.000Z"
|
||||
return Instant.parse(text);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -411,7 +411,7 @@ public class ScheduledAnnotationBeanPostProcessor
|
||||
}
|
||||
catch (RuntimeException ex) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid initialDelayString value \"" + initialDelayString + "\" - cannot parse into long");
|
||||
"Invalid initialDelayString value \"" + initialDelayString + "\"; " + ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -463,7 +463,7 @@ public class ScheduledAnnotationBeanPostProcessor
|
||||
}
|
||||
catch (RuntimeException ex) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid fixedDelayString value \"" + fixedDelayString + "\" - cannot parse into long");
|
||||
"Invalid fixedDelayString value \"" + fixedDelayString + "\"; " + ex);
|
||||
}
|
||||
tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, delayToUse)));
|
||||
}
|
||||
@@ -489,7 +489,7 @@ public class ScheduledAnnotationBeanPostProcessor
|
||||
}
|
||||
catch (RuntimeException ex) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid fixedRateString value \"" + fixedRateString + "\" - cannot parse into long");
|
||||
"Invalid fixedRateString value \"" + fixedRateString + "\"; " + ex);
|
||||
}
|
||||
tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, delayToUse)));
|
||||
}
|
||||
|
||||
+3
-6
@@ -28,17 +28,14 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
*
|
||||
* @author Adrian Colyer
|
||||
* @author Chris Beams
|
||||
* @author Juergen Hoeller
|
||||
*/
|
||||
class OverloadedAdviceTests {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("resource")
|
||||
void testExceptionOnConfigParsingWithMismatchedAdviceMethod() {
|
||||
assertThatExceptionOfType(BeanCreationException.class)
|
||||
.isThrownBy(() -> new ClassPathXmlApplicationContext(getClass().getSimpleName() + ".xml", getClass()))
|
||||
.havingRootCause()
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.as("invalidAbsoluteTypeName should be detected by AJ").withMessageContaining("invalidAbsoluteTypeName");
|
||||
void testConfigParsingWithMismatchedAdviceMethod() {
|
||||
new ClassPathXmlApplicationContext(getClass().getSimpleName() + ".xml", getClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+6
-6
@@ -101,8 +101,8 @@ class ConfigurationClassPostProcessorAotContributionTests {
|
||||
initializer.accept(freshBeanFactory);
|
||||
freshContext.refresh();
|
||||
assertThat(freshBeanFactory.getBeanPostProcessors()).filteredOn(ImportAwareAotBeanPostProcessor.class::isInstance)
|
||||
.singleElement().satisfies(postProcessor -> assertPostProcessorEntry(postProcessor, ImportAwareConfiguration.class,
|
||||
ImportConfiguration.class));
|
||||
.singleElement().satisfies(postProcessor ->
|
||||
assertPostProcessorEntry(postProcessor, ImportAwareConfiguration.class, ImportConfiguration.class));
|
||||
freshContext.close();
|
||||
});
|
||||
}
|
||||
@@ -245,6 +245,7 @@ class ConfigurationClassPostProcessorAotContributionTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
class PropertySourceTests {
|
||||
|
||||
@@ -410,9 +411,9 @@ class ConfigurationClassPostProcessorAotContributionTests {
|
||||
@PropertySource("classpath:org/springframework/context/annotation/p?.properties")
|
||||
static class PropertySourceWithWildcardLocationPatternConfiguration {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
class ConfigurationClassProxyTests {
|
||||
|
||||
@@ -432,15 +433,14 @@ class ConfigurationClassPostProcessorAotContributionTests {
|
||||
getRegisteredBean(CglibConfiguration.class))).isNotNull();
|
||||
}
|
||||
|
||||
|
||||
private RegisteredBean getRegisteredBean(Class<?> bean) {
|
||||
this.beanFactory.registerBeanDefinition("test", new RootBeanDefinition(bean));
|
||||
this.processor.postProcessBeanFactory(this.beanFactory);
|
||||
return RegisteredBean.of(this.beanFactory, "test");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
private BeanFactoryInitializationAotContribution getContribution(Class<?>... types) {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
@@ -458,8 +458,8 @@ class ConfigurationClassPostProcessorAotContributionTests {
|
||||
.containsExactly(entry(key.getName(), value.getName()));
|
||||
}
|
||||
|
||||
static class CustomPropertySourcesFactory extends DefaultPropertySourceFactory {
|
||||
|
||||
static class CustomPropertySourcesFactory extends DefaultPropertySourceFactory {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+48
-30
@@ -29,7 +29,9 @@ import org.springframework.beans.factory.support.AbstractBeanFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.GenericBeanDefinition;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -39,8 +41,9 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* {@link FactoryBean FactoryBeans} defined in the configuration.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Juergen Hoeller
|
||||
*/
|
||||
class ConfigurationWithFactoryBeanBeanEarlyDeductionTests {
|
||||
class ConfigurationWithFactoryBeanEarlyDeductionTests {
|
||||
|
||||
@Test
|
||||
void preFreezeDirect() {
|
||||
@@ -82,6 +85,16 @@ class ConfigurationWithFactoryBeanBeanEarlyDeductionTests {
|
||||
assertPostFreeze(AttributeClassConfiguration.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void preFreezeTargetType() {
|
||||
assertPreFreeze(TargetTypeConfiguration.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void postFreezeTargetType() {
|
||||
assertPostFreeze(TargetTypeConfiguration.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void preFreezeUnresolvedGenericFactoryBean() {
|
||||
// Covers the case where a @Configuration is picked up via component scanning
|
||||
@@ -105,14 +118,13 @@ class ConfigurationWithFactoryBeanBeanEarlyDeductionTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void assertPostFreeze(Class<?> configurationClass) {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
configurationClass);
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(configurationClass);
|
||||
assertContainsMyBeanName(context);
|
||||
}
|
||||
|
||||
private void assertPreFreeze(Class<?> configurationClass,
|
||||
BeanFactoryPostProcessor... postProcessors) {
|
||||
private void assertPreFreeze(Class<?> configurationClass, BeanFactoryPostProcessor... postProcessors) {
|
||||
NameCollectingBeanFactoryPostProcessor postProcessor = new NameCollectingBeanFactoryPostProcessor();
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
||||
try (context) {
|
||||
@@ -132,41 +144,38 @@ class ConfigurationWithFactoryBeanBeanEarlyDeductionTests {
|
||||
assertThat(names).containsExactly("myBean");
|
||||
}
|
||||
|
||||
private static class NameCollectingBeanFactoryPostProcessor
|
||||
implements BeanFactoryPostProcessor {
|
||||
|
||||
private static class NameCollectingBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
|
||||
|
||||
private String[] names;
|
||||
|
||||
@Override
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
|
||||
throws BeansException {
|
||||
this.names = beanFactory.getBeanNamesForType(MyBean.class, true, false);
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
||||
ResolvableType typeToMatch = ResolvableType.forClassWithGenerics(MyBean.class, String.class);
|
||||
this.names = beanFactory.getBeanNamesForType(typeToMatch, true, false);
|
||||
}
|
||||
|
||||
public String[] getNames() {
|
||||
return this.names;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class DirectConfiguration {
|
||||
|
||||
@Bean
|
||||
MyBean myBean() {
|
||||
return new MyBean();
|
||||
MyBean<String> myBean() {
|
||||
return new MyBean<>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class GenericMethodConfiguration {
|
||||
|
||||
@Bean
|
||||
FactoryBean<MyBean> myBean() {
|
||||
return new TestFactoryBean<>(new MyBean());
|
||||
FactoryBean<MyBean<String>> myBean() {
|
||||
return new TestFactoryBean<>(new MyBean<>());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@@ -176,13 +185,11 @@ class ConfigurationWithFactoryBeanBeanEarlyDeductionTests {
|
||||
MyFactoryBean myBean() {
|
||||
return new MyFactoryBean();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Import(AttributeClassRegistrar.class)
|
||||
static class AttributeClassConfiguration {
|
||||
|
||||
}
|
||||
|
||||
static class AttributeClassRegistrar implements ImportBeanDefinitionRegistrar {
|
||||
@@ -191,16 +198,32 @@ class ConfigurationWithFactoryBeanBeanEarlyDeductionTests {
|
||||
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
|
||||
BeanDefinition definition = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
RawWithAbstractObjectTypeFactoryBean.class).getBeanDefinition();
|
||||
definition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, MyBean.class);
|
||||
definition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE,
|
||||
ResolvableType.forClassWithGenerics(MyBean.class, String.class));
|
||||
registry.registerBeanDefinition("myBean", definition);
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Import(TargetTypeRegistrar.class)
|
||||
static class TargetTypeConfiguration {
|
||||
}
|
||||
|
||||
static class TargetTypeRegistrar implements ImportBeanDefinitionRegistrar {
|
||||
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
|
||||
RootBeanDefinition definition = new RootBeanDefinition(RawWithAbstractObjectTypeFactoryBean.class);
|
||||
definition.setTargetType(ResolvableType.forClassWithGenerics(FactoryBean.class,
|
||||
ResolvableType.forClassWithGenerics(MyBean.class, String.class)));
|
||||
registry.registerBeanDefinition("myBean", definition);
|
||||
}
|
||||
}
|
||||
|
||||
abstract static class AbstractMyBean {
|
||||
}
|
||||
|
||||
static class MyBean extends AbstractMyBean {
|
||||
static class MyBean<T> extends AbstractMyBean {
|
||||
}
|
||||
|
||||
static class TestFactoryBean<T> implements FactoryBean<T> {
|
||||
@@ -220,31 +243,26 @@ class ConfigurationWithFactoryBeanBeanEarlyDeductionTests {
|
||||
public Class<?> getObjectType() {
|
||||
return this.instance.getClass();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class MyFactoryBean extends TestFactoryBean<MyBean> {
|
||||
static class MyFactoryBean extends TestFactoryBean<MyBean<String>> {
|
||||
|
||||
public MyFactoryBean() {
|
||||
super(new MyBean());
|
||||
super(new MyBean<>());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class RawWithAbstractObjectTypeFactoryBean implements FactoryBean<Object> {
|
||||
|
||||
private final Object object = new MyBean();
|
||||
|
||||
@Override
|
||||
public Object getObject() {
|
||||
return object;
|
||||
public Object getObject() throws Exception {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return MyBean.class;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+26
-7
@@ -35,6 +35,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @author Phillip Webb
|
||||
* @author Juergen Hoeller
|
||||
*/
|
||||
class DateFormatterTests {
|
||||
|
||||
@@ -45,6 +46,7 @@ class DateFormatterTests {
|
||||
void shouldPrintAndParseDefault() throws Exception {
|
||||
DateFormatter formatter = new DateFormatter();
|
||||
formatter.setTimeZone(UTC);
|
||||
|
||||
Date date = getDate(2009, Calendar.JUNE, 1);
|
||||
assertThat(formatter.print(date, Locale.US)).isEqualTo("Jun 1, 2009");
|
||||
assertThat(formatter.parse("Jun 1, 2009", Locale.US)).isEqualTo(date);
|
||||
@@ -54,6 +56,7 @@ class DateFormatterTests {
|
||||
void shouldPrintAndParseFromPattern() throws ParseException {
|
||||
DateFormatter formatter = new DateFormatter("yyyy-MM-dd");
|
||||
formatter.setTimeZone(UTC);
|
||||
|
||||
Date date = getDate(2009, Calendar.JUNE, 1);
|
||||
assertThat(formatter.print(date, Locale.US)).isEqualTo("2009-06-01");
|
||||
assertThat(formatter.parse("2009-06-01", Locale.US)).isEqualTo(date);
|
||||
@@ -64,6 +67,7 @@ class DateFormatterTests {
|
||||
DateFormatter formatter = new DateFormatter();
|
||||
formatter.setTimeZone(UTC);
|
||||
formatter.setStyle(DateFormat.SHORT);
|
||||
|
||||
Date date = getDate(2009, Calendar.JUNE, 1);
|
||||
assertThat(formatter.print(date, Locale.US)).isEqualTo("6/1/09");
|
||||
assertThat(formatter.parse("6/1/09", Locale.US)).isEqualTo(date);
|
||||
@@ -74,6 +78,7 @@ class DateFormatterTests {
|
||||
DateFormatter formatter = new DateFormatter();
|
||||
formatter.setTimeZone(UTC);
|
||||
formatter.setStyle(DateFormat.MEDIUM);
|
||||
|
||||
Date date = getDate(2009, Calendar.JUNE, 1);
|
||||
assertThat(formatter.print(date, Locale.US)).isEqualTo("Jun 1, 2009");
|
||||
assertThat(formatter.parse("Jun 1, 2009", Locale.US)).isEqualTo(date);
|
||||
@@ -84,6 +89,7 @@ class DateFormatterTests {
|
||||
DateFormatter formatter = new DateFormatter();
|
||||
formatter.setTimeZone(UTC);
|
||||
formatter.setStyle(DateFormat.LONG);
|
||||
|
||||
Date date = getDate(2009, Calendar.JUNE, 1);
|
||||
assertThat(formatter.print(date, Locale.US)).isEqualTo("June 1, 2009");
|
||||
assertThat(formatter.parse("June 1, 2009", Locale.US)).isEqualTo(date);
|
||||
@@ -94,16 +100,18 @@ class DateFormatterTests {
|
||||
DateFormatter formatter = new DateFormatter();
|
||||
formatter.setTimeZone(UTC);
|
||||
formatter.setStyle(DateFormat.FULL);
|
||||
|
||||
Date date = getDate(2009, Calendar.JUNE, 1);
|
||||
assertThat(formatter.print(date, Locale.US)).isEqualTo("Monday, June 1, 2009");
|
||||
assertThat(formatter.parse("Monday, June 1, 2009", Locale.US)).isEqualTo(date);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPrintAndParseISODate() throws Exception {
|
||||
void shouldPrintAndParseIsoDate() throws Exception {
|
||||
DateFormatter formatter = new DateFormatter();
|
||||
formatter.setTimeZone(UTC);
|
||||
formatter.setIso(ISO.DATE);
|
||||
|
||||
Date date = getDate(2009, Calendar.JUNE, 1, 14, 23, 5, 3);
|
||||
assertThat(formatter.print(date, Locale.US)).isEqualTo("2009-06-01");
|
||||
assertThat(formatter.parse("2009-6-01", Locale.US))
|
||||
@@ -111,33 +119,44 @@ class DateFormatterTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPrintAndParseISOTime() throws Exception {
|
||||
void shouldPrintAndParseIsoTime() throws Exception {
|
||||
DateFormatter formatter = new DateFormatter();
|
||||
formatter.setTimeZone(UTC);
|
||||
formatter.setIso(ISO.TIME);
|
||||
|
||||
Date date = getDate(2009, Calendar.JANUARY, 1, 14, 23, 5, 3);
|
||||
assertThat(formatter.print(date, Locale.US)).isEqualTo("14:23:05.003Z");
|
||||
assertThat(formatter.parse("14:23:05.003Z", Locale.US))
|
||||
.isEqualTo(getDate(1970, Calendar.JANUARY, 1, 14, 23, 5, 3));
|
||||
|
||||
date = getDate(2009, Calendar.JANUARY, 1, 14, 23, 5, 0);
|
||||
assertThat(formatter.print(date, Locale.US)).isEqualTo("14:23:05.000Z");
|
||||
assertThat(formatter.parse("14:23:05Z", Locale.US))
|
||||
.isEqualTo(getDate(1970, Calendar.JANUARY, 1, 14, 23, 5, 0).toInstant());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPrintAndParseISODateTime() throws Exception {
|
||||
void shouldPrintAndParseIsoDateTime() throws Exception {
|
||||
DateFormatter formatter = new DateFormatter();
|
||||
formatter.setTimeZone(UTC);
|
||||
formatter.setIso(ISO.DATE_TIME);
|
||||
|
||||
Date date = getDate(2009, Calendar.JUNE, 1, 14, 23, 5, 3);
|
||||
assertThat(formatter.print(date, Locale.US)).isEqualTo("2009-06-01T14:23:05.003Z");
|
||||
assertThat(formatter.parse("2009-06-01T14:23:05.003Z", Locale.US)).isEqualTo(date);
|
||||
|
||||
date = getDate(2009, Calendar.JUNE, 1, 14, 23, 5, 0);
|
||||
assertThat(formatter.print(date, Locale.US)).isEqualTo("2009-06-01T14:23:05.000Z");
|
||||
assertThat(formatter.parse("2009-06-01T14:23:05Z", Locale.US)).isEqualTo(date.toInstant());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowOnUnsupportedStylePattern() {
|
||||
DateFormatter formatter = new DateFormatter();
|
||||
formatter.setStylePattern("OO");
|
||||
assertThatIllegalStateException().isThrownBy(() ->
|
||||
formatter.parse("2009", Locale.US))
|
||||
.withMessageContaining("Unsupported style pattern 'OO'");
|
||||
|
||||
assertThatIllegalStateException().isThrownBy(() -> formatter.parse("2009", Locale.US))
|
||||
.withMessageContaining("Unsupported style pattern 'OO'");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -148,8 +167,8 @@ class DateFormatterTests {
|
||||
formatter.setStylePattern("L-");
|
||||
formatter.setIso(ISO.DATE_TIME);
|
||||
formatter.setPattern("yyyy");
|
||||
Date date = getDate(2009, Calendar.JUNE, 1, 14, 23, 5, 3);
|
||||
|
||||
Date date = getDate(2009, Calendar.JUNE, 1, 14, 23, 5, 3);
|
||||
assertThat(formatter.print(date, Locale.US)).as("uses pattern").isEqualTo("2009");
|
||||
|
||||
formatter.setPattern("");
|
||||
|
||||
+10
-20
@@ -274,9 +274,7 @@ class DateTimeFormattingTests {
|
||||
binder.bind(propertyValues);
|
||||
assertThat(binder.getBindingResult().getErrorCount()).isZero();
|
||||
String value = binder.getBindingResult().getFieldValue("localDateTime").toString();
|
||||
assertThat(value)
|
||||
.startsWith("10/31/09")
|
||||
.endsWith("12:00 PM");
|
||||
assertThat(value).startsWith("10/31/09").endsWith("12:00 PM");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -286,9 +284,7 @@ class DateTimeFormattingTests {
|
||||
binder.bind(propertyValues);
|
||||
assertThat(binder.getBindingResult().getErrorCount()).isZero();
|
||||
String value = binder.getBindingResult().getFieldValue("localDateTime").toString();
|
||||
assertThat(value)
|
||||
.startsWith("10/31/09")
|
||||
.endsWith("12:00 PM");
|
||||
assertThat(value).startsWith("10/31/09").endsWith("12:00 PM");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -298,9 +294,7 @@ class DateTimeFormattingTests {
|
||||
binder.bind(propertyValues);
|
||||
assertThat(binder.getBindingResult().getErrorCount()).isZero();
|
||||
String value = binder.getBindingResult().getFieldValue("styleLocalDateTime").toString();
|
||||
assertThat(value)
|
||||
.startsWith("Oct 31, 2009")
|
||||
.endsWith("12:00:00 PM");
|
||||
assertThat(value).startsWith("Oct 31, 2009").endsWith("12:00:00 PM");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -310,9 +304,7 @@ class DateTimeFormattingTests {
|
||||
binder.bind(propertyValues);
|
||||
assertThat(binder.getBindingResult().getErrorCount()).isZero();
|
||||
String value = binder.getBindingResult().getFieldValue("localDateTime").toString();
|
||||
assertThat(value)
|
||||
.startsWith("10/31/09")
|
||||
.endsWith("12:00 PM");
|
||||
assertThat(value).startsWith("10/31/09").endsWith("12:00 PM");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -325,9 +317,7 @@ class DateTimeFormattingTests {
|
||||
binder.bind(propertyValues);
|
||||
assertThat(binder.getBindingResult().getErrorCount()).isZero();
|
||||
String value = binder.getBindingResult().getFieldValue("localDateTime").toString();
|
||||
assertThat(value)
|
||||
.startsWith("Oct 31, 2009")
|
||||
.endsWith("12:00:00 PM");
|
||||
assertThat(value).startsWith("Oct 31, 2009").endsWith("12:00:00 PM");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -540,6 +530,7 @@ class DateTimeFormattingTests {
|
||||
assertThat(binder.getBindingResult().getRawFieldValue("monthDayAnnotatedPattern")).isEqualTo(MonthDay.parse("--01-03"));
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
class FallbackPatternTests {
|
||||
|
||||
@@ -644,10 +635,10 @@ class DateTimeFormattingTests {
|
||||
@DateTimeFormat(style = "M-")
|
||||
private LocalDate styleLocalDate;
|
||||
|
||||
@DateTimeFormat(style = "S-", fallbackPatterns = { "yyyy-MM-dd", "yyyyMMdd", "yyyy.MM.dd" })
|
||||
@DateTimeFormat(style = "S-", fallbackPatterns = {"yyyy-MM-dd", "yyyyMMdd", "yyyy.MM.dd"})
|
||||
private LocalDate styleLocalDateWithFallbackPatterns;
|
||||
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd", fallbackPatterns = { "M/d/yy", "yyyyMMdd", "yyyy.MM.dd" })
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd", fallbackPatterns = {"M/d/yy", "yyyyMMdd", "yyyy.MM.dd"})
|
||||
private LocalDate patternLocalDateWithFallbackPatterns;
|
||||
|
||||
private LocalTime localTime;
|
||||
@@ -655,7 +646,7 @@ class DateTimeFormattingTests {
|
||||
@DateTimeFormat(style = "-M")
|
||||
private LocalTime styleLocalTime;
|
||||
|
||||
@DateTimeFormat(style = "-M", fallbackPatterns = { "HH:mm:ss", "HH:mm"})
|
||||
@DateTimeFormat(style = "-M", fallbackPatterns = {"HH:mm:ss", "HH:mm"})
|
||||
private LocalTime styleLocalTimeWithFallbackPatterns;
|
||||
|
||||
private LocalDateTime localDateTime;
|
||||
@@ -675,7 +666,7 @@ class DateTimeFormattingTests {
|
||||
@DateTimeFormat(iso = ISO.DATE_TIME)
|
||||
private LocalDateTime isoLocalDateTime;
|
||||
|
||||
@DateTimeFormat(iso = ISO.DATE_TIME, fallbackPatterns = { "yyyy-MM-dd HH:mm:ss", "M/d/yy HH:mm"})
|
||||
@DateTimeFormat(iso = ISO.DATE_TIME, fallbackPatterns = {"yyyy-MM-dd HH:mm:ss", "M/d/yy HH:mm"})
|
||||
private LocalDateTime isoLocalDateTimeWithFallbackPatterns;
|
||||
|
||||
private Instant instant;
|
||||
@@ -703,7 +694,6 @@ class DateTimeFormattingTests {
|
||||
|
||||
private final List<DateTimeBean> children = new ArrayList<>();
|
||||
|
||||
|
||||
public LocalDate getLocalDate() {
|
||||
return this.localDate;
|
||||
}
|
||||
|
||||
+11
-11
@@ -20,6 +20,7 @@ import java.text.ParseException;
|
||||
import java.time.Instant;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Locale;
|
||||
import java.util.Random;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -50,13 +51,12 @@ class InstantFormatterTests {
|
||||
|
||||
private final InstantFormatter instantFormatter = new InstantFormatter();
|
||||
|
||||
|
||||
@ParameterizedTest
|
||||
@ArgumentsSource(ISOSerializedInstantProvider.class)
|
||||
void should_parse_an_ISO_formatted_string_representation_of_an_Instant(String input) throws ParseException {
|
||||
Instant expected = DateTimeFormatter.ISO_INSTANT.parse(input, Instant::from);
|
||||
|
||||
Instant actual = instantFormatter.parse(input, null);
|
||||
|
||||
Instant actual = instantFormatter.parse(input, Locale.US);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@@ -64,9 +64,7 @@ class InstantFormatterTests {
|
||||
@ArgumentsSource(RFC1123SerializedInstantProvider.class)
|
||||
void should_parse_an_RFC1123_formatted_string_representation_of_an_Instant(String input) throws ParseException {
|
||||
Instant expected = DateTimeFormatter.RFC_1123_DATE_TIME.parse(input, Instant::from);
|
||||
|
||||
Instant actual = instantFormatter.parse(input, null);
|
||||
|
||||
Instant actual = instantFormatter.parse(input, Locale.US);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@@ -74,20 +72,18 @@ class InstantFormatterTests {
|
||||
@ArgumentsSource(RandomInstantProvider.class)
|
||||
void should_serialize_an_Instant_using_ISO_format_and_ignoring_Locale(Instant input) {
|
||||
String expected = DateTimeFormatter.ISO_INSTANT.format(input);
|
||||
|
||||
String actual = instantFormatter.print(input, null);
|
||||
|
||||
String actual = instantFormatter.print(input, Locale.US);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ArgumentsSource(RandomEpochMillisProvider.class)
|
||||
void should_parse_into_an_Instant_from_epoch_milli(Instant input) throws ParseException {
|
||||
Instant actual = instantFormatter.parse(Long.toString(input.toEpochMilli()), null);
|
||||
|
||||
Instant actual = instantFormatter.parse(Long.toString(input.toEpochMilli()), Locale.US);
|
||||
assertThat(actual).isEqualTo(input);
|
||||
}
|
||||
|
||||
|
||||
private static class RandomInstantProvider implements ArgumentsProvider {
|
||||
|
||||
private static final long DATA_SET_SIZE = 10;
|
||||
@@ -109,6 +105,7 @@ class InstantFormatterTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class ISOSerializedInstantProvider extends RandomInstantProvider {
|
||||
|
||||
@Override
|
||||
@@ -117,6 +114,7 @@ class InstantFormatterTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class RFC1123SerializedInstantProvider extends RandomInstantProvider {
|
||||
|
||||
// RFC-1123 supports only 4-digit years
|
||||
@@ -130,6 +128,8 @@ class InstantFormatterTests {
|
||||
.map(DateTimeFormatter.RFC_1123_DATE_TIME.withZone(systemDefault())::format);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static final class RandomEpochMillisProvider implements ArgumentsProvider {
|
||||
|
||||
private static final long DATA_SET_SIZE = 10;
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -64,9 +64,9 @@ class KotlinApplicationListenerMethodAdapterTests {
|
||||
private class StaticApplicationListenerMethodAdapter(method: Method, private val targetBean: Any) :
|
||||
ApplicationListenerMethodAdapter("unused", targetBean.javaClass, method) {
|
||||
public override fun getTargetBean(): Any {
|
||||
return targetBean
|
||||
return targetBean
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("RedundantSuspendModifier", "UNUSED_PARAMETER")
|
||||
private class SampleEvents {
|
||||
|
||||
+2
@@ -18,4 +18,6 @@
|
||||
|
||||
<bean id="testBean" class="org.springframework.beans.testfixture.beans.TestBean"/>
|
||||
|
||||
<bean id="testBean2" class="org.springframework.beans.testfixture.beans.TestBean"/>
|
||||
|
||||
</beans>
|
||||
+9
-9
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -124,7 +124,7 @@ class InstrumentedMethodTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void classGetConstructorShouldMatchInstrospectPublicConstructorsHint() {
|
||||
void classGetConstructorShouldMatchIntrospectPublicConstructorsHint() {
|
||||
hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_PUBLIC_CONSTRUCTORS);
|
||||
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCONSTRUCTOR, this.stringGetConstructor);
|
||||
}
|
||||
@@ -148,7 +148,7 @@ class InstrumentedMethodTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void classGetConstructorShouldMatchInstrospectConstructorHint() {
|
||||
void classGetConstructorShouldMatchIntrospectConstructorHint() {
|
||||
hints.reflection().registerType(String.class,typeHint ->
|
||||
typeHint.withConstructor(Collections.emptyList(), ExecutableMode.INTROSPECT));
|
||||
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCONSTRUCTOR, this.stringGetConstructor);
|
||||
@@ -210,7 +210,7 @@ class InstrumentedMethodTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void classGetDeclaredConstructorShouldMatchInstrospectConstructorHint() {
|
||||
void classGetDeclaredConstructorShouldMatchIntrospectConstructorHint() {
|
||||
hints.reflection().registerType(String.class, typeHint ->
|
||||
typeHint.withConstructor(TypeReference.listOf(byte[].class, byte.class), ExecutableMode.INTROSPECT));
|
||||
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDCONSTRUCTOR, this.stringGetDeclaredConstructor);
|
||||
@@ -354,13 +354,13 @@ class InstrumentedMethodTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void classGetMethodsShouldMatchInstrospectDeclaredMethodsHint() {
|
||||
void classGetMethodsShouldMatchIntrospectDeclaredMethodsHint() {
|
||||
hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_DECLARED_METHODS);
|
||||
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHODS, this.stringGetMethods);
|
||||
}
|
||||
|
||||
@Test
|
||||
void classGetMethodsShouldMatchInstrospectPublicMethodsHint() {
|
||||
void classGetMethodsShouldMatchIntrospectPublicMethodsHint() {
|
||||
hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_PUBLIC_METHODS);
|
||||
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHODS, this.stringGetMethods);
|
||||
}
|
||||
@@ -396,7 +396,7 @@ class InstrumentedMethodTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void classGetMethodShouldMatchInstrospectPublicMethodsHint() {
|
||||
void classGetMethodShouldMatchIntrospectPublicMethodsHint() {
|
||||
hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_PUBLIC_METHODS);
|
||||
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetToStringMethod);
|
||||
}
|
||||
@@ -434,13 +434,13 @@ class InstrumentedMethodTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void classGetMethodShouldNotMatchInstrospectPublicMethodsHintWhenPrivate() {
|
||||
void classGetMethodShouldNotMatchIntrospectPublicMethodsHintWhenPrivate() {
|
||||
hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_PUBLIC_METHODS);
|
||||
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetScaleMethod);
|
||||
}
|
||||
|
||||
@Test
|
||||
void classGetMethodShouldMatchInstrospectDeclaredMethodsHintWhenPrivate() {
|
||||
void classGetMethodShouldMatchIntrospectDeclaredMethodsHintWhenPrivate() {
|
||||
hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_DECLARED_METHODS);
|
||||
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetScaleMethod);
|
||||
}
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ public class BindingReflectionHintsRegistrar {
|
||||
* @param hints the hints instance to use
|
||||
* @param types the types to register
|
||||
*/
|
||||
public void registerReflectionHints(ReflectionHints hints, @Nullable Type... types) {
|
||||
public void registerReflectionHints(ReflectionHints hints, Type... types) {
|
||||
Set<Type> seen = new LinkedHashSet<>();
|
||||
for (Type type : types) {
|
||||
registerReflectionHints(hints, seen, type);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -39,8 +39,7 @@ import org.springframework.core.annotation.AliasFor;
|
||||
* @see ReflectiveRuntimeHintsRegistrar
|
||||
* @see RegisterReflectionForBinding @RegisterReflectionForBinding
|
||||
*/
|
||||
@Target({ ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.CONSTRUCTOR,
|
||||
ElementType.FIELD, ElementType.METHOD })
|
||||
@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface Reflective {
|
||||
|
||||
+2
-3
@@ -36,7 +36,7 @@ import org.springframework.core.annotation.AliasFor;
|
||||
*
|
||||
* <pre class="code">
|
||||
* @Configuration
|
||||
* @RegisterReflectionForBinding({ Foo.class, Bar.class })
|
||||
* @RegisterReflectionForBinding({Foo.class, Bar.class})
|
||||
* public class MyConfig {
|
||||
* // ...
|
||||
* }</pre>
|
||||
@@ -77,8 +77,7 @@ public @interface RegisterReflectionForBinding {
|
||||
|
||||
/**
|
||||
* Classes for which reflection hints should be registered.
|
||||
* <p>At least one class must be specified either via {@link #value} or
|
||||
* {@link #classes}.
|
||||
* <p>At least one class must be specified either via {@link #value} or {@code classes}.
|
||||
* @see #value()
|
||||
*/
|
||||
@AliasFor("value")
|
||||
|
||||
+5
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -48,8 +48,8 @@ class SpringFactoriesLoaderRuntimeHints implements RuntimeHintsRegistrar {
|
||||
|
||||
@Override
|
||||
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
|
||||
ClassLoader classLoaderToUse = (classLoader != null ? classLoader
|
||||
: SpringFactoriesLoaderRuntimeHints.class.getClassLoader());
|
||||
ClassLoader classLoaderToUse = (classLoader != null ? classLoader :
|
||||
SpringFactoriesLoaderRuntimeHints.class.getClassLoader());
|
||||
for (String resourceLocation : RESOURCE_LOCATIONS) {
|
||||
registerHints(hints, classLoaderToUse, resourceLocation);
|
||||
}
|
||||
@@ -65,6 +65,7 @@ class SpringFactoriesLoaderRuntimeHints implements RuntimeHintsRegistrar {
|
||||
|
||||
private void registerHints(RuntimeHints hints, ClassLoader classLoader,
|
||||
String factoryClassName, List<String> implementationClassNames) {
|
||||
|
||||
Class<?> factoryClass = resolveClassName(classLoader, factoryClassName);
|
||||
if (factoryClass == null) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
@@ -102,6 +103,7 @@ class SpringFactoriesLoaderRuntimeHints implements RuntimeHintsRegistrar {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class ExtendedSpringFactoriesLoader extends SpringFactoriesLoader {
|
||||
|
||||
ExtendedSpringFactoriesLoader(@Nullable ClassLoader classLoader, Map<String, List<String>> factories) {
|
||||
@@ -111,7 +113,6 @@ class SpringFactoriesLoaderRuntimeHints implements RuntimeHintsRegistrar {
|
||||
static Map<String, List<String>> accessLoadFactoriesResource(ClassLoader classLoader, String resourceLocation) {
|
||||
return SpringFactoriesLoader.loadFactoriesResource(classLoader, resourceLocation);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ public class ReflectUtils {
|
||||
Throwable throwable = null;
|
||||
try {
|
||||
classLoaderDefineClass = ClassLoader.class.getDeclaredMethod("defineClass",
|
||||
String.class, byte[].class, Integer.TYPE, Integer.TYPE, ProtectionDomain.class);
|
||||
String.class, byte[].class, Integer.TYPE, Integer.TYPE, ProtectionDomain.class);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
classLoaderDefineClass = null;
|
||||
@@ -544,7 +544,15 @@ public class ReflectUtils {
|
||||
|
||||
// No defineClass variant available at all?
|
||||
if (c == null) {
|
||||
throw new CodeGenerationException(t);
|
||||
throw new CodeGenerationException(t) {
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return "No compatible defineClass mechanism detected: " +
|
||||
"JVM should be started with --add-opens=java.base/java.lang=ALL-UNNAMED " +
|
||||
"for ClassLoader.defineClass to be accessible. On the module path, " +
|
||||
"you may not be able to define this CGLIB-generated class at all.";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Force static initializers to run.
|
||||
|
||||
@@ -57,7 +57,7 @@ public class MethodProxy {
|
||||
proxy.createInfo = new CreateInfo(c1, c2);
|
||||
|
||||
// SPRING PATCH BEGIN
|
||||
if (!c1.isInterface() && c1 != Object.class && !Factory.class.isAssignableFrom(c2)) {
|
||||
if (c1 != Object.class && c1.isAssignableFrom(c2.getSuperclass()) && !Factory.class.isAssignableFrom(c2)) {
|
||||
// Try early initialization for overridden methods on specifically purposed subclasses
|
||||
try {
|
||||
proxy.init();
|
||||
|
||||
+5
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -418,7 +418,10 @@ final class TypeMappedAnnotations implements MergedAnnotations {
|
||||
|
||||
Annotation[] repeatedAnnotations = repeatableContainers.findRepeatedAnnotations(annotation);
|
||||
if (repeatedAnnotations != null) {
|
||||
return doWithAnnotations(type, aggregateIndex, source, repeatedAnnotations);
|
||||
MergedAnnotation<A> result = doWithAnnotations(type, aggregateIndex, source, repeatedAnnotations);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(
|
||||
annotation.annotationType(), repeatableContainers, annotationFilter);
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -48,7 +48,7 @@ public class PropertiesPropertySource extends MapPropertySource {
|
||||
@Override
|
||||
public String[] getPropertyNames() {
|
||||
synchronized (this.source) {
|
||||
return super.getPropertyNames();
|
||||
return ((Map<?, ?>) this.source).keySet().stream().filter(k -> k instanceof String).toArray(String[]::new);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,16 +23,22 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link Resource} implementation for a given {@link InputStream}.
|
||||
* {@link Resource} implementation for a given {@link InputStream} or a given
|
||||
* {@link InputStreamSource} (which can be supplied as a lambda expression)
|
||||
* for a lazy {@link InputStream} on demand.
|
||||
*
|
||||
* <p>Should only be used if no other specific {@code Resource} implementation
|
||||
* is applicable. In particular, prefer {@link ByteArrayResource} or any of the
|
||||
* file-based {@code Resource} implementations where possible.
|
||||
* file-based {@code Resource} implementations if possible. If you need to obtain
|
||||
* a custom stream multiple times, use a custom {@link AbstractResource} subclass
|
||||
* with a corresponding {@code getInputStream()} implementation.
|
||||
*
|
||||
* <p>In contrast to other {@code Resource} implementations, this is a descriptor
|
||||
* for an <i>already opened</i> resource - therefore returning {@code true} from
|
||||
* {@link #isOpen()}. Do not use an {@code InputStreamResource} if you need to
|
||||
* keep the resource descriptor somewhere, or if you need to read from a stream
|
||||
* multiple times.
|
||||
* {@link #isOpen()}. Do not use an {@code InputStreamResource} if you need to keep
|
||||
* the resource descriptor somewhere, or if you need to read from a stream multiple
|
||||
* times. This also applies when constructed with an {@code InputStreamSource}
|
||||
* which lazily obtains the stream but only allows for single access as well.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
@@ -44,30 +50,62 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class InputStreamResource extends AbstractResource {
|
||||
|
||||
private final InputStream inputStream;
|
||||
private final InputStreamSource inputStreamSource;
|
||||
|
||||
private final String description;
|
||||
|
||||
private final Object equality;
|
||||
|
||||
private boolean read = false;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new InputStreamResource.
|
||||
* Create a new {@code InputStreamResource} with a lazy {@code InputStream}
|
||||
* for single use.
|
||||
* @param inputStreamSource an on-demand source for a single-use InputStream
|
||||
* @since 6.1.7
|
||||
*/
|
||||
public InputStreamResource(InputStreamSource inputStreamSource) {
|
||||
this(inputStreamSource, "resource loaded from InputStreamSource");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@code InputStreamResource} with a lazy {@code InputStream}
|
||||
* for single use.
|
||||
* @param inputStreamSource an on-demand source for a single-use InputStream
|
||||
* @param description where the InputStream comes from
|
||||
* @since 6.1.7
|
||||
*/
|
||||
public InputStreamResource(InputStreamSource inputStreamSource, @Nullable String description) {
|
||||
Assert.notNull(inputStreamSource, "InputStreamSource must not be null");
|
||||
this.inputStreamSource = inputStreamSource;
|
||||
this.description = (description != null ? description : "");
|
||||
this.equality = inputStreamSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@code InputStreamResource} for an existing {@code InputStream}.
|
||||
* <p>Consider retrieving the InputStream on demand if possible, reducing its
|
||||
* lifetime and reliably opening it and closing it through regular
|
||||
* {@link InputStreamSource#getInputStream()} usage.
|
||||
* @param inputStream the InputStream to use
|
||||
* @see #InputStreamResource(InputStreamSource)
|
||||
*/
|
||||
public InputStreamResource(InputStream inputStream) {
|
||||
this(inputStream, "resource loaded through InputStream");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new InputStreamResource.
|
||||
* Create a new {@code InputStreamResource} for an existing {@code InputStream}.
|
||||
* @param inputStream the InputStream to use
|
||||
* @param description where the InputStream comes from
|
||||
* @see #InputStreamResource(InputStreamSource, String)
|
||||
*/
|
||||
public InputStreamResource(InputStream inputStream, @Nullable String description) {
|
||||
Assert.notNull(inputStream, "InputStream must not be null");
|
||||
this.inputStream = inputStream;
|
||||
this.inputStreamSource = () -> inputStream;
|
||||
this.description = (description != null ? description : "");
|
||||
this.equality = inputStream;
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +136,7 @@ public class InputStreamResource extends AbstractResource {
|
||||
"do not use InputStreamResource if a stream needs to be read multiple times");
|
||||
}
|
||||
this.read = true;
|
||||
return this.inputStream;
|
||||
return this.inputStreamSource.getInputStream();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,7 +155,7 @@ public class InputStreamResource extends AbstractResource {
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
return (this == other || (other instanceof InputStreamResource that &&
|
||||
this.inputStream.equals(that.inputStream)));
|
||||
this.equality.equals(that.equality)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,7 +163,7 @@ public class InputStreamResource extends AbstractResource {
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.inputStream.hashCode();
|
||||
return this.equality.hashCode();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user