Compare commits

..

1 Commits

Author SHA1 Message Date
Spring Builds b932df6ad2 Release v6.1.6 2024-04-11 08:14:00 +00:00
192 changed files with 1257 additions and 2628 deletions
@@ -35,7 +35,9 @@ jobs:
env:
CI: 'true'
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
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 }}
run: ./gradlew -PdeploymentRepository=$(pwd)/deployment-repository build publishAllPublicationsToDeploymentRepository
- name: Deploy
uses: spring-io/artifactory-deploy-action@v0.0.1
+3 -1
View File
@@ -66,7 +66,9 @@ jobs:
env:
CI: 'true'
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
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 }}
run: ./gradlew check antora
- name: Send notification
uses: ./.github/actions/send-notification
-2
View File
@@ -51,5 +51,3 @@ atlassian-ide-plugin.xml
.vscode/
cached-antora-playbook.yml
node_modules
+1 -1
View File
@@ -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.11-librca
java=17.0.10-librca
+2
View File
@@ -89,6 +89,8 @@ 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.16.0");
checkstyle.setToolVersion("10.15.0");
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
+1 -1
View File
@@ -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/17/GPL/openjdk-23-ea+17_linux-x64_bin.tar.gz"
echo "https://download.java.net/java/early_access/jdk23/10/GPL/openjdk-23-ea+10_linux-x64_bin.tar.gz"
;;
*)
echo $"Unknown java version"
+1 -1
View File
@@ -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 java23
for jdk in java17 java21 java22
do
JDK_URL=$( /get-jdk-url.sh $jdk )
mkdir $jdk
+3 -1
View File
@@ -5,7 +5,9 @@ anchors:
password: ((github-ci-release-token))
branch: ((branch))
gradle-enterprise-task-params: &gradle-enterprise-task-params
DEVELOCITY_ACCESS_KEY: ((gradle_enterprise_secret_access_key))
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))
sonatype-task-params: &sonatype-task-params
SONATYPE_USERNAME: ((sonatype-username))
SONATYPE_PASSWORD: ((sonatype-password))
-39
View File
@@ -1,39 +0,0 @@
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
+20 -3
View File
@@ -10,10 +10,27 @@ apply from: "${rootDir}/gradle/ide.gradle"
apply from: "${rootDir}/gradle/publications.gradle"
antora {
options = [clean: true, fetch: !project.gradle.startParameter.offline, stacktrace: true]
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']
environment = [
'BUILD_REFNAME': 'HEAD',
'BUILD_VERSION': project.version,
'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'
]
}
@@ -19,10 +19,6 @@ 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,19 +326,6 @@ 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,16 +1,33 @@
[[beans-annotation-config]]
= Annotation-based Container Configuration
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.
.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.
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
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
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
@@ -19,16 +36,13 @@ can be found in the xref:core/beans/standard-annotations.adoc[relevant section].
[NOTE]
====
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.
Annotation injection is performed before XML injection. Thus, the XML configuration
overrides the annotations for properties wired through both approaches.
====
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:
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):
[source,xml,indent=0,subs="verbatim,quotes"]
----
@@ -186,15 +186,6 @@ 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:
@@ -277,12 +268,6 @@ 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,28 +2,36 @@
= 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 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.
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.
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`].
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.
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 plain web application scenario,
a simple boilerplate web descriptor XML in the `web.xml` file of the application suffices (see
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
xref:core/beans/context-introduction.adoc#context-create[Convenient ApplicationContext Instantiation for Web Applications]).
In a Spring Boot scenario, the application context is implicitly bootstrapped for you
based on common setup conventions.
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.
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[]
@@ -35,25 +43,33 @@ 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 components in your application.
application developer, tell the Spring container to instantiate, configure, and assemble
the objects 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:
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/annotation-config.adoc[Annotation-based configuration]: define beans using
annotation-based configuration metadata on your application's component classes.
annotation-based configuration metadata.
* xref:core/beans/java.adoc[Java-based configuration]: define beans external to your application
classes by using Java-based configuration classes. To use these features, see the
classes by using Java rather than XML files. 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. Java configuration typically uses `@Bean`-annotated
methods within a `@Configuration` class, each corresponding to one bean definition.
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.
These bean definitions correspond to the actual objects that make up your application.
Typically, you define service layer objects, persistence layer objects such as
@@ -63,14 +79,7 @@ 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.
[[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:
The following example shows the basic structure of XML-based configuration metadata:
[source,xml,indent=0,subs="verbatim,quotes"]
----
@@ -101,9 +110,14 @@ 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.
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
[[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
as the local file system, the Java `CLASSPATH`, and so on.
[tabs]
@@ -195,9 +209,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 `ClassPathXmlApplicationContext` constructor to load bean definitions from
You can use the application context constructor to load bean definitions from all these
XML fragments. This constructor takes multiple `Resource` locations, as was shown in the
xref:core/beans/basics.adoc#beans-factory-xml[previous section]. Alternatively,
xref:core/beans/basics.adoc#beans-factory-instantiation[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:
@@ -245,7 +259,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.
[[beans-factory-groovy]]
[[groovy-bean-definition-dsl]]
=== The Groovy Bean Definition DSL
As a further example for externalized configuration metadata, bean definitions can also
@@ -406,3 +420,4 @@ a dependency on a specific bean through metadata (such as an autowiring annotati
@@ -234,10 +234,6 @@ 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
@@ -298,24 +294,6 @@ 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
@@ -438,8 +416,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
@@ -466,3 +444,5 @@ cases into account and returns the type of object that a `BeanFactory.getBean` c
going to return for the same bean name.
@@ -55,34 +55,33 @@ The preceding `AppConfig` class is equivalent to the following Spring `<beans/>`
</beans>
----
.@Configuration classes with or without local calls between @Bean methods?
.Full @Configuration vs "`lite`" @Bean mode?
****
In common scenarios, `@Bean` methods are to be declared within `@Configuration` classes,
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.
`@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.
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.
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.
****
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.
@@ -74,11 +74,10 @@ 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.
@@ -437,10 +436,9 @@ 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.
@@ -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:
Here's an example of how to use Vavr's Try with a transactional method:
[tabs]
======
Java::
@@ -42,32 +42,6 @@ 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/JRE must have a base image. If you add the flags above to
To create a CDS archive, your JDK 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,8 +32,7 @@ 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 is usually provided out-of-the-box, but can also be created if needed by issuing the following
command:
The base CDS archive can be created by issuing the following command:
[source,shell,indent=0,subs="verbatim"]
----
@@ -45,9 +44,6 @@ 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
@@ -62,11 +58,8 @@ 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)
----
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).
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.
@@ -71,34 +71,8 @@ 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.
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].
@@ -494,9 +468,6 @@ 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#beans-factory-groovy[The Groovy Bean Definition DSL].
xref:core/beans/basics.adoc#groovy-bean-definition-dsl[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();
}
}
----
@@ -111,8 +111,7 @@ a mock service with Mockito:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<bean id="accountService" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg type="java.lang.Class" value="org.example.AccountService"/>
<constructor-arg type="java.lang.String" value="accountService"/>
<constructor-arg value="org.example.AccountService"/>
</bean>
----
@@ -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.
@@ -1,16 +1,38 @@
[[spring-mvc-test-vs-streaming-response]]
= Streaming Responses
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].
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:
`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.
[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].
@@ -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#beans-factory-groovy[Groovy Bean Definition DSL], you can annotate
xref:core/beans/basics.adoc#groovy-bean-definition-dsl[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>()
.returnResult()
.expectBody(Person.class)
.returnResult();
// For a response without a body
val result = client.get().uri("/path")
.exchange()
.expectBody().isEmpty()
.expectBody().isEmpty();
----
======
@@ -4,13 +4,10 @@
`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.
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).
`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).
For annotated controllers, WebFlux transparently adapts to the reactive library chosen by
the application. This is done with the help of the
@@ -18,3 +15,15 @@ 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}/rfc9457.html[RFC 9457].
specification, {rfc-site}/rfc7807.html[RFC 7807].
The following are the main abstractions for this support:
- `ProblemDetail` -- representation for an RFC 9457 problem detail; a simple container
- `ProblemDetail` -- representation for an RFC 7807 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 9457; this allows exceptions to
status, response headers, and a body in the format of RFC 7807; 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 9457 response. This is processed as follows:
any `@RequestMapping` method to render an RFC 7807 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 9457 responses for Spring WebFlux exceptions and for any
To enable RFC 7807 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 9457 response with non-standard fields in one of two ways.
You can extend an RFC 7807 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
@@ -4,20 +4,9 @@
[.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/webflux-reactive-libraries.adoc[or other] are
types from libraries such as Reactor, RxJava, xref:web-reactive.adoc#webflux-reactive-libraries[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
@@ -35,11 +24,11 @@ Controllers can then return a `Flux<List<B>>`; Reactor provides a dedicated oper
| For returning a response with headers and no body.
| `ErrorResponse`
| To render an RFC 9457 error response with details in the body,
| To render an RFC 7807 error response with details in the body,
see xref:web/webflux/ann-rest-exceptions.adoc[Error Responses]
| `ProblemDetail`
| To render an RFC 9457 error response with details in the body,
| To render an RFC 7807 error response with details in the body,
see xref:web/webflux/ann-rest-exceptions.adoc[Error Responses]
| `String`
@@ -3,40 +3,28 @@
[.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] for
`@RequestMapping` methods, including xref:core/validation/beanvalidation.adoc[Java Bean Validation].
Validation may be applied at one of two levels:
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.
1. xref:web/webflux/controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
First, resolvers for
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] 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`.
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.
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.
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.
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}/rfc9457[RFC 9457].
specification, {rfc-site}/rfc7807[RFC 7807].
The following are the main abstractions for this support:
- `ProblemDetail` -- representation for an RFC 9457 problem detail; a simple container
- `ProblemDetail` -- representation for an RFC 7807 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 9457; this allows exceptions to
status, response headers, and a body in the format of RFC 7807; 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 9457 response. This is processed as follows:
any `@RequestMapping` method to render an RFC 7807 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 9457 responses for Spring WebFlux exceptions and for any
To enable RFC 7807 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 9457 response with non-standard fields in one of two ways.
You can extend an RFC 7807 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,10 +52,14 @@ The following example shows how to achieve the same configuration in XML:
</mvc:interceptors>
----
WARNING: Interceptors are not ideally suited as a security layer due to the potential for
a mismatch with annotated controller path matching. Generally, we recommend using Spring
Security, or alternatively a similar approach integrated with the Servlet filter chain,
and applied as early as possible.
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.
NOTE: The XML config declares interceptors as `MappedInterceptor` beans, and those are in
turn detected by any `HandlerMapping` bean, including those from other frameworks.
@@ -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 9457 error response with details in the body,
| To render an RFC 7807 error response with details in the body,
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
| `ProblemDetail`
| To render an RFC 9457 error response with details in the body,
| To render an RFC 7807 error response with details in the body,
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
| `String`
@@ -37,13 +37,6 @@ 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.
@@ -32,16 +32,6 @@ 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:
@@ -23,11 +23,11 @@ supported for all return values.
| For returning a response with headers and no body.
| `ErrorResponse`
| To render an RFC 9457 error response with details in the body,
| To render an RFC 7807 error response with details in the body,
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
| `ProblemDetail`
| To render an RFC 9457 error response with details in the body,
| To render an RFC 7807 error response with details in the body,
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
| `String`
@@ -3,40 +3,28 @@
[.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] for
`@RequestMapping` methods, including xref:core/validation/beanvalidation.adoc[Java Bean Validation].
Validation may be applied at one of two levels:
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.
1. xref:web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute],
First, resolvers for
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] 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`.
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.
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.
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.
You can configure a `Validator` globally through the
xref:web/webmvc/mvc-config/validation.adoc[WebMvc config], or locally through an
@@ -1,29 +1,34 @@
[[mvc-handlermapping-interceptor]]
= Interception
All `HandlerMapping` implementations support handler interception which is useful when
you want to apply functionality across requests. A `HandlerInterceptor` can implement the
following:
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:
* `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.
* `preHandle(..)`: Before the actual handler is run
* `postHandle(..)`: After the handler is run
* `afterCompletion(..)`: After the complete request has finished
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`.
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.
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.
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.
`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`.
-10
View File
@@ -1,10 +0,0 @@
{
"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"
}
}
+18 -18
View File
@@ -8,20 +8,20 @@ javaPlatform {
dependencies {
api(platform("com.fasterxml.jackson:jackson-bom:2.15.4"))
api(platform("io.micrometer:micrometer-bom:1.12.6"))
api(platform("io.netty:netty-bom:4.1.109.Final"))
api(platform("io.micrometer:micrometer-bom:1.12.5"))
api(platform("io.netty:netty-bom:4.1.108.Final"))
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
api(platform("io.projectreactor:reactor-bom:2023.0.6"))
api(platform("io.projectreactor:reactor-bom:2023.0.5"))
api(platform("io.rsocket:rsocket-bom:1.1.3"))
api(platform("org.apache.groovy:groovy-bom:4.0.21"))
api(platform("org.apache.groovy:groovy-bom:4.0.20"))
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.9"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.9"))
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.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.12.0"))
api(platform("org.mockito:mockito-bom:5.11.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.13.Final")
api("io.undertow:undertow-servlet:2.3.13.Final")
api("io.undertow:undertow-websockets-jsr:2.3.13.Final")
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.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.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.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.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.4")
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.3")
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
View File
@@ -1,4 +1,4 @@
version=6.1.8
version=6.1.6
org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m
-2
View File
@@ -6,7 +6,6 @@ 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/"))
@@ -22,7 +21,6 @@ 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
View File
@@ -1,6 +1,6 @@
plugins {
id "com.gradle.develocity" version "3.17.2"
id "io.spring.ge.conventions" version "0.0.17"
id "com.gradle.enterprise" version "3.16.2"
id "io.spring.ge.conventions" version "0.0.15"
id "org.gradle.toolchains.foojay-resolver-convention" version "0.7.0"
}
@@ -39,7 +39,7 @@ rootProject.children.each {project ->
}
settings.gradle.projectsLoaded {
develocity {
gradleEnterprise {
buildScan {
File buildDir = settings.gradle.rootProject
.getLayout().getBuildDirectory().getAsFile().get()
@@ -41,7 +41,6 @@ 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;
@@ -116,8 +115,6 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
@Nullable
private transient PointcutExpression pointcutExpression;
private transient boolean pointcutParsingFailed = false;
private transient Map<Method, ShadowMatch> shadowMatchCache = new ConcurrentHashMap<>(32);
@@ -173,30 +170,25 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
@Override
public ClassFilter getClassFilter() {
checkExpression();
obtainPointcutExpression();
return this;
}
@Override
public MethodMatcher getMethodMatcher() {
checkExpression();
obtainPointcutExpression();
return this;
}
/**
* Check whether this pointcut is ready to match.
* Check whether this pointcut is ready to match,
* lazily building the underlying AspectJ pointcut expression.
*/
private void checkExpression() {
private PointcutExpression obtainPointcutExpression() {
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);
@@ -273,13 +265,10 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
@Override
public boolean matches(Class<?> targetClass) {
if (this.pointcutParsingFailed) {
return false;
}
PointcutExpression pointcutExpression = obtainPointcutExpression();
try {
try {
return obtainPointcutExpression().couldMatchJoinPointsInType(targetClass);
return pointcutExpression.couldMatchJoinPointsInType(targetClass);
}
catch (ReflectionWorldException ex) {
logger.debug("PointcutExpression matching rejected target class - trying fallback expression", ex);
@@ -290,12 +279,6 @@ 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);
}
@@ -304,6 +287,7 @@ 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
@@ -341,6 +325,7 @@ 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,
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ 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;
@@ -55,6 +56,8 @@ 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};
@@ -65,11 +68,37 @@ 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);
@@ -86,7 +115,6 @@ 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...).
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,8 +16,6 @@
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;
@@ -36,8 +34,6 @@ 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());
@@ -47,22 +43,13 @@ class AspectJAdvisorBeanRegistrationAotProcessor implements BeanRegistrationAotP
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
if (aspectjPresent) {
Class<?> beanClass = registeredBean.getBeanClass();
if (compiledByAjc(beanClass)) {
if (AbstractAspectJAdvisorFactory.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 {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -124,16 +124,10 @@ public class AspectMetadata implements Serializable {
* Extract contents from String of form {@code pertarget(contents)}.
*/
private String findPerClause(Class<?> aspectClass) {
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);
String str = aspectClass.getAnnotation(Aspect.class).value();
int beginIndex = str.indexOf('(') + 1;
int endIndex = str.length() - 1;
return str.substring(beginIndex, endIndex);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -50,7 +50,6 @@ 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;
@@ -134,19 +133,17 @@ public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFacto
List<Advisor> advisors = new ArrayList<>();
for (Method method : getAdvisorMethods(aspectClass)) {
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);
}
// 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,7 +369,8 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
private void validateIntroductionAdvisor(IntroductionAdvisor advisor) {
advisor.validateInterfaces();
// If the advisor passed validation, we can make the change.
for (Class<?> ifc : advisor.getInterfaces()) {
Class<?>[] ifcs = advisor.getInterfaces();
for (Class<?> ifc : ifcs) {
addInterface(ifc);
}
}
@@ -23,6 +23,8 @@ 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;
@@ -39,13 +41,14 @@ 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 {
@@ -60,7 +63,7 @@ class AspectJExpressionPointcutTests {
@BeforeEach
void setup() throws NoSuchMethodException {
public void setup() throws NoSuchMethodException {
getAge = TestBean.class.getMethod("getAge");
setAge = TestBean.class.getMethod("setAge", int.class);
setSomeNumber = TestBean.class.getMethod("setSomeNumber", Number.class);
@@ -171,7 +174,7 @@ class AspectJExpressionPointcutTests {
void testFriendlyErrorOnNoLocationClassMatching() {
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
assertThatIllegalStateException()
.isThrownBy(() -> pc.getClassFilter().matches(ITestBean.class))
.isThrownBy(() -> pc.matches(ITestBean.class))
.withMessageContaining("expression");
}
@@ -179,7 +182,7 @@ class AspectJExpressionPointcutTests {
void testFriendlyErrorOnNoLocation2ArgMatching() {
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
assertThatIllegalStateException()
.isThrownBy(() -> pc.getMethodMatcher().matches(getAge, ITestBean.class))
.isThrownBy(() -> pc.matches(getAge, ITestBean.class))
.withMessageContaining("expression");
}
@@ -187,13 +190,13 @@ class AspectJExpressionPointcutTests {
void testFriendlyErrorOnNoLocation3ArgMatching() {
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
assertThatIllegalStateException()
.isThrownBy(() -> pc.getMethodMatcher().matches(getAge, ITestBean.class, (Object[]) null))
.isThrownBy(() -> pc.matches(getAge, ITestBean.class, (Object[]) null))
.withMessageContaining("expression");
}
@Test
void testMatchWithArgs() {
void testMatchWithArgs() throws Exception {
String expression = "execution(void org.springframework.beans.testfixture.beans.TestBean.setSomeNumber(Number)) && args(Double)";
Pointcut pointcut = getPointcut(expression);
@@ -243,7 +246,7 @@ class AspectJExpressionPointcutTests {
@Test
void testInvalidExpression() {
String expression = "execution(void org.springframework.beans.testfixture.beans.TestBean.setSomeNumber(Number) && args(Double)";
assertThat(getPointcut(expression).getClassFilter().matches(Object.class)).isFalse();
assertThatIllegalArgumentException().isThrownBy(getPointcut(expression)::getClassFilter); // call to getClassFilter forces resolution
}
private TestBean getAdvisedProxy(String pointcutExpression, CallCountingInterceptor interceptor) {
@@ -273,7 +276,9 @@ class AspectJExpressionPointcutTests {
@Test
void testWithUnsupportedPointcutPrimitive() {
String expression = "call(int org.springframework.beans.testfixture.beans.TestBean.getAge())";
assertThat(getPointcut(expression).getClassFilter().matches(Object.class)).isFalse();
assertThatExceptionOfType(UnsupportedPointcutPrimitiveException.class)
.isThrownBy(() -> getPointcut(expression).getClassFilter()) // call to getClassFilter forces resolution...
.satisfies(ex -> assertThat(ex.getUnsupportedPrimitive()).isEqualTo(PointcutPrimitive.CALL));
}
@Test
@@ -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,15 +770,9 @@ 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;
@@ -786,6 +780,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
}
@Aspect
private static class InvocationTrackingAspect {
@@ -22,12 +22,11 @@ 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");
}
@@ -20,14 +20,11 @@ import org.junit.jupiter.api.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author Ramnivas Laddad
* @author Juergen Hoeller
*/
class SpringConfiguredWithAutoProxyingTests {
public 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");
}
@@ -2,21 +2,16 @@
<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/context https://www.springframework.org/schema/context/spring-context-2.5.xsd">
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
<aop:aspectj-autoproxy/>
<context:spring-configured/>
<bean id="myAspect" class="org.springframework.aop.aspectj.autoproxy.CodeStyleAspect" factory-method="aspectOf">
<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>
@@ -165,7 +165,7 @@ class BeanDefinitionMethodGenerator {
this.aotContributions.forEach(aotContribution -> aotContribution.applyTo(generationContext, codeGenerator));
CodeWarnings codeWarnings = new CodeWarnings();
codeWarnings.detectDeprecation(this.registeredBean.getBeanType());
codeWarnings.detectDeprecation(this.registeredBean.getBeanClass());
return generatedMethods.add("getBeanDefinition", method -> {
method.addJavadoc("Get the $L definition for '$L'.",
(this.registeredBean.isInnerBean() ? "inner-bean" : "bean"),
@@ -23,12 +23,10 @@ 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
@@ -74,26 +72,6 @@ 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
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
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;
@@ -34,7 +35,6 @@ 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<InstantiationDescriptor> instantiationDescriptor;
private final Supplier<Executable> constructorOrFactoryMethod;
DefaultBeanRegistrationCodeFragments(BeanRegistrationsCode beanRegistrationsCode,
@@ -72,18 +72,17 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
this.beanRegistrationsCode = beanRegistrationsCode;
this.registeredBean = registeredBean;
this.beanDefinitionMethodGeneratorFactory = beanDefinitionMethodGeneratorFactory;
this.instantiationDescriptor = SingletonSupplier.of(registeredBean::resolveInstantiationDescriptor);
this.constructorOrFactoryMethod = SingletonSupplier.of(registeredBean::resolveConstructorOrFactoryMethod);
}
@Override
public ClassName getTarget(RegisteredBean registeredBean) {
if (hasInstanceSupplier()) {
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");
throw new IllegalStateException("Default code generation is not supported for bean definitions "
+ "declaring an instance supplier callback: " + registeredBean.getMergedBeanDefinition());
}
Class<?> target = extractDeclaringClass(registeredBean, this.instantiationDescriptor.get());
Class<?> target = extractDeclaringClass(registeredBean.getBeanType(), this.constructorOrFactoryMethod.get());
while (target.getName().startsWith("java.") && registeredBean.isInnerBean()) {
RegisteredBean parent = registeredBean.getParent();
Assert.state(parent != null, "No parent available for inner bean");
@@ -92,14 +91,14 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
return (target.isArray() ? ClassName.get(target.getComponentType()) : ClassName.get(target));
}
private Class<?> extractDeclaringClass(RegisteredBean registeredBean, InstantiationDescriptor instantiationDescriptor) {
Class<?> declaringClass = ClassUtils.getUserClass(instantiationDescriptor.targetClass());
if (instantiationDescriptor.executable() instanceof Constructor<?>
&& AccessControl.forMember(instantiationDescriptor.executable()).isPublic()
private Class<?> extractDeclaringClass(ResolvableType beanType, Executable executable) {
Class<?> declaringClass = ClassUtils.getUserClass(executable.getDeclaringClass());
if (executable instanceof Constructor<?>
&& AccessControl.forMember(executable).isPublic()
&& FactoryBean.class.isAssignableFrom(declaringClass)) {
return extractTargetClassFromFactoryBean(declaringClass, registeredBean.getBeanType());
return extractTargetClassFromFactoryBean(declaringClass, beanType);
}
return declaringClass;
return executable.getDeclaringClass();
}
/**
@@ -239,9 +238,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.instantiationDescriptor.get());
return new InstanceSupplierCodeGenerator(generationContext,
beanRegistrationCode.getClassName(), beanRegistrationCode.getMethods(), allowDirectSupplierShortcut)
.generateCode(this.registeredBean, this.constructorOrFactoryMethod.get());
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -46,7 +46,6 @@ 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;
@@ -121,29 +120,14 @@ 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, instantiationDescriptor.targetClass());
return generateCodeForFactoryMethod(registeredBean, method);
}
throw new IllegalStateException(
"No suitable executor found for " + registeredBean.getBeanName());
@@ -269,21 +253,21 @@ public class InstanceSupplierCodeGenerator {
declaringClass.getSimpleName(), args);
}
private CodeBlock generateCodeForFactoryMethod(RegisteredBean registeredBean, Method factoryMethod, Class<?> targetClass) {
private CodeBlock generateCodeForFactoryMethod(RegisteredBean registeredBean, Method factoryMethod) {
String beanName = registeredBean.getBeanName();
Class<?> targetClassToUse = ClassUtils.getUserClass(targetClass);
Class<?> declaringClass = ClassUtils.getUserClass(factoryMethod.getDeclaringClass());
boolean dependsOnBean = !Modifier.isStatic(factoryMethod.getModifiers());
Visibility accessVisibility = getAccessVisibility(registeredBean, factoryMethod);
if (accessVisibility != Visibility.PRIVATE) {
return generateCodeForAccessibleFactoryMethod(
beanName, factoryMethod, targetClassToUse, dependsOnBean);
beanName, factoryMethod, declaringClass, dependsOnBean);
}
return generateCodeForInaccessibleFactoryMethod(beanName, factoryMethod, targetClassToUse);
return generateCodeForInaccessibleFactoryMethod(beanName, factoryMethod, declaringClass);
}
private CodeBlock generateCodeForAccessibleFactoryMethod(String beanName,
Method factoryMethod, Class<?> targetClass, boolean dependsOnBean) {
Method factoryMethod, Class<?> declaringClass, boolean dependsOnBean) {
this.generationContext.getRuntimeHints().reflection().registerMethod(
factoryMethod, ExecutableMode.INTROSPECT);
@@ -292,20 +276,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, targetClass, factoryMethod.getName());
suppliedType, declaringClass, factoryMethod.getName());
code.add(".withGenerator(($L) -> $T.$L())", REGISTERED_BEAN_PARAMETER_NAME,
targetClass, factoryMethod.getName());
declaringClass, factoryMethod.getName());
return code.build();
}
GeneratedMethod getInstanceMethod = generateGetInstanceSupplierMethod(method ->
buildGetInstanceMethodForFactoryMethod(method, beanName, factoryMethod,
targetClass, dependsOnBean, PRIVATE_STATIC));
declaringClass, dependsOnBean, PRIVATE_STATIC));
return generateReturnStatement(getInstanceMethod);
}
private CodeBlock generateCodeForInaccessibleFactoryMethod(
String beanName, Method factoryMethod, Class<?> targetClass) {
String beanName, Method factoryMethod, Class<?> declaringClass) {
this.generationContext.getRuntimeHints().reflection().registerMethod(factoryMethod, ExecutableMode.INVOKE);
GeneratedMethod getInstanceMethod = generateGetInstanceSupplierMethod(method -> {
@@ -314,19 +298,19 @@ public class InstanceSupplierCodeGenerator {
method.addModifiers(PRIVATE_STATIC);
method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, suppliedType));
method.addStatement(generateInstanceSupplierForFactoryMethod(
factoryMethod, suppliedType, targetClass, factoryMethod.getName()));
factoryMethod, suppliedType, declaringClass, factoryMethod.getName()));
});
return generateReturnStatement(getInstanceMethod);
}
private void buildGetInstanceMethodForFactoryMethod(MethodSpec.Builder method,
String beanName, Method factoryMethod, Class<?> targetClass,
String beanName, Method factoryMethod, Class<?> declaringClass,
boolean dependsOnBean, javax.lang.model.element.Modifier... modifiers) {
String factoryMethodName = factoryMethod.getName();
Class<?> suppliedType = ClassUtils.resolvePrimitiveIfNecessary(factoryMethod.getReturnType());
CodeWarnings codeWarnings = new CodeWarnings();
codeWarnings.detectDeprecation(targetClass, factoryMethod, suppliedType)
codeWarnings.detectDeprecation(declaringClass, factoryMethod, suppliedType)
.detectDeprecation(Arrays.stream(factoryMethod.getParameters()).map(Parameter::getType));
method.addJavadoc("Get the bean instance supplier for '$L'.", beanName);
@@ -336,41 +320,41 @@ public class InstanceSupplierCodeGenerator {
CodeBlock.Builder code = CodeBlock.builder();
code.add(generateInstanceSupplierForFactoryMethod(
factoryMethod, suppliedType, targetClass, factoryMethodName));
factoryMethod, suppliedType, declaringClass, factoryMethodName));
boolean hasArguments = factoryMethod.getParameterCount() > 0;
CodeBlock arguments = hasArguments ?
new AutowiredArgumentsCodeGenerator(targetClass, factoryMethod)
new AutowiredArgumentsCodeGenerator(declaringClass, factoryMethod)
.generateCode(factoryMethod.getParameterTypes())
: NO_ARGS;
CodeBlock newInstance = generateNewInstanceCodeForMethod(
dependsOnBean, targetClass, factoryMethodName, arguments);
dependsOnBean, declaringClass, factoryMethodName, arguments);
code.add(generateWithGeneratorCode(hasArguments, newInstance));
method.addStatement(code.build());
}
private CodeBlock generateInstanceSupplierForFactoryMethod(Method factoryMethod,
Class<?> suppliedType, Class<?> targetClass, String factoryMethodName) {
Class<?> suppliedType, Class<?> declaringClass, String factoryMethodName) {
if (factoryMethod.getParameterCount() == 0) {
return CodeBlock.of("return $T.<$T>forFactoryMethod($T.class, $S)",
BeanInstanceSupplier.class, suppliedType, targetClass, factoryMethodName);
BeanInstanceSupplier.class, suppliedType, declaringClass, factoryMethodName);
}
CodeBlock parameterTypes = generateParameterTypesCode(factoryMethod.getParameterTypes(), 0);
return CodeBlock.of("return $T.<$T>forFactoryMethod($T.class, $S, $L)",
BeanInstanceSupplier.class, suppliedType, targetClass, factoryMethodName, parameterTypes);
BeanInstanceSupplier.class, suppliedType, declaringClass, factoryMethodName, parameterTypes);
}
private CodeBlock generateNewInstanceCodeForMethod(boolean dependsOnBean,
Class<?> targetClass, String factoryMethodName, CodeBlock args) {
Class<?> declaringClass, String factoryMethodName, CodeBlock args) {
if (!dependsOnBean) {
return CodeBlock.of("$T.$L($L)", targetClass, factoryMethodName, args);
return CodeBlock.of("$T.$L($L)", declaringClass, factoryMethodName, args);
}
return CodeBlock.of("$L.getBeanFactory().getBean($T.class).$L($L)",
REGISTERED_BEAN_PARAMETER_NAME, targetClass, factoryMethodName, args);
REGISTERED_BEAN_PARAMETER_NAME, declaringClass, factoryMethodName, args);
}
private CodeBlock generateReturnStatement(GeneratedMethod generatedMethod) {
@@ -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, it checks the FactoryBean's {@code getObjectType} method
* declared as a raw type, 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}, full
* creation of the FactoryBean is attempted as fallback (through delegation to the
* superclass implementation).
* 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).
* <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,11 +1156,9 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
"Bean class isn't public, and non-public access not allowed: " + beanClass.getName());
}
if (args == null) {
Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
if (instanceSupplier != null) {
return obtainFromSupplier(instanceSupplier, beanName, mbd);
}
Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
if (instanceSupplier != null) {
return obtainFromSupplier(instanceSupplier, beanName, mbd);
}
if (mbd.getFactoryMethodName() != null) {
@@ -316,17 +316,6 @@ 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;
}
}
}
@@ -1098,7 +1087,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;
}
@@ -1697,7 +1686,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} is set on the bean definition
* <p>If no {@link FactoryBean#OBJECT_TYPE_ATTRIBUTE} if 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
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,8 +17,6 @@
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;
@@ -43,8 +41,6 @@ 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 {
@@ -210,32 +206,12 @@ 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)
@@ -262,24 +238,6 @@ 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.
*/
@@ -1967,42 +1967,6 @@ 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 {
@@ -3209,10 +3173,6 @@ 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);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -161,8 +161,7 @@ class BeanDefinitionMethodGeneratorTests {
@Test
void generateWithBeanClassAndFactoryMethodNameSetsTargetTypeAndBeanClass() {
this.beanFactory.registerBeanDefinition("factory",
new RootBeanDefinition(SimpleBeanConfiguration.class));
this.beanFactory.registerSingleton("factory", new SimpleBeanConfiguration());
RootBeanDefinition beanDefinition = new RootBeanDefinition(SimpleBean.class);
beanDefinition.setFactoryBeanName("factory");
beanDefinition.setFactoryMethodName("simpleBean");
@@ -183,8 +182,7 @@ class BeanDefinitionMethodGeneratorTests {
@Test
void generateWithTargetTypeAndFactoryMethodNameSetsOnlyBeanClass() {
this.beanFactory.registerBeanDefinition("factory",
new RootBeanDefinition(SimpleBeanConfiguration.class));
this.beanFactory.registerSingleton("factory", new SimpleBeanConfiguration());
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setTargetType(SimpleBean.class);
beanDefinition.setFactoryBeanName("factory");
@@ -692,8 +690,8 @@ class BeanDefinitionMethodGeneratorTests {
this.methodGeneratorFactory, registeredBean, null,
List.of());
assertThatIllegalStateException().isThrownBy(() -> generator.generateBeanDefinitionMethod(
this.generationContext, this.beanRegistrationsCode)).withMessage(
"Error processing bean with name 'testBean': instance supplier is not supported");
this.generationContext, this.beanRegistrationsCode)).withMessageStartingWith(
"Default code generation is not supported for bean definitions declaring an instance supplier callback");
}
@Test
@@ -729,8 +727,8 @@ class BeanDefinitionMethodGeneratorTests {
this.methodGeneratorFactory, registeredBean, null,
List.of(aotContribution));
assertThatIllegalStateException().isThrownBy(() -> generator.generateBeanDefinitionMethod(
this.generationContext, this.beanRegistrationsCode)).withMessage(
"Error processing bean with name 'testBean': instance supplier is not supported");
this.generationContext, this.beanRegistrationsCode)).withMessageStartingWith(
"Default code generation is not supported for bean definitions declaring an instance supplier callback");
}
@Test
@@ -782,19 +780,6 @@ 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) -> {})));
@@ -17,21 +17,15 @@
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;
@@ -104,40 +98,6 @@ 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");
@@ -31,7 +31,6 @@ 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;
@@ -39,7 +38,6 @@ 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;
@@ -48,7 +46,6 @@ 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;
@@ -66,28 +63,6 @@ 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,
@@ -151,21 +126,6 @@ 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);
@@ -230,7 +190,7 @@ class DefaultBeanRegistrationCodeFragmentsTests {
}
@Test
void customizedGetTargetDoesNotResolveInstantiationDescriptor() {
void customizedGetTargetDoesNotResolveConstructorOrFactoryMethod() {
RegisteredBean registeredBean = spy(registerTestBean(SimpleBean.class));
BeanRegistrationCodeFragments customCodeFragments = createCustomCodeFragments(registeredBean, codeFragments -> new BeanRegistrationCodeFragmentsDecorator(codeFragments) {
@Override
@@ -239,11 +199,11 @@ class DefaultBeanRegistrationCodeFragmentsTests {
}
});
assertTarget(customCodeFragments.getTarget(registeredBean), String.class);
verify(registeredBean, never()).resolveInstantiationDescriptor();
verify(registeredBean, never()).resolveConstructorOrFactoryMethod();
}
@Test
void customizedGenerateInstanceSupplierCodeDoesNotResolveInstantiationDescriptor() {
void customizedGenerateInstanceSupplierCodeDoesNotResolveConstructorOrFactoryMethod() {
RegisteredBean registeredBean = spy(registerTestBean(SimpleBean.class));
BeanRegistrationCodeFragments customCodeFragments = createCustomCodeFragments(registeredBean, codeFragments -> new BeanRegistrationCodeFragmentsDecorator(codeFragments) {
@Override
@@ -254,7 +214,7 @@ class DefaultBeanRegistrationCodeFragmentsTests {
});
assertThat(customCodeFragments.generateInstanceSupplierCode(this.generationContext,
new MockBeanRegistrationCode(this.generationContext), false)).hasToString("// Hello");
verify(registeredBean, never()).resolveInstantiationDescriptor();
verify(registeredBean, never()).resolveConstructorOrFactoryMethod();
}
private BeanRegistrationCodeFragments createCustomCodeFragments(RegisteredBean registeredBean, UnaryOperator<BeanRegistrationCodeFragments> customFragments) {
@@ -280,16 +240,13 @@ class DefaultBeanRegistrationCodeFragmentsTests {
return RegisteredBean.of(this.beanFactory, "testBean");
}
private RegisteredBean registerTestBean(ResolvableType beanType,
@Nullable Executable constructorOrFactoryMethod) {
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setTargetType(beanType);
return registerTestBean(applyConstructorOrFactoryMethod(
beanDefinition, constructorOrFactoryMethod));
}
private RegisteredBean registerTestBean(RootBeanDefinition beanDefinition) {
this.beanFactory.registerBeanDefinition("testBean", beanDefinition);
this.beanFactory.registerBeanDefinition("testBean",
applyConstructorOrFactoryMethod(beanDefinition, constructorOrFactoryMethod));
return RegisteredBean.of(this.beanFactory, "testBean");
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
package org.springframework.beans.factory.aot;
import java.lang.reflect.Executable;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
@@ -37,14 +38,10 @@ 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;
@@ -188,23 +185,6 @@ 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
@@ -422,9 +402,9 @@ class InstanceSupplierCodeGeneratorTests {
InstanceSupplierCodeGenerator generator = new InstanceSupplierCodeGenerator(
this.generationContext, generateClass.getName(),
generateClass.getMethods(), false);
InstantiationDescriptor instantiationDescriptor = registeredBean.resolveInstantiationDescriptor();
assertThat(instantiationDescriptor).isNotNull();
CodeBlock generatedCode = generator.generateCode(registeredBean, instantiationDescriptor);
Executable constructorOrFactoryMethod = registeredBean.resolveConstructorOrFactoryMethod();
assertThat(constructorOrFactoryMethod).isNotNull();
CodeBlock generatedCode = generator.generateCode(registeredBean, constructorOrFactoryMethod);
typeBuilder.set(type -> {
type.addModifiers(Modifier.PUBLIC);
type.addSuperinterface(ParameterizedTypeName.get(Supplier.class, InstanceSupplier.class));
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -116,9 +116,9 @@ class InstanceSupplierCodeGeneratorKotlinTests {
generationContext, generateClass.name,
generateClass.methods, false
)
val instantiationDescriptor = registeredBean.resolveInstantiationDescriptor()
Assertions.assertThat(instantiationDescriptor).isNotNull()
val generatedCode = generator.generateCode(registeredBean, instantiationDescriptor)
val constructorOrFactoryMethod = registeredBean.resolveConstructorOrFactoryMethod()
Assertions.assertThat(constructorOrFactoryMethod).isNotNull()
val generatedCode = generator.generateCode(registeredBean, constructorOrFactoryMethod)
typeBuilder.set { type: TypeSpec.Builder ->
type.addModifiers(Modifier.PUBLIC)
type.addSuperinterface(
@@ -1,25 +0,0 @@
/*
* 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();
}
}
@@ -1,30 +0,0 @@
/*
* 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();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,7 +27,7 @@ import org.springframework.util.ClassUtils;
/**
* {@link RuntimeHintsRegistrar} implementation that makes sure {@link SchedulerFactoryBean}
* reflection hints are registered.
* reflection entries 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 static final ReflectiveRuntimeHintsRegistrar registrar = new ReflectiveRuntimeHintsRegistrar();
private final ReflectiveRuntimeHintsRegistrar reflectiveRegistrar = 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);
registrar.registerRuntimeHints(hints, LocalTaskExecutorThreadPool.class);
this.reflectiveRegistrar.registerRuntimeHints(hints, LocalTaskExecutorThreadPool.class);
}
private void typeHint(Builder typeHint) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,7 +28,6 @@ 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;
@@ -38,7 +37,6 @@ 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;
@@ -108,19 +106,12 @@ class ConfigurationClassEnhancer {
}
return configClass;
}
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);
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;
}
/**
@@ -234,6 +225,7 @@ class ConfigurationClassEnhancer {
};
return new TransformingClassGenerator(cg, transformer);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -75,7 +75,6 @@ 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;
@@ -388,11 +387,11 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
});
// Detect any custom bean name generation strategy supplied through the enclosing application context
SingletonBeanRegistry singletonRegistry = null;
if (registry instanceof SingletonBeanRegistry sbr) {
singletonRegistry = sbr;
SingletonBeanRegistry sbr = null;
if (registry instanceof SingletonBeanRegistry _sbr) {
sbr = _sbr;
if (!this.localBeanNameGeneratorSet) {
BeanNameGenerator generator = (BeanNameGenerator) singletonRegistry.getSingleton(
BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
if (generator != null) {
this.componentScanBeanNameGenerator = generator;
@@ -453,8 +452,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
while (!candidates.isEmpty());
// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
if (singletonRegistry != null && !singletonRegistry.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
singletonRegistry.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
}
// Store the PropertySourceDescriptors to contribute them Ahead-of-time if necessary
@@ -553,7 +552,6 @@ 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.
@@ -649,8 +647,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
}
return mappings;
}
}
}
private static class PropertySourcesAotContribution implements BeanFactoryInitializationAotContribution {
@@ -763,8 +761,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
return nonNull.get();
}
}
}
}
private static class ConfigurationClassProxyBeanRegistrationCodeFragments extends BeanRegistrationCodeFragmentsDecorator {
@@ -772,9 +770,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
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();
@@ -783,7 +780,6 @@ 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));
@@ -794,29 +790,29 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
@Override
public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode, boolean allowDirectSupplierShortcut) {
BeanRegistrationCode beanRegistrationCode,
boolean allowDirectSupplierShortcut) {
InstantiationDescriptor instantiationDescriptor = proxyInstantiationDescriptor(
generationContext.getRuntimeHints(), this.registeredBean.resolveInstantiationDescriptor());
Executable executableToUse = proxyExecutable(generationContext.getRuntimeHints(),
this.registeredBean.resolveConstructorOrFactoryMethod());
return new InstanceSupplierCodeGenerator(generationContext,
beanRegistrationCode.getClassName(), beanRegistrationCode.getMethods(), allowDirectSupplierShortcut)
.generateCode(this.registeredBean, instantiationDescriptor);
.generateCode(this.registeredBean, executableToUse);
}
private InstantiationDescriptor proxyInstantiationDescriptor(RuntimeHints runtimeHints, InstantiationDescriptor instantiationDescriptor) {
Executable userExecutable = instantiationDescriptor.executable();
private Executable proxyExecutable(RuntimeHints runtimeHints, Executable userExecutable) {
if (userExecutable instanceof Constructor<?> userConstructor) {
try {
runtimeHints.reflection().registerConstructor(userConstructor, ExecutableMode.INTROSPECT);
Constructor<?> constructor = this.proxyClass.getConstructor(userExecutable.getParameterTypes());
return new InstantiationDescriptor(constructor);
return this.proxyClass.getConstructor(userExecutable.getParameterTypes());
}
catch (NoSuchMethodException ex) {
throw new IllegalStateException("No matching constructor found on proxy " + this.proxyClass, ex);
}
}
return instantiationDescriptor;
return userExecutable;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,8 +39,7 @@ 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) {
@@ -50,9 +49,7 @@ class ReflectiveProcessorBeanFactoryInitializationAotProcessor implements BeanFa
return new ReflectiveProcessorBeanFactoryInitializationAotContribution(beanTypes);
}
private static class ReflectiveProcessorBeanFactoryInitializationAotContribution
implements BeanFactoryInitializationAotContribution {
private static class ReflectiveProcessorBeanFactoryInitializationAotContribution implements BeanFactoryInitializationAotContribution {
private final Class<?>[] types;
@@ -63,8 +60,9 @@ 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);
}
}
}
@@ -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) {
beanFactory.getBean(weaverAwareName, LoadTimeWeaverAware.class);
getBean(weaverAwareName);
}
// Stop using the temporary ClassLoader for type matching.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -126,7 +126,6 @@ public abstract class AbstractRefreshableApplicationContext extends AbstractAppl
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
beanFactory.setApplicationStartup(getApplicationStartup());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
this.beanFactory = beanFactory;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@ 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;
@@ -429,37 +428,16 @@ 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()) {
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;
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;
}
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,10 +22,8 @@ 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;
@@ -37,14 +35,9 @@ 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
@@ -56,23 +49,15 @@ 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);
}
@@ -217,16 +202,8 @@ 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)) {
Collections.addAll(fallbackPatterns, this.fallbackPatterns);
}
if (!fallbackPatterns.isEmpty()) {
for (String pattern : fallbackPatterns) {
for (String pattern : this.fallbackPatterns) {
try {
DateFormat dateFormat = configureDateFormat(new SimpleDateFormat(pattern, locale));
// Align timezone for parsing format with printing format if ISO is set.
@@ -244,8 +221,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;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,12 +45,12 @@ public class InstantFormatter implements Formatter<Instant> {
return Instant.ofEpochMilli(Long.parseLong(text));
}
catch (NumberFormatException ex) {
if (!text.isEmpty() && Character.isAlphabetic(text.charAt(0))) {
if (text.length() > 0 && 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.000Z"
// assuming UTC instant a la "2007-12-03T10:15:30.00Z"
return Instant.parse(text);
}
}
@@ -411,7 +411,7 @@ public class ScheduledAnnotationBeanPostProcessor
}
catch (RuntimeException ex) {
throw new IllegalArgumentException(
"Invalid initialDelayString value \"" + initialDelayString + "\"; " + ex);
"Invalid initialDelayString value \"" + initialDelayString + "\" - cannot parse into long");
}
}
}
@@ -463,7 +463,7 @@ public class ScheduledAnnotationBeanPostProcessor
}
catch (RuntimeException ex) {
throw new IllegalArgumentException(
"Invalid fixedDelayString value \"" + fixedDelayString + "\"; " + ex);
"Invalid fixedDelayString value \"" + fixedDelayString + "\" - cannot parse into long");
}
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 + "\"; " + ex);
"Invalid fixedRateString value \"" + fixedRateString + "\" - cannot parse into long");
}
tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, delayToUse)));
}
@@ -28,14 +28,17 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
*
* @author Adrian Colyer
* @author Chris Beams
* @author Juergen Hoeller
*/
class OverloadedAdviceTests {
@Test
@SuppressWarnings("resource")
void testConfigParsingWithMismatchedAdviceMethod() {
new ClassPathXmlApplicationContext(getClass().getSimpleName() + ".xml", getClass());
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");
}
@Test
@@ -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,7 +245,6 @@ class ConfigurationClassPostProcessorAotContributionTests {
}
}
@Nested
class PropertySourceTests {
@@ -411,8 +410,8 @@ class ConfigurationClassPostProcessorAotContributionTests {
@PropertySource("classpath:org/springframework/context/annotation/p?.properties")
static class PropertySourceWithWildcardLocationPatternConfiguration {
}
}
}
@Nested
class ConfigurationClassProxyTests {
@@ -433,13 +432,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) {
@@ -458,8 +458,8 @@ class ConfigurationClassPostProcessorAotContributionTests {
.containsExactly(entry(key.getName(), value.getName()));
}
static class CustomPropertySourcesFactory extends DefaultPropertySourceFactory {
}
}
@@ -29,9 +29,7 @@ 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;
@@ -41,9 +39,8 @@ import static org.assertj.core.api.Assertions.assertThat;
* {@link FactoryBean FactoryBeans} defined in the configuration.
*
* @author Phillip Webb
* @author Juergen Hoeller
*/
class ConfigurationWithFactoryBeanEarlyDeductionTests {
class ConfigurationWithFactoryBeanBeanEarlyDeductionTests {
@Test
void preFreezeDirect() {
@@ -85,16 +82,6 @@ class ConfigurationWithFactoryBeanEarlyDeductionTests {
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
@@ -118,13 +105,14 @@ class ConfigurationWithFactoryBeanEarlyDeductionTests {
}
}
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) {
@@ -144,38 +132,41 @@ class ConfigurationWithFactoryBeanEarlyDeductionTests {
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 {
ResolvableType typeToMatch = ResolvableType.forClassWithGenerics(MyBean.class, String.class);
this.names = beanFactory.getBeanNamesForType(typeToMatch, true, false);
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
this.names = beanFactory.getBeanNamesForType(MyBean.class, true, false);
}
public String[] getNames() {
return this.names;
}
}
@Configuration
static class DirectConfiguration {
@Bean
MyBean<String> myBean() {
return new MyBean<>();
MyBean myBean() {
return new MyBean();
}
}
@Configuration
static class GenericMethodConfiguration {
@Bean
FactoryBean<MyBean<String>> myBean() {
return new TestFactoryBean<>(new MyBean<>());
FactoryBean<MyBean> myBean() {
return new TestFactoryBean<>(new MyBean());
}
}
@Configuration
@@ -185,11 +176,13 @@ class ConfigurationWithFactoryBeanEarlyDeductionTests {
MyFactoryBean myBean() {
return new MyFactoryBean();
}
}
@Configuration
@Import(AttributeClassRegistrar.class)
static class AttributeClassConfiguration {
}
static class AttributeClassRegistrar implements ImportBeanDefinitionRegistrar {
@@ -198,32 +191,16 @@ class ConfigurationWithFactoryBeanEarlyDeductionTests {
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
BeanDefinition definition = BeanDefinitionBuilder.genericBeanDefinition(
RawWithAbstractObjectTypeFactoryBean.class).getBeanDefinition();
definition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE,
ResolvableType.forClassWithGenerics(MyBean.class, String.class));
definition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, MyBean.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<T> extends AbstractMyBean {
static class MyBean extends AbstractMyBean {
}
static class TestFactoryBean<T> implements FactoryBean<T> {
@@ -243,26 +220,31 @@ class ConfigurationWithFactoryBeanEarlyDeductionTests {
public Class<?> getObjectType() {
return this.instance.getClass();
}
}
static class MyFactoryBean extends TestFactoryBean<MyBean<String>> {
static class MyFactoryBean extends TestFactoryBean<MyBean> {
public MyFactoryBean() {
super(new MyBean<>());
super(new MyBean());
}
}
static class RawWithAbstractObjectTypeFactoryBean implements FactoryBean<Object> {
private final Object object = new MyBean();
@Override
public Object getObject() throws Exception {
throw new IllegalStateException();
public Object getObject() {
return object;
}
@Override
public Class<?> getObjectType() {
return MyBean.class;
}
}
}
@@ -35,7 +35,6 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
*
* @author Keith Donald
* @author Phillip Webb
* @author Juergen Hoeller
*/
class DateFormatterTests {
@@ -46,7 +45,6 @@ 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);
@@ -56,7 +54,6 @@ 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);
@@ -67,7 +64,6 @@ 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);
@@ -78,7 +74,6 @@ 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);
@@ -89,7 +84,6 @@ 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);
@@ -100,18 +94,16 @@ 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))
@@ -119,44 +111,33 @@ 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
@@ -167,8 +148,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);
assertThat(formatter.print(date, Locale.US)).as("uses pattern").isEqualTo("2009");
formatter.setPattern("");
@@ -274,7 +274,9 @@ 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
@@ -284,7 +286,9 @@ 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
@@ -294,7 +298,9 @@ 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
@@ -304,7 +310,9 @@ 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
@@ -317,7 +325,9 @@ 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
@@ -530,7 +540,6 @@ class DateTimeFormattingTests {
assertThat(binder.getBindingResult().getRawFieldValue("monthDayAnnotatedPattern")).isEqualTo(MonthDay.parse("--01-03"));
}
@Nested
class FallbackPatternTests {
@@ -635,10 +644,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;
@@ -646,7 +655,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;
@@ -666,7 +675,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;
@@ -694,6 +703,7 @@ class DateTimeFormattingTests {
private final List<DateTimeBean> children = new ArrayList<>();
public LocalDate getLocalDate() {
return this.localDate;
}
@@ -20,7 +20,6 @@ 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;
@@ -51,12 +50,13 @@ 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, Locale.US);
Instant actual = instantFormatter.parse(input, null);
assertThat(actual).isEqualTo(expected);
}
@@ -64,7 +64,9 @@ 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, Locale.US);
Instant actual = instantFormatter.parse(input, null);
assertThat(actual).isEqualTo(expected);
}
@@ -72,18 +74,20 @@ 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, Locale.US);
String actual = instantFormatter.print(input, null);
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()), Locale.US);
Instant actual = instantFormatter.parse(Long.toString(input.toEpochMilli()), null);
assertThat(actual).isEqualTo(input);
}
private static class RandomInstantProvider implements ArgumentsProvider {
private static final long DATA_SET_SIZE = 10;
@@ -105,7 +109,6 @@ class InstantFormatterTests {
}
}
private static class ISOSerializedInstantProvider extends RandomInstantProvider {
@Override
@@ -114,7 +117,6 @@ class InstantFormatterTests {
}
}
private static class RFC1123SerializedInstantProvider extends RandomInstantProvider {
// RFC-1123 supports only 4-digit years
@@ -128,8 +130,6 @@ 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;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -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 {
@@ -18,6 +18,4 @@
<bean id="testBean" class="org.springframework.beans.testfixture.beans.TestBean"/>
<bean id="testBean2" class="org.springframework.beans.testfixture.beans.TestBean"/>
</beans>
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -124,7 +124,7 @@ class InstrumentedMethodTests {
}
@Test
void classGetConstructorShouldMatchIntrospectPublicConstructorsHint() {
void classGetConstructorShouldMatchInstrospectPublicConstructorsHint() {
hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_PUBLIC_CONSTRUCTORS);
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCONSTRUCTOR, this.stringGetConstructor);
}
@@ -148,7 +148,7 @@ class InstrumentedMethodTests {
}
@Test
void classGetConstructorShouldMatchIntrospectConstructorHint() {
void classGetConstructorShouldMatchInstrospectConstructorHint() {
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 classGetDeclaredConstructorShouldMatchIntrospectConstructorHint() {
void classGetDeclaredConstructorShouldMatchInstrospectConstructorHint() {
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 classGetMethodsShouldMatchIntrospectDeclaredMethodsHint() {
void classGetMethodsShouldMatchInstrospectDeclaredMethodsHint() {
hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_DECLARED_METHODS);
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHODS, this.stringGetMethods);
}
@Test
void classGetMethodsShouldMatchIntrospectPublicMethodsHint() {
void classGetMethodsShouldMatchInstrospectPublicMethodsHint() {
hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_PUBLIC_METHODS);
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHODS, this.stringGetMethods);
}
@@ -396,7 +396,7 @@ class InstrumentedMethodTests {
}
@Test
void classGetMethodShouldMatchIntrospectPublicMethodsHint() {
void classGetMethodShouldMatchInstrospectPublicMethodsHint() {
hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_PUBLIC_METHODS);
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetToStringMethod);
}
@@ -434,13 +434,13 @@ class InstrumentedMethodTests {
}
@Test
void classGetMethodShouldNotMatchIntrospectPublicMethodsHintWhenPrivate() {
void classGetMethodShouldNotMatchInstrospectPublicMethodsHintWhenPrivate() {
hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_PUBLIC_METHODS);
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetScaleMethod);
}
@Test
void classGetMethodShouldMatchIntrospectDeclaredMethodsHintWhenPrivate() {
void classGetMethodShouldMatchInstrospectDeclaredMethodsHintWhenPrivate() {
hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_DECLARED_METHODS);
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetScaleMethod);
}
@@ -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, Type... types) {
public void registerReflectionHints(ReflectionHints hints, @Nullable Type... types) {
Set<Type> seen = new LinkedHashSet<>();
for (Type type : types) {
registerReflectionHints(hints, seen, type);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,7 +39,8 @@ 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 {
@@ -36,7 +36,7 @@ import org.springframework.core.annotation.AliasFor;
*
* <pre class="code">
* &#064;Configuration
* &#064;RegisterReflectionForBinding({Foo.class, Bar.class})
* &#064;RegisterReflectionForBinding({ Foo.class, Bar.class })
* public class MyConfig {
* // ...
* }</pre>
@@ -77,7 +77,8 @@ public @interface RegisterReflectionForBinding {
/**
* Classes for which reflection hints should be registered.
* <p>At least one class must be specified either via {@link #value} or {@code classes}.
* <p>At least one class must be specified either via {@link #value} or
* {@link #classes}.
* @see #value()
*/
@AliasFor("value")
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -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,7 +65,6 @@ 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()) {
@@ -103,7 +102,6 @@ class SpringFactoriesLoaderRuntimeHints implements RuntimeHintsRegistrar {
}
}
private static class ExtendedSpringFactoriesLoader extends SpringFactoriesLoader {
ExtendedSpringFactoriesLoader(@Nullable ClassLoader classLoader, Map<String, List<String>> factories) {
@@ -113,6 +111,7 @@ 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,15 +544,7 @@ public class ReflectUtils {
// No defineClass variant available at all?
if (c == null) {
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.";
}
};
throw new CodeGenerationException(t);
}
// Force static initializers to run.
@@ -57,7 +57,7 @@ public class MethodProxy {
proxy.createInfo = new CreateInfo(c1, c2);
// SPRING PATCH BEGIN
if (c1 != Object.class && c1.isAssignableFrom(c2.getSuperclass()) && !Factory.class.isAssignableFrom(c2)) {
if (!c1.isInterface() && c1 != Object.class && !Factory.class.isAssignableFrom(c2)) {
// Try early initialization for overridden methods on specifically purposed subclasses
try {
proxy.init();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -418,10 +418,7 @@ final class TypeMappedAnnotations implements MergedAnnotations {
Annotation[] repeatedAnnotations = repeatableContainers.findRepeatedAnnotations(annotation);
if (repeatedAnnotations != null) {
MergedAnnotation<A> result = doWithAnnotations(type, aggregateIndex, source, repeatedAnnotations);
if (result != null) {
return result;
}
return doWithAnnotations(type, aggregateIndex, source, repeatedAnnotations);
}
AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(
annotation.annotationType(), repeatableContainers, annotationFilter);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -48,7 +48,7 @@ public class PropertiesPropertySource extends MapPropertySource {
@Override
public String[] getPropertyNames() {
synchronized (this.source) {
return ((Map<?, ?>) this.source).keySet().stream().filter(k -> k instanceof String).toArray(String[]::new);
return super.getPropertyNames();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,22 +23,16 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* {@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.
*
* {@link Resource} implementation for a given {@link InputStream}.
* <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 if possible. If you need to obtain
* a custom stream multiple times, use a custom {@link AbstractResource} subclass
* with a corresponding {@code getInputStream()} implementation.
* file-based {@code Resource} implementations where possible.
*
* <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. This also applies when constructed with an {@code InputStreamSource}
* which lazily obtains the stream but only allows for single access as well.
* {@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.
*
* @author Juergen Hoeller
* @author Sam Brannen
@@ -50,62 +44,30 @@ import org.springframework.util.Assert;
*/
public class InputStreamResource extends AbstractResource {
private final InputStreamSource inputStreamSource;
private final InputStream inputStream;
private final String description;
private final Object equality;
private boolean read = false;
/**
* 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.
* Create a new InputStreamResource.
* @param inputStream the InputStream to use
* @see #InputStreamResource(InputStreamSource)
*/
public InputStreamResource(InputStream inputStream) {
this(inputStream, "resource loaded through InputStream");
}
/**
* Create a new {@code InputStreamResource} for an existing {@code InputStream}.
* Create a new InputStreamResource.
* @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.inputStreamSource = () -> inputStream;
this.inputStream = inputStream;
this.description = (description != null ? description : "");
this.equality = inputStream;
}
@@ -136,7 +98,7 @@ public class InputStreamResource extends AbstractResource {
"do not use InputStreamResource if a stream needs to be read multiple times");
}
this.read = true;
return this.inputStreamSource.getInputStream();
return this.inputStream;
}
/**
@@ -155,7 +117,7 @@ public class InputStreamResource extends AbstractResource {
@Override
public boolean equals(@Nullable Object other) {
return (this == other || (other instanceof InputStreamResource that &&
this.equality.equals(that.equality)));
this.inputStream.equals(that.inputStream)));
}
/**
@@ -163,7 +125,7 @@ public class InputStreamResource extends AbstractResource {
*/
@Override
public int hashCode() {
return this.equality.hashCode();
return this.inputStream.hashCode();
}
}

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