Compare commits

...

86 Commits

Author SHA1 Message Date
Spring Builds 5d6f2c84c8 Release v6.1.7 2024-05-16 08:52:46 +00:00
rstoyanchev 010e8a303b Polishing contribution
Closes gh-32799
2024-05-15 20:06:48 +01:00
Matteo Cristoforo 78549d4963 Fix cookie management in reactive JdkClientHttpRequest
Prevent cookies to be set to empty in the HTTP request if no cookie
is present

See gh-32799
2024-05-15 20:02:04 +01:00
Juergen Hoeller e4e6910122 Polishing 2024-05-15 14:15:52 +02:00
Juergen Hoeller 2270df515b Enforce cleaned URL for root resource from ClassLoader
Closes gh-32828
2024-05-15 14:15:47 +02:00
Juergen Hoeller 70886e32c0 Upgrade to Reactor 2023.0.6
Includes AspectJ 1.9.22.1, Tomcat 10.1.24, Jetty 12.0.9, Mockito 5.12

Closes gh-32786
2024-05-14 22:01:07 +02:00
Juergen Hoeller e509385eae Add InputStreamResource(InputStreamSource) constructor for lambda expressions
Includes notes for reliable InputStream closing, in particular with Spring MVC.

Closes gh-32802
2024-05-14 21:59:42 +02:00
Juergen Hoeller b7aafda872 Polishing 2024-05-14 13:43:35 +02:00
Brian Clozel a89a88d17c Upgrade to Micrometer 1.12.6
Closes gh-32810
2024-05-13 21:25:09 +02:00
Brian Clozel c6b6ccdc89 Close ResponseBodyEmitter in case of write errors
Prior to this commit, the `ReactiveTypeHandler` would handle `Flux`-like
return types from controller methods and adapt them to SSE streams using
the `SseEmitter`/`ResponseBodyEmitter` APIs. In case an `IOException` is
thrown while writing to the HTTP response stream, the
`ReactiveTypeHandler` would rely on the Servlet container to call
`AsyncListener#onError` - this would be the signal for Spring MVC to
complete the async exchange. To prevent racing issues between this
signal and the actual handling of the exception, changes like  gh-20173
were applied. Since then, robust checks were added with gh-32340 in
`StandardServletAsyncWebRequest.LifecycleHttpServletResponse`.

With Jetty 12, `AsyncListener#onError` would not be called as the error
would happen while writing in blocking mode to the response (so, not
using the Servlet WriteListener contract). But still, such `IOException`
would still result in the closing of the HTTP connection. As of Jetty
12.0.4, this is no longer the case and the party managing the async
lifecycle is in charge of completing the exchange, as it should. This
means that the current behavior leaks HTTP connections for these cases
and causes memory issues.

This commit ensures that such exceptions happening during response
writes are caught and result in the completion of the `SSEEmitter` and
the closing of the exchange. Even if other Servlet containers still
propagate the error `AsyncListener#onError`, competing signals are still
managed with gh-32340.

Closes gh-32629
2024-05-13 20:30:13 +02:00
Brian Clozel 09b8feadc2 Document streaming/collecting behavior for Flux return values
Closes gh-32630
2024-05-13 13:12:53 +02:00
rstoyanchev d03ea0bf19 Update docs on HandlerInterceptor
Closes gh-32729
2024-05-13 11:40:32 +01:00
rstoyanchev a5a1ef6b30 Use instance field for ProblemDetail in ErrorResponse's
Closes gh-32644
2024-05-13 11:40:32 +01:00
rstoyanchev 1b60b86bb2 Update MockMvc section on Streaming in the docs
Closes gh-32687
2024-05-13 11:40:32 +01:00
Brian Clozel 89ce63f1f3 Replace RFC7807 by RFC9457 in documentation
This commit updates all references to RFC7807 by RFC9457 since the
former is now obsolete.

Closes gh-32806
2024-05-13 10:42:35 +02:00
Sam Brannen 7536980be9 Fix Dokka links to Spring Framework and Servlet APIs
This commit fixes links from Spring Framework's Dokka HTML to Javadoc
for Spring Framework and Servlet APIs by explicitly configuring the
`element-list` page as the `package-list` in the Dokka Gradle plugin.

Closes gh-32797
2024-05-12 12:29:52 +02:00
Sam Brannen c1229b03a7 Polishing 2024-05-11 16:01:08 +02:00
Juergen Hoeller a4135bac5c Leniently ignore unsupported pointcut expression
See gh-32793
2024-05-10 13:52:39 +02:00
Juergen Hoeller bf08e0c1b4 Polishing 2024-05-10 12:27:29 +02:00
Juergen Hoeller 9202c0ad41 Accept ajc-compiled @Aspect classes for Spring AOP proxy usage
Closes gh-32793
2024-05-10 12:27:12 +02:00
Juergen Hoeller 22b6d66a28 Document @Order behavior on @Configuration classes versus @Bean methods
Includes brief note on self injection (extracted from qualifiers section).

Closes gh-30177
Closes gh-28299
2024-05-08 17:52:02 +02:00
Juergen Hoeller 9376e6322d Revise IoC container introduction for modern configuration styles
Includes @Configuration(proxyBeanMethods=false) documentation.

Closes gh-32429
2024-05-08 17:51:25 +02:00
Juergen Hoeller 0eb937a866 Document limitations of CGLIB proxy class generation in JPMS module setups
Includes extended exception messages with common hints and explanations.

Closes gh-32671
2024-05-08 17:51:17 +02:00
Stéphane Nicoll 6250b64766 Add support for form data in MockHttpServletRequestBuilder
Closes gh-32757
2024-05-08 11:56:16 +02:00
Stéphane Nicoll 5b1278d03c Refine exception message to include bean name
Closes gh-32775
2024-05-08 10:27:34 +02:00
Juergen Hoeller 4f02be263f Polishing 2024-05-07 15:52:13 +02:00
Juergen Hoeller 05d9b52b19 Polishing 2024-05-06 20:10:40 +02:00
Juergen Hoeller 59a125d06f Unwrap raw target Query instance in case of proxy mismatch
Closes gh-32766
2024-05-06 20:09:43 +02:00
Sam Brannen aebc48ee8d Revise contribution
See gh-32704
2024-05-03 17:04:11 +03:00
Mikaël Francoeur f51be0a17e Support varargs invocations in SpEL for varargs array subtype
Closes gh-32704
2024-05-03 17:04:11 +03:00
Sam Brannen 1d2b5a15c3 Polishing 2024-05-03 17:04:11 +03:00
Sam Brannen 4baad16437 Include repeatable annotation container in MergedAnnotations results
A bug has existed in Spring's MergedAnnotations support since it was
introduced in Spring Framework 5.2. Specifically, if the
MergedAnnotations API is used to search for annotations with "standard
repeatable annotation" support enabled (which is the default), it's
possible to search for a repeatable annotation but not for the
repeatable annotation's container annotation.

The reason is that MergedAnnotationFinder.process(Object, int, Object,
Annotation) does not process the container annotation and instead only
processes the "contained" annotations, which prevents a container
annotation from being included in search results.

In #29685, we fixed a bug that prevented the MergedAnnotations support
from recognizing an annotation as a container if the container
annotation declares attributes other than the required `value`
attribute. As a consequence of that bug fix, since Spring Framework
5.3.25, the MergedAnnotations infrastructure considers such an
annotation a container, and due to the aforementioned bug the container
is no longer processed, which results in a regression in behavior for
annotation searches for such a container annotation.

This commit addresses the original bug as well as the regression by
processing container annotations in addition to the contained
repeatable annotations.

See gh-29685
Closes gh-32731
2024-05-03 12:18:04 +03:00
Stéphane Nicoll abcc1dfc6c Review usage of BindingReflectionHintsRegistrar#registerReflectionHints
Closes gh-32753
2024-05-02 16:53:59 +02:00
Brian Clozel 47c5cd208c Add missing "Content-Length: 0" header with HttpComponents
Prior to this commit, HTTP requests sent with the
`HttpComponentsClientHttpRequestFactory` would not set a
"Content-Length" header for empty request bodies. Setting a request
entity is the expected behavior for unsafe HTTP methods, and this would
align the behavior with other HTTP clients.
Developers would often rely on `BufferingClientHttpRequestFactory` to
set this information on the request.

This commit ensures that a `NullEntity` is used for unsafe HTTP methods,
when no body has been set for the request. This result in a
"Content-Length:0" request header.

Fixes gh-32678
2024-05-02 15:42:35 +02:00
Sam Brannen 5a24e94d2e Polish tests 2024-05-02 13:28:48 +03:00
Stéphane Nicoll f90bdbef42 Add noop implementation for ResponseErrorHandler
Closes gh-32750
2024-05-02 11:44:42 +02:00
Sébastien Deleuze f17527a48b Use expectBody<Person>() in WebTestClient documentation
Closes gh-32733
2024-05-02 11:07:36 +02:00
Brian Clozel 64b0283042 Revert "Do not set 0 Content-Length header in BufferingClientHttpRequestFactory"
This reverts commit b3a45670f9.

See gh-32650
2024-05-02 09:11:54 +02:00
Juergen Hoeller 25cedcfb99 Consistently propagate ApplicationStartup to BeanFactory
Closes gh-32747
2024-05-01 18:06:27 +02:00
Juergen Hoeller 610626aec6 Ignore non-String keys in PropertiesPropertySource.getPropertyNames()
Closes gh-32742
2024-05-01 15:41:26 +02:00
Brian Clozel d8afe7a8ad Fix build warnings
See gh-32724
2024-04-30 19:10:31 +02:00
Seungrae Kim 32c80d5ae6 Fix incorrect class reference syntax in Kotlin code sample
Closes gh-32733
2024-04-30 18:09:02 +03:00
Arjen Poutsma 0241a02e66 Fix guard against multiple subscriptions
This commit changes the guard against multiple subscriptions, as the
previously used doOnSubscribe hook could not function as guard in
certain scenarios.

Closes gh-32727
2024-04-30 16:07:03 +02:00
Juergen Hoeller cbda46984c Polishing 2024-04-30 15:46:28 +02:00
Juergen Hoeller 7263771552 Add documentation for CompletableFuture-driven rollback
Closes gh-32709
2024-04-30 15:45:56 +02:00
Juergen Hoeller fab3633c75 Add notes on constructor and factory method overloading
Closes gh-32091
2024-04-30 15:45:46 +02:00
Juergen Hoeller 26706f0039 Upgrade to Groovy 4.0.21, Tomcat 10.1.23, Jetty 12.0.8, Netty 4.1.109, Undertow 2.3.13, Checkstyle 10.16 2024-04-29 18:04:25 +02:00
Juergen Hoeller 3cf2cd70ab Polishing 2024-04-29 18:02:44 +02:00
Juergen Hoeller 0ac04a7f86 Include original exception message in dependency initialization exception
Closes gh-32470
2024-04-29 18:02:39 +02:00
Brian Clozel 370a4ee8b3 Upgrade to gradle-enterprise-conventions 0.0.17
Closes gh-32724
2024-04-29 13:15:05 +02:00
Simon Baslé 55361fa4f6 Release RSocket setup payload when it is not handled
This commit counterbalances the retain in the MessagingRSocket
handleConnectionSetupPayload method with a conditional release on SETUP
frame type in handleNoMatch, preventing Netty buffer leaks.

Closes gh-32424
2024-04-25 10:09:05 +02:00
Sam Brannen 546ca9b834 Polishing 2024-04-24 15:32:57 +03:00
Juergen Hoeller 09b476ac18 Polishing 2024-04-24 13:42:02 +02:00
Juergen Hoeller 387e34d881 Wrap depends-on exception for specifically requested top-level bean
Closes gh-32470
2024-04-24 13:41:48 +02:00
Stéphane Nicoll 4a10bc3288 Refine preDetermineBeanTypes algorithm
This commit refines the preDetermineBeanTypes algorithm that AOT uses
to approximate the order of preInstantiateSingletons more closely.

Previously, the algorithm assumed that all beans where non-lazy
singletons in terms of initialization order, which led to inconsistent
order in CGLIB proxy generation.

We now explicitly park lazy beans so that their types are determined
during a second phase, matching the order of regular initialization
order.

Closes gh-32669

Co-authored-by: Juergen Hoeller <juergen.hoeller@broadcom.com>
2024-04-24 09:23:51 +02:00
Juergen Hoeller 580adfcbed Polishing 2024-04-23 16:07:09 +02:00
Juergen Hoeller d151931f86 Skip close lock if acquired by other thread already
Closes gh-32445
2024-04-23 16:06:48 +02:00
Stéphane Nicoll 16d8d69cf9 Merge pull request #32696 from izeye
* pr/32696:
  Update copyright year of changed files
  Polish

Closes gh-32696
2024-04-23 15:34:44 +02:00
Stéphane Nicoll 95ac0eae4a Update copyright year of changed files
See gh-32696
2024-04-23 15:32:59 +02:00
Johnny Lim cc73ccefef Polish
See gh-32696
2024-04-23 15:31:48 +02:00
Stéphane Nicoll 4d34444a69 Consistent annotation lookup in MvcUriComponentsBuilder
Previously, a UriComponents build based on a method would require the
given method to be the annotated one as method parameter resolution only
applied locally. This was a problem when a controller was specified on
a method whose mapping is defined in a parent.

This commit harmonies the lookup using AnnotatedMethod who provides
support for a synthesized method parameter that takes annotations from
parent parameter into account.

Closes gh-32553
2024-04-23 15:21:27 +02:00
Juergen Hoeller d4ddbd537b Polishing 2024-04-23 13:16:33 +02:00
Juergen Hoeller 0b9b9b4602 Avoid overreading of InputStream in copyRange
Closes gh-32695
2024-04-23 13:16:28 +02:00
Stéphane Nicoll 40596d444c Provide a more explicit link to URI composition examples
Closes gh-32685
2024-04-23 12:43:15 +02:00
Stéphane Nicoll cb5b9dcaed Document AOT limitation of creating beans with custom arguments
Closes gh-32690
2024-04-23 11:03:28 +02:00
Stéphane Nicoll c99e7f8616 Ignore InstanceSupplier for getBean with arguments
Previously, a BeanDefinition that has an instance supplier would be
used irrespective of custom arguments being provided. This commit only
applies the instance supplier if no arguments are provided. With
custom arguments, the regular lookup takes place based on the
information provided in the bean factory.

Closes gh-32657
2024-04-22 16:59:31 +02:00
Stéphane Nicoll 8a8c8fe00e Detect target of factory method with AOT
Previously, if a factory method is defined on a parent, the generated
code would blindly use the method's declaring class for both the target
of the generated code, and the signature of the method.

This commit improves the resolution by considering the factory metadata
in the BeanDefinition.

Closes gh-32609
2024-04-22 15:13:56 +02:00
Stéphane Nicoll f45e7b9b9b Relax scope of DataIntegrityViolationException
This commit updates the Javadoc of DataIntegrityViolationException to
broaden its scope as it was too specific before.

Closes gh-32631
2024-04-22 14:49:17 +02:00
Juergen Hoeller ec1f5ca600 Polishing 2024-04-22 13:43:07 +02:00
Juergen Hoeller 62efdfb89c Try early initialization for all user-declared methods (including interfaces)
Closes gh-32682
2024-04-22 13:43:02 +02:00
Stéphane Nicoll c3da43291b Upgrade to Java 17.0.11 2024-04-22 12:13:01 +02:00
Brian Clozel 5aa576f5c6 Register status handler exceptions in observations
Prior to this commit, `RestClientException` thrown by status handlers
would not be registered as observation errors. This commit ensures that
such exceptions are first caught, registered in the observation and
rethrown as expected.

Closes gh-32575
2024-04-20 19:44:11 +02:00
Brian Clozel 0268180799 Rethrow unhandled JMS listener exceptions
Fixes gh-32666
2024-04-18 21:44:44 +02:00
Brian Clozel da4547a27e Extend observations to RestClient ResponseSpec
Prior to this commit, the `RestClient` observations would be stopped as
soon as the exchange function was called. This means that all errors
related to response decoding or mapping would not be recorded by the
obsevations.

This commit extends the observation recording to the `ResponseSpec` DSL
calls as well as custom exchange functions.

Fixes gh-32575
2024-04-18 21:26:18 +02:00
rstoyanchev 97eddb769a Avoid duplicate subscriptionId's in destinationCache
Closes gh-32625
2024-04-18 14:18:30 +01:00
Juergen Hoeller 0e0397a385 Polishing 2024-04-18 12:18:18 +02:00
Juergen Hoeller fec4f9b8cb Add test for pre-resolved target type on RootBeanDefinition
See gh-32649
2024-04-18 12:17:17 +02:00
Juergen Hoeller 4e20cdeb43 Expand comment on participation in existing transaction
Closes gh-32659
2024-04-18 12:16:33 +02:00
yhao3 7f27ba3902 Update links to HttpOnly documentation at OWASP in ResponseCookie
Closes gh-32663
2024-04-18 11:52:50 +02:00
Brian Clozel b3a45670f9 Do not set 0 Content-Length header in BufferingClientHttpRequestFactory
Prior to this commit, the `BufferingClientHttpRequestFactory`, through
the `AbstractBufferingClientHttpRequest`, would set a "Content-Length"
header value, even if the buffered body was empty.

This behavior is invalid since no request body would be set by the
client code in the first place.

This commit ensures that this header is only set if a request body has
been buffered and is about to be written to the request.

Fixes gh-32650
2024-04-17 22:44:36 +02:00
xumengqi 8ec27438e7 Fix transaction definition setTimeout error message since zero is valid
This commit rephrases the `DefaultTransactionDefinition#setTimeout`
exception message to better reflect the fact that 0 is a valid input
value.

Even though this often leads to a transaction immediately timing out
after opening, there is one notable case where this has another
meaningful effect: in Jakarta transactions (`UserTransaction`), when
passing 0 "the transaction service restores the default value".

Closes gh-32635
2024-04-16 16:39:23 +02:00
xumengqi bf3278596c Include actual cause's message in various parsing exception messages
This change improves the message of several parsing-related exceptions
that would previously entirely swallow the original exception's message
and sometimes have a slightly misleading message as a result.

This is done by appending the cause's `toString` representation to the
IllegalArgumentException messages instead of an hardcoded "cause".

Closes gh-32636
2024-04-16 16:25:42 +02:00
Arjen Poutsma 3971632415 Reintroduce SimpleClientHttpRequestFactory limitations
Closes gh-32641
2024-04-16 11:43:09 +02:00
Simon Baslé 1e54916119 Ensure multipart data is deleted in WebFlux when connection terminates
Before this change temporary files would not consistently be deleted
when the connection which uploads the multipart files closes naturally.

This change uses the usingWhen Reactor operator to ensure that the
termination of the connection doesn't prevent individual file parts
from being deleted due to a cancellation signal.

Closes gh-31217
2024-04-15 14:20:48 +02:00
Brian Clozel 6e20af41bf Upgrade CI image to Java 23ea17 2024-04-11 11:32:48 +02:00
Spring Builds c68c62b6c6 Next development version (v6.1.7-SNAPSHOT) 2024-04-11 08:26:17 +00:00
162 changed files with 2135 additions and 1038 deletions
@@ -35,9 +35,7 @@ jobs:
env:
CI: 'true'
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
run: ./gradlew -PdeploymentRepository=$(pwd)/deployment-repository build publishAllPublicationsToDeploymentRepository
- name: Deploy
uses: spring-io/artifactory-deploy-action@v0.0.1
+1 -3
View File
@@ -66,9 +66,7 @@ jobs:
env:
CI: 'true'
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
run: ./gradlew check antora
- name: Send notification
uses: ./.github/actions/send-notification
+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.10-librca
java=17.0.11-librca
@@ -50,7 +50,7 @@ public class CheckstyleConventions {
project.getPlugins().apply(CheckstylePlugin.class);
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g"));
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
checkstyle.setToolVersion("10.15.0");
checkstyle.setToolVersion("10.16.0");
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
+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/10/GPL/openjdk-23-ea+10_linux-x64_bin.tar.gz"
echo "https://download.java.net/java/early_access/jdk23/17/GPL/openjdk-23-ea+17_linux-x64_bin.tar.gz"
;;
*)
echo $"Unknown java version"
+1 -1
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 java22
for jdk in java17 java21 java23
do
JDK_URL=$( /get-jdk-url.sh $jdk )
mkdir $jdk
+1 -3
View File
@@ -5,9 +5,7 @@ anchors:
password: ((github-ci-release-token))
branch: ((branch))
gradle-enterprise-task-params: &gradle-enterprise-task-params
GRADLE_ENTERPRISE_ACCESS_KEY: ((gradle_enterprise_secret_access_key))
GRADLE_ENTERPRISE_CACHE_USERNAME: ((gradle_enterprise_cache_user.username))
GRADLE_ENTERPRISE_CACHE_PASSWORD: ((gradle_enterprise_cache_user.password))
DEVELOCITY_ACCESS_KEY: ((gradle_enterprise_secret_access_key))
sonatype-task-params: &sonatype-task-params
SONATYPE_USERNAME: ((sonatype-username))
SONATYPE_PASSWORD: ((sonatype-password))
@@ -19,6 +19,10 @@ you can do so. However, you should consider the following issues:
since the CGLIB proxy instance is created through Objenesis. Only if your JVM does
not allow for constructor bypassing, you might see double invocations and
corresponding debug log entries from Spring's AOP support.
* Your CGLIB proxy usage may face limitations with the JDK 9+ platform module system.
As a typical case, you cannot create a CGLIB proxy for a class from the `java.lang`
package when deploying on the module path. Such cases require a JVM bootstrap flag
`--add-opens=java.base/java.lang=ALL-UNNAMED` which is not available for modules.
To force the use of CGLIB proxies, set the value of the `proxy-target-class` attribute
of the `<aop:config>` element to true, as follows:
@@ -326,6 +326,19 @@ However, this is not a best practice and flagging the preferred constructor with
In case you are working on a code base that you cannot modify, you can set the {spring-framework-api}/beans/factory/support/AbstractBeanDefinition.html#PREFERRED_CONSTRUCTORS_ATTRIBUTE[`preferredConstructors` attribute] on the related bean definition to indicate which constructor should be used.
[[aot.bestpractices.custom-arguments]]
=== Avoid Creating Bean with Custom Arguments
Spring AOT detects what needs to be done to create a bean and translates that in generated code using an instance supplier.
The container also supports creating a bean with {spring-framework-api}++/beans/factory/BeanFactory.html#getBean(java.lang.String,java.lang.Object...)++[custom arguments] that leads to several issues with AOT:
. The custom arguments require dynamic introspection of a matching constructor or factory method.
Those arguments cannot be detected by AOT, so the necessary reflection hints will have to be provided manually.
. By-passing the instance supplier means that all other optimizations after creation are skipped as well.
For instance, autowiring on fields and methods will be skipped as they are handled in the instance supplier.
Rather than having prototype-scoped beans created with custom arguments, we recommend a manual factory pattern where a bean is responsible for the creation of the instance.
[[aot.bestpractices.factory-bean]]
=== FactoryBean
@@ -1,33 +1,16 @@
[[beans-annotation-config]]
= Annotation-based Container Configuration
.Are annotations better than XML for configuring Spring?
****
The introduction of annotation-based configuration raised the question of whether this
approach is "`better`" than XML. The short answer is "`it depends.`" The long answer is
that each approach has its pros and cons, and, usually, it is up to the developer to
decide which strategy suits them better. Due to the way they are defined, annotations
provide a lot of context in their declaration, leading to shorter and more concise
configuration. However, XML excels at wiring up components without touching their source
code or recompiling them. Some developers prefer having the wiring close to the source
while others argue that annotated classes are no longer POJOs and, furthermore, that the
configuration becomes decentralized and harder to control.
Spring provides comprehensive support for annotation-based configuration, operating on
metadata in the component class itself by using annotations on the relevant class,
method, or field declaration. As mentioned in
xref:core/beans/factory-extension.adoc#beans-factory-extension-bpp-examples-aabpp[Example: The `AutowiredAnnotationBeanPostProcessor`],
Spring uses `BeanPostProcessors` in conjunction with annotations to make the core IOC
container aware of specific annotations.
No matter the choice, Spring can accommodate both styles and even mix them together.
It is worth pointing out that through its xref:core/beans/java.adoc[JavaConfig] option, Spring lets
annotations be used in a non-invasive way, without touching the target components'
source code and that, in terms of tooling, all configuration styles are supported by
{spring-site-tools}[Spring Tools] for Eclipse, Visual Studio Code, and Theia.
****
An alternative to XML setup is provided by annotation-based configuration, which relies
on bytecode metadata for wiring up components instead of XML declarations. Instead of
using XML to describe a bean wiring, the developer moves the configuration into the
component class itself by using annotations on the relevant class, method, or field
declaration. As mentioned in xref:core/beans/factory-extension.adoc#beans-factory-extension-bpp-examples-aabpp[Example: The `AutowiredAnnotationBeanPostProcessor`], using a
`BeanPostProcessor` in conjunction with annotations is a common means of extending the
Spring IoC container. For example, the xref:core/beans/annotation-config/autowired.adoc[`@Autowired`]
annotation provides the same capabilities as described in xref:core/beans/dependencies/factory-autowire.adoc[Autowiring Collaborators] but
For example, the xref:core/beans/annotation-config/autowired.adoc[`@Autowired`]
annotation provides the same capabilities as described in
xref:core/beans/dependencies/factory-autowire.adoc[Autowiring Collaborators] but
with more fine-grained control and wider applicability. In addition, Spring provides
support for JSR-250 annotations, such as `@PostConstruct` and `@PreDestroy`, as well as
support for JSR-330 (Dependency Injection for Java) annotations contained in the
@@ -36,13 +19,16 @@ can be found in the xref:core/beans/standard-annotations.adoc[relevant section].
[NOTE]
====
Annotation injection is performed before XML injection. Thus, the XML configuration
overrides the annotations for properties wired through both approaches.
Annotation injection is performed before external property injection. Thus, external
configuration (e.g. XML-specified bean properties) effectively overrides the annotations
for properties when wired through mixed approaches.
====
As always, you can register the post-processors as individual bean definitions, but they
can also be implicitly registered by including the following tag in an XML-based Spring
configuration (notice the inclusion of the `context` namespace):
Technically, you can register the post-processors as individual bean definitions, but they
are implicitly registered in an `AnnotationConfigApplicationContext` already.
In an XML-based Spring setup, you may include the following configuration tag to enable
mixing and matching with annotation-based configuration:
[source,xml,indent=0,subs="verbatim,quotes"]
----
@@ -186,6 +186,15 @@ implementation type, consider declaring the most specific return type on your fa
method (at least as specific as required by the injection points referring to your bean).
====
[NOTE]
====
As of 4.3, `@Autowired` also considers self references for injection (that is, references
back to the bean that is currently injected). Note that self injection is a fallback.
In practice, you should use self references as a last resort only (for example, for
calling other methods on the same instance through the bean's transactional proxy).
Consider factoring out the affected methods to a separate delegate bean in such a scenario.
====
You can also instruct Spring to provide all beans of a particular type from the
`ApplicationContext` by adding the `@Autowired` annotation to a field or method that
expects an array of that type, as the following example shows:
@@ -268,6 +277,12 @@ use the same bean class). `@Order` values may influence priorities at injection
but be aware that they do not influence singleton startup order, which is an
orthogonal concern determined by dependency relationships and `@DependsOn` declarations.
Note that `@Order` annotations on configuration classes just influence the evaluation
order within the overall set of configuration classes on startup. Such configuration-level
order values do not affect the contained `@Bean` methods at all. For bean-level ordering,
each `@Bean` method needs to have its own `@Order` annotation which applies within a
set of multiple matches for the specific bean type (as returned by the factory method).
Note that the standard `jakarta.annotation.Priority` annotation is not available at the
`@Bean` level, since it cannot be declared on methods. Its semantics can be modeled
through `@Order` values in combination with `@Primary` on a single bean for each type.
@@ -2,36 +2,28 @@
= Container Overview
The `org.springframework.context.ApplicationContext` interface represents the Spring IoC
container and is responsible for instantiating, configuring, and assembling the
beans. The container gets its instructions on what objects to
instantiate, configure, and assemble by reading configuration metadata. The
configuration metadata is represented in XML, Java annotations, or Java code. It lets
you express the objects that compose your application and the rich interdependencies
between those objects.
container and is responsible for instantiating, configuring, and assembling the beans.
The container gets its instructions on the components to instantiate, configure, and
assemble by reading configuration metadata. The configuration metadata can be represented
as annotated component classes, configuration classes with factory methods, or external
XML files or Groovy scripts. With either format, you may compose your application and the
rich interdependencies between those components.
Several implementations of the `ApplicationContext` interface are supplied
with Spring. In stand-alone applications, it is common to create an
instance of
{spring-framework-api}/context/support/ClassPathXmlApplicationContext.html[`ClassPathXmlApplicationContext`]
or {spring-framework-api}/context/support/FileSystemXmlApplicationContext.html[`FileSystemXmlApplicationContext`].
While XML has been the traditional format for defining configuration metadata, you can
instruct the container to use Java annotations or code as the metadata format by
providing a small amount of XML configuration to declaratively enable support for these
additional metadata formats.
Several implementations of the `ApplicationContext` interface are part of core Spring.
In stand-alone applications, it is common to create an instance of
{spring-framework-api}/context/annotation/AnnotationConfigApplicationContext.html[`AnnotationConfigApplicationContext`]
or {spring-framework-api}/context/support/ClassPathXmlApplicationContext.html[`ClassPathXmlApplicationContext`].
In most application scenarios, explicit user code is not required to instantiate one or
more instances of a Spring IoC container. For example, in a web application scenario, a
simple eight (or so) lines of boilerplate web descriptor XML in the `web.xml` file
of the application typically suffices (see
more instances of a Spring IoC container. For example, in a plain web application scenario,
a simple boilerplate web descriptor XML in the `web.xml` file of the application suffices (see
xref:core/beans/context-introduction.adoc#context-create[Convenient ApplicationContext Instantiation for Web Applications]).
If you use the {spring-site-tools}[Spring Tools for Eclipse] (an Eclipse-powered
development environment), you can easily create this boilerplate configuration with a
few mouse clicks or keystrokes.
In a Spring Boot scenario, the application context is implicitly bootstrapped for you
based on common setup conventions.
The following diagram shows a high-level view of how Spring works. Your application classes
are combined with configuration metadata so that, after the `ApplicationContext` is
created and initialized, you have a fully configured and executable system or
application.
created and initialized, you have a fully configured and executable system or application.
.The Spring IoC container
image::container-magic.png[]
@@ -43,33 +35,25 @@ image::container-magic.png[]
As the preceding diagram shows, the Spring IoC container consumes a form of
configuration metadata. This configuration metadata represents how you, as an
application developer, tell the Spring container to instantiate, configure, and assemble
the objects in your application.
application developer, tell the Spring container to instantiate, configure,
and assemble the components in your application.
Configuration metadata is traditionally supplied in a simple and intuitive XML format,
which is what most of this chapter uses to convey key concepts and features of the
Spring IoC container.
NOTE: XML-based metadata is not the only allowed form of configuration metadata.
The Spring IoC container itself is totally decoupled from the format in which this
configuration metadata is actually written. These days, many developers choose
xref:core/beans/java.adoc[Java-based configuration] for their Spring applications.
For information about using other forms of metadata with the Spring container, see:
xref:core/beans/java.adoc[Java-based configuration] for their Spring applications:
* xref:core/beans/annotation-config.adoc[Annotation-based configuration]: define beans using
annotation-based configuration metadata.
annotation-based configuration metadata on your application's component classes.
* xref:core/beans/java.adoc[Java-based configuration]: define beans external to your application
classes by using Java rather than XML files. To use these features, see the
classes by using Java-based configuration classes. To use these features, see the
{spring-framework-api}/context/annotation/Configuration.html[`@Configuration`],
{spring-framework-api}/context/annotation/Bean.html[`@Bean`],
{spring-framework-api}/context/annotation/Import.html[`@Import`],
and {spring-framework-api}/context/annotation/DependsOn.html[`@DependsOn`] annotations.
Spring configuration consists of at least one and typically more than one bean
definition that the container must manage. XML-based configuration metadata configures these
beans as `<bean/>` elements inside a top-level `<beans/>` element. Java
configuration typically uses `@Bean`-annotated methods within a `@Configuration` class.
Spring configuration consists of at least one and typically more than one bean definition
that the container must manage. Java configuration typically uses `@Bean`-annotated
methods within a `@Configuration` class, each corresponding to one bean definition.
These bean definitions correspond to the actual objects that make up your application.
Typically, you define service layer objects, persistence layer objects such as
@@ -79,7 +63,14 @@ Typically, one does not configure fine-grained domain objects in the container,
it is usually the responsibility of repositories and business logic to create and load
domain objects.
The following example shows the basic structure of XML-based configuration metadata:
[[beans-factory-xml]]
=== XML as an External Configuration DSL
XML-based configuration metadata configures these beans as `<bean/>` elements inside
a top-level `<beans/>` element. The following example shows the basic structure of
XML-based configuration metadata:
[source,xml,indent=0,subs="verbatim,quotes"]
----
@@ -110,14 +101,9 @@ The value of the `id` attribute can be used to refer to collaborating objects. T
for referring to collaborating objects is not shown in this example. See
xref:core/beans/dependencies.adoc[Dependencies] for more information.
[[beans-factory-instantiation]]
== Instantiating a Container
The location path or paths
supplied to an `ApplicationContext` constructor are resource strings that let
the container load configuration metadata from a variety of external resources, such
For instantiating a container, the location path or paths to the XML resource files
need to be supplied to a `ClassPathXmlApplicationContext` constructor that let the
container load configuration metadata from a variety of external resources, such
as the local file system, the Java `CLASSPATH`, and so on.
[tabs]
@@ -209,9 +195,9 @@ xref:core/beans/dependencies.adoc[Dependencies].
It can be useful to have bean definitions span multiple XML files. Often, each individual
XML configuration file represents a logical layer or module in your architecture.
You can use the application context constructor to load bean definitions from all these
You can use the `ClassPathXmlApplicationContext` constructor to load bean definitions from
XML fragments. This constructor takes multiple `Resource` locations, as was shown in the
xref:core/beans/basics.adoc#beans-factory-instantiation[previous section]. Alternatively,
xref:core/beans/basics.adoc#beans-factory-xml[previous section]. Alternatively,
use one or more occurrences of the `<import/>` element to load bean definitions from
another file or files. The following example shows how to do so:
@@ -259,7 +245,7 @@ configuration features beyond plain bean definitions are available in a selectio
of XML namespaces provided by Spring -- for example, the `context` and `util` namespaces.
[[groovy-bean-definition-dsl]]
[[beans-factory-groovy]]
=== The Groovy Bean Definition DSL
As a further example for externalized configuration metadata, bean definitions can also
@@ -420,4 +406,3 @@ a dependency on a specific bean through metadata (such as an autowiring annotati
@@ -234,6 +234,10 @@ For details about the mechanism for supplying arguments to the constructor (if r
and setting object instance properties after the object is constructed, see
xref:core/beans/dependencies/factory-collaborators.adoc[Injecting Dependencies].
NOTE: In the case of constructor arguments, the container can select a corresponding
constructor among several overloaded constructors. That said, to avoid ambiguities,
it is recommended to keep your constructor signatures as straightforward as possible.
[[beans-factory-class-static-factory-method]]
=== Instantiation with a Static Factory Method
@@ -294,6 +298,24 @@ For details about the mechanism for supplying (optional) arguments to the factor
and setting object instance properties after the object is returned from the factory,
see xref:core/beans/dependencies/factory-properties-detailed.adoc[Dependencies and Configuration in Detail].
NOTE: In the case of factory method arguments, the container can select a corresponding
method among several overloaded methods of the same name. That said, to avoid ambiguities,
it is recommended to keep your factory method signatures as straightforward as possible.
[TIP]
====
A typical problematic case with factory method overloading is Mockito with its many
overloads of the `mock` method. Choose the most specific variant of `mock` possible:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<bean id="clientService" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg type="java.lang.Class" value="examples.ClientService"/>
<constructor-arg type="java.lang.String" value="clientService"/>
</bean>
----
====
[[beans-factory-class-instance-factory-method]]
=== Instantiation by Using an Instance Factory Method
@@ -416,8 +438,8 @@ Kotlin::
======
This approach shows that the factory bean itself can be managed and configured through
dependency injection (DI). See xref:core/beans/dependencies/factory-properties-detailed.adoc[Dependencies and Configuration in Detail]
.
dependency injection (DI).
See xref:core/beans/dependencies/factory-properties-detailed.adoc[Dependencies and Configuration in Detail].
NOTE: In Spring documentation, "factory bean" refers to a bean that is configured in the
Spring container and that creates objects through an
@@ -444,5 +466,3 @@ cases into account and returns the type of object that a `BeanFactory.getBean` c
going to return for the same bean name.
@@ -55,33 +55,34 @@ The preceding `AppConfig` class is equivalent to the following Spring `<beans/>`
</beans>
----
.Full @Configuration vs "`lite`" @Bean mode?
.@Configuration classes with or without local calls between @Bean methods?
****
When `@Bean` methods are declared within classes that are not annotated with
`@Configuration`, they are referred to as being processed in a "`lite`" mode. Bean methods
declared on a bean that is not annotated with `@Configuration` are considered to be "`lite`",
with a different primary purpose of the containing class and a `@Bean` method
being a sort of bonus there. For example, service components may expose management views
to the container through an additional `@Bean` method on each applicable component class.
In such scenarios, `@Bean` methods are a general-purpose factory method mechanism.
Unlike full `@Configuration`, lite `@Bean` methods cannot declare inter-bean dependencies.
Instead, they operate on their containing component's internal state and, optionally, on
arguments that they may declare. Such a `@Bean` method should therefore not invoke other
`@Bean` methods. Each such method is literally only a factory method for a particular
bean reference, without any special runtime semantics. The positive side-effect here is
that no CGLIB subclassing has to be applied at runtime, so there are no limitations in
terms of class design (that is, the containing class may be `final` and so forth).
In common scenarios, `@Bean` methods are to be declared within `@Configuration` classes,
ensuring that "`full`" mode is always used and that cross-method references therefore
get redirected to the container's lifecycle management. This prevents the same
`@Bean` method from accidentally being invoked through a regular Java call, which helps
to reduce subtle bugs that can be hard to track down when operating in "`lite`" mode.
ensuring that full configuration class processing applies and that cross-method
references therefore get redirected to the container's lifecycle management.
This prevents the same `@Bean` method from accidentally being invoked through a regular
Java method call, which helps to reduce subtle bugs that can be hard to track down.
When `@Bean` methods are declared within classes that are not annotated with
`@Configuration` - or when `@Configuration(proxyBeanMethods=false)` is declared -,
they are referred to as being processed in a "lite" mode. In such scenarios,
`@Bean` methods are effectively a general-purpose factory method mechanism without
special runtime processing (that is, without generating a CGLIB subclass for it).
A custom Java call to such a method will not get intercepted by the container and
therefore behaves just like a regular method call, creating a new instance every time
rather than reusing an existing singleton (or scoped) instance for the given bean.
As a consequence, `@Bean` methods on classes without runtime proxying are not meant to
declare inter-bean dependencies at all. Instead, they are expected to operate on their
containing component's fields and, optionally, on arguments that a factory method may
declare in order to receive autowired collaborators. Such a `@Bean` method therefore
never needs to invoke other `@Bean` methods; every such call can be expressed through
a factory method argument instead. The positive side-effect here is that no CGLIB
subclassing has to be applied at runtime, reducing the overhead and the footprint.
****
The `@Bean` and `@Configuration` annotations are discussed in depth in the following sections.
First, however, we cover the various ways of creating a spring container by using
First, however, we cover the various ways of creating a Spring container by using
Java-based configuration.
@@ -74,10 +74,11 @@ Kotlin::
----
======
Used at the class level as above, the annotation indicates a default for all methods of
the declaring class (as well as its subclasses). Alternatively, each method can be
annotated individually. See xref:data-access/transaction/declarative/annotations.adoc#transaction-declarative-annotations-method-visibility[method visibility] for
further details on which methods Spring considers transactional. Note that a class-level
Used at the class level as above, the annotation indicates a default for all methods
of the declaring class (as well as its subclasses). Alternatively, each method can be
annotated individually. See
xref:data-access/transaction/declarative/annotations.adoc#transaction-declarative-annotations-method-visibility[method visibility]
for further details on which methods Spring considers transactional. Note that a class-level
annotation does not apply to ancestor classes up the class hierarchy; in such a scenario,
inherited methods need to be locally redeclared in order to participate in a
subclass-level annotation.
@@ -436,9 +437,10 @@ properties of the `@Transactional` annotation:
| Optional array of exception name patterns that must not cause rollback.
|===
TIP: See xref:data-access/transaction/declarative/rolling-back.adoc#transaction-declarative-rollback-rules[Rollback rules] for further details
on rollback rule semantics, patterns, and warnings regarding possible unintentional
matches for pattern-based rollback rules.
TIP: See
xref:data-access/transaction/declarative/rolling-back.adoc#transaction-declarative-rollback-rules[Rollback rules]
for further details on rollback rule semantics, patterns, and warnings
regarding possible unintentional matches for pattern-based rollback rules.
Currently, you cannot have explicit control over the name of a transaction, where 'name'
means the transaction name that appears in a transaction monitor and in logging output.
@@ -24,8 +24,8 @@ Vavr's `Try` method to trigger transaction rollbacks when it returns a 'Failure'
This allows you to handle functional-style errors using Try and have the transaction
automatically rolled back in case of a failure. For more information on Vavr's Try,
refer to the https://docs.vavr.io/#_try[official Vavr documentation].
Here's an example of how to use Vavr's Try with a transactional method:
[tabs]
======
Java::
@@ -42,6 +42,32 @@ Java::
----
======
As of Spring Framework 6.1, there is also special treatment of `CompletableFuture`
(and general `Future`) return values, triggering a rollback for such a handle if it
was exceptionally completed at the time of being returned from the original method.
This is intended for `@Async` methods where the actual method implementation may
need to comply with a `CompletableFuture` signature (auto-adapted to an actual
asynchronous handle for a call to the proxy by `@Async` processing at runtime),
preferring exposure in the returned handle rather than rethrowing an exception:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
@Transactional @Async
public CompletableFuture<String> myTransactionalMethod() {
try {
return CompletableFuture.completedFuture(delegate.myDataAccessOperation());
}
catch (DataAccessException ex) {
return CompletableFuture.failedFuture(ex);
}
}
----
======
Checked exceptions that are thrown from a transactional method do not result in a rollback
in the default configuration. You can configure exactly which `Exception` types mark a
transaction for rollback, including checked exceptions by specifying _rollback rules_.
@@ -71,8 +71,34 @@ This can be done with `method(HttpMethod)` or with the convenience methods `get(
Next, the request URI can be specified with the `uri` methods.
This step is optional and can be skipped if the `RestClient` is configured with a default URI.
The URL is typically specified as a `String`, with optional URI template variables.
String URLs are encoded by default, but this can be changed by building a client with a custom `uriBuilderFactory`.
The following example configures a GET request to `https://example.com/orders/42`:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
int id = 42;
restClient.get()
.uri("https://example.com/orders/{id}", id)
....
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val id = 42
restClient.get()
.uri("https://example.com/orders/{id}", id)
...
----
======
A function can also be used for more controls, such as specifying xref:web/webmvc/mvc-uri-building.adoc[request parameters].
String URLs are encoded by default, but this can be changed by building a client with a custom `uriBuilderFactory`.
The URL can also be provided with a function or as a `java.net.URI`, both of which are not encoded.
For more details on working with and encoding URIs, see xref:web/webmvc/mvc-uri-building.adoc[URI Links].
@@ -468,6 +494,9 @@ If no request factory is specified when the `RestClient` was built, it will use
Otherwise, if the `java.net.http` module is loaded, it will use Java's `HttpClient`.
Finally, it will resort to the simple default.
TIP: Note that the `SimpleClientHttpRequestFactory` may raise an exception when accessing the status of a response that represents an error (e.g. 401).
If this is an issue, use any of the alternative request factories.
[[rest-webclient]]
== `WebClient`
@@ -8,7 +8,7 @@ existing Java application.
The Spring Framework provides a dedicated `ApplicationContext` that supports a Groovy-based
Bean Definition DSL. For more details, see
xref:core/beans/basics.adoc#groovy-bean-definition-dsl[The Groovy Bean Definition DSL].
xref:core/beans/basics.adoc#beans-factory-groovy[The Groovy Bean Definition DSL].
Further support for Groovy, including beans written in Groovy, refreshable script beans,
and more is available in xref:languages/dynamic.adoc[Dynamic Language Support].
@@ -166,7 +166,7 @@ public class SampleConfiguration {
@Bean
@NotNull
public SampleBean sampleBean$demo_kotlin_internal_test() {
return new SampleBean();
return new SampleBean();
}
}
----
@@ -111,7 +111,8 @@ a mock service with Mockito:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<bean id="accountService" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.example.AccountService"/>
<constructor-arg type="java.lang.Class" value="org.example.AccountService"/>
<constructor-arg type="java.lang.String" value="accountService"/>
</bean>
----
@@ -1,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,38 +1,16 @@
[[spring-mvc-test-vs-streaming-response]]
= Streaming Responses
The best way to test streaming responses such as Server-Sent Events is through the
<<WebTestClient>> which can be used as a test client to connect to a `MockMvc` instance
to perform tests on Spring MVC controllers without a running server. For example:
You can use `WebTestClient` to test xref:testing/webtestclient.adoc#webtestclient-stream[streaming responses]
such as Server-Sent Events. However, `MockMvcWebTestClient` doesn't support infinite
streams because there is no way to cancel the server stream from the client side.
To test infinite streams, you'll need to
xref:testing/webtestclient.adoc#webtestclient-server-config[bind to] a running server,
or when using Spring Boot,
{spring-boot-docs}/spring-boot-features.html#boot-features-testing-spring-boot-applications-testing-with-running-server[test with a running server].
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
WebTestClient client = MockMvcWebTestClient.bindToController(new SseController()).build();
FluxExchangeResult<Person> exchangeResult = client.get()
.uri("/persons")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType("text/event-stream")
.returnResult(Person.class);
// Use StepVerifier from Project Reactor to test the streaming response
StepVerifier.create(exchangeResult.getResponseBody())
.expectNext(new Person("N0"), new Person("N1"), new Person("N2"))
.expectNextCount(4)
.consumeNextWith(person -> assertThat(person.getName()).endsWith("7"))
.thenCancel()
.verify();
----
======
`WebTestClient` can also connect to a live server and perform full end-to-end integration
tests. This is also supported in Spring Boot where you can
{spring-boot-docs}/spring-boot-features.html#boot-features-testing-spring-boot-applications-testing-with-running-server[test a running server].
`MockMvcWebTestClient` does support asynchronous responses, and even streaming responses.
The limitation is that it can't influence the server to stop, and therefore the server
must finish writing the response on its own.
@@ -2,7 +2,7 @@
= Context Configuration with Groovy Scripts
To load an `ApplicationContext` for your tests by using Groovy scripts that use the
xref:core/beans/basics.adoc#groovy-bean-definition-dsl[Groovy Bean Definition DSL], you can annotate
xref:core/beans/basics.adoc#beans-factory-groovy[Groovy Bean Definition DSL], you can annotate
your test class with `@ContextConfiguration` and configure the `locations` or `value`
attribute with an array that contains the resource locations of Groovy scripts. Resource
lookup semantics for Groovy scripts are the same as those described for
@@ -672,13 +672,13 @@ Kotlin::
val result = client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectBody(Person.class)
.returnResult();
.expectBody<Person>()
.returnResult()
// For a response without a body
val result = client.get().uri("/path")
.exchange()
.expectBody().isEmpty();
.expectBody().isEmpty()
----
======
@@ -4,10 +4,13 @@
`spring-webflux` depends on `reactor-core` and uses it internally to compose asynchronous
logic and to provide Reactive Streams support. Generally, WebFlux APIs return `Flux` or
`Mono` (since those are used internally) and leniently accept any Reactive Streams
`Publisher` implementation as input. The use of `Flux` versus `Mono` is important, because
it helps to express cardinality -- for example, whether a single or multiple asynchronous
values are expected, and that can be essential for making decisions (for example, when
encoding or decoding HTTP messages).
`Publisher` implementation as input.
When a `Publisher` is provided, it can be treated only as a stream with unknown semantics (0..N).
If, however, the semantics are known, you should wrap it with `Flux` or `Mono.from(Publisher)` instead
of passing the raw `Publisher`.
The use of `Flux` versus `Mono` is important, because it helps to express cardinality --
for example, whether a single or multiple asynchronous values are expected,
and that can be essential for making decisions (for example, when encoding or decoding HTTP messages).
For annotated controllers, WebFlux transparently adapts to the reactive library chosen by
the application. This is done with the help of the
@@ -15,15 +18,3 @@ the application. This is done with the help of the
provides pluggable support for reactive library and other asynchronous types. The registry
has built-in support for RxJava 3, Kotlin coroutines and SmallRye Mutiny, but you can
register others, too.
For functional APIs (such as <<webflux-fn>>, the `WebClient`, and others), the general rules
for WebFlux APIs apply -- `Flux` and `Mono` as return values and a Reactive Streams
`Publisher` as input. When a `Publisher`, whether custom or from another reactive library,
is provided, it can be treated only as a stream with unknown semantics (0..N). If, however,
the semantics are known, you can wrap it with `Flux` or `Mono.from(Publisher)` instead
of passing the raw `Publisher`.
For example, given a `Publisher` that is not a `Mono`, the Jackson JSON message writer
expects multiple values. If the media type implies an infinite stream (for example,
`application/json+stream`), values are written and flushed individually. Otherwise,
values are buffered into a list and rendered as a JSON array.
@@ -5,14 +5,14 @@
A common requirement for REST services is to include details in the body of error
responses. The Spring Framework supports the "Problem Details for HTTP APIs"
specification, {rfc-site}/rfc7807.html[RFC 7807].
specification, {rfc-site}/rfc9457.html[RFC 9457].
The following are the main abstractions for this support:
- `ProblemDetail` -- representation for an RFC 7807 problem detail; a simple container
- `ProblemDetail` -- representation for an RFC 9457 problem detail; a simple container
for both standard fields defined in the spec, and for non-standard ones.
- `ErrorResponse` -- contract to expose HTTP error response details including HTTP
status, response headers, and a body in the format of RFC 7807; this allows exceptions to
status, response headers, and a body in the format of RFC 9457; this allows exceptions to
encapsulate and expose the details of how they map to an HTTP response. All Spring WebFlux
exceptions implement this.
- `ErrorResponseException` -- basic `ErrorResponse` implementation that others
@@ -28,7 +28,7 @@ and any `ErrorResponseException`, and renders an error response with a body.
[.small]#xref:web/webmvc/mvc-ann-rest-exceptions.adoc#mvc-ann-rest-exceptions-render[See equivalent in the Servlet stack]#
You can return `ProblemDetail` or `ErrorResponse` from any `@ExceptionHandler` or from
any `@RequestMapping` method to render an RFC 7807 response. This is processed as follows:
any `@RequestMapping` method to render an RFC 9457 response. This is processed as follows:
- The `status` property of `ProblemDetail` determines the HTTP status.
- The `instance` property of `ProblemDetail` is set from the current URL path, if not
@@ -37,7 +37,7 @@ already set.
"application/problem+json" over "application/json" when rendering a `ProblemDetail`,
and also falls back on it if no compatible media type is found.
To enable RFC 7807 responses for Spring WebFlux exceptions and for any
To enable RFC 9457 responses for Spring WebFlux exceptions and for any
`ErrorResponseException`, extend `ResponseEntityExceptionHandler` and declare it as an
xref:web/webflux/controller/ann-advice.adoc[@ControllerAdvice] in Spring configuration. The handler
has an `@ExceptionHandler` method that handles any `ErrorResponse` exception, which
@@ -50,7 +50,7 @@ use a protected method to map any exception to a `ProblemDetail`.
== Non-Standard Fields
[.small]#xref:web/webmvc/mvc-ann-rest-exceptions.adoc#mvc-ann-rest-exceptions-non-standard[See equivalent in the Servlet stack]#
You can extend an RFC 7807 response with non-standard fields in one of two ways.
You can extend an RFC 9457 response with non-standard fields in one of two ways.
One, insert into the "properties" `Map` of `ProblemDetail`. When using the Jackson
library, the Spring Framework registers `ProblemDetailJacksonMixin` that ensures this
@@ -4,9 +4,20 @@
[.small]#xref:web/webmvc/mvc-controller/ann-methods/return-types.adoc[See equivalent in the Servlet stack]#
The following table shows the supported controller method return values. Note that reactive
types from libraries such as Reactor, RxJava, xref:web-reactive.adoc#webflux-reactive-libraries[or other] are
types from libraries such as Reactor, RxJava, xref:web/webflux-reactive-libraries.adoc[or other] are
generally supported for all return values.
For return types like `Flux`, when multiple values are expected, elements are streamed as they come
and are not buffered. This is the default behavior, as keeping a potentially large amount of elements in memory
is not efficient. If the media type implies an infinite stream (for example,
`application/json+stream`), values are written and flushed individually. Otherwise,
values are written individually and the flushing happens separately.
NOTE: If an error happens while an element is encoded to JSON, the response might have been written to and committed already
and it is impossible at that point to render a proper error response.
In some cases, applications can choose to trade memory efficiency for better handling such errors by buffering elements and encoding them all at once.
Controllers can then return a `Flux<List<B>>`; Reactor provides a dedicated operator for that, `Flux#collectList()`.
[cols="1,2", options="header"]
|===
| Controller method return value | Description
@@ -24,11 +35,11 @@ generally supported for all return values.
| For returning a response with headers and no body.
| `ErrorResponse`
| To render an RFC 7807 error response with details in the body,
| To render an RFC 9457 error response with details in the body,
see xref:web/webflux/ann-rest-exceptions.adoc[Error Responses]
| `ProblemDetail`
| To render an RFC 7807 error response with details in the body,
| To render an RFC 9457 error response with details in the body,
see xref:web/webflux/ann-rest-exceptions.adoc[Error Responses]
| `String`
@@ -5,14 +5,14 @@
A common requirement for REST services is to include details in the body of error
responses. The Spring Framework supports the "Problem Details for HTTP APIs"
specification, {rfc-site}/rfc7807[RFC 7807].
specification, {rfc-site}/rfc9457[RFC 9457].
The following are the main abstractions for this support:
- `ProblemDetail` -- representation for an RFC 7807 problem detail; a simple container
- `ProblemDetail` -- representation for an RFC 9457 problem detail; a simple container
for both standard fields defined in the spec, and for non-standard ones.
- `ErrorResponse` -- contract to expose HTTP error response details including HTTP
status, response headers, and a body in the format of RFC 7807; this allows exceptions to
status, response headers, and a body in the format of RFC 9457; this allows exceptions to
encapsulate and expose the details of how they map to an HTTP response. All Spring MVC
exceptions implement this.
- `ErrorResponseException` -- basic `ErrorResponse` implementation that others
@@ -28,7 +28,7 @@ and any `ErrorResponseException`, and renders an error response with a body.
[.small]#xref:web/webflux/ann-rest-exceptions.adoc#webflux-ann-rest-exceptions-render[See equivalent in the Reactive stack]#
You can return `ProblemDetail` or `ErrorResponse` from any `@ExceptionHandler` or from
any `@RequestMapping` method to render an RFC 7807 response. This is processed as follows:
any `@RequestMapping` method to render an RFC 9457 response. This is processed as follows:
- The `status` property of `ProblemDetail` determines the HTTP status.
- The `instance` property of `ProblemDetail` is set from the current URL path, if not
@@ -37,7 +37,7 @@ already set.
"application/problem+json" over "application/json" when rendering a `ProblemDetail`,
and also falls back on it if no compatible media type is found.
To enable RFC 7807 responses for Spring WebFlux exceptions and for any
To enable RFC 9457 responses for Spring WebFlux exceptions and for any
`ErrorResponseException`, extend `ResponseEntityExceptionHandler` and declare it as an
xref:web/webmvc/mvc-controller/ann-advice.adoc[@ControllerAdvice] in Spring configuration. The handler
has an `@ExceptionHandler` method that handles any `ErrorResponse` exception, which
@@ -50,7 +50,7 @@ use a protected method to map any exception to a `ProblemDetail`.
== Non-Standard Fields
[.small]#xref:web/webflux/ann-rest-exceptions.adoc#webflux-ann-rest-exceptions-non-standard[See equivalent in the Reactive stack]#
You can extend an RFC 7807 response with non-standard fields in one of two ways.
You can extend an RFC 9457 response with non-standard fields in one of two ways.
One, insert into the "properties" `Map` of `ProblemDetail`. When using the Jackson
library, the Spring Framework registers `ProblemDetailJacksonMixin` that ensures this
@@ -52,14 +52,10 @@ The following example shows how to achieve the same configuration in XML:
</mvc:interceptors>
----
NOTE: Interceptors are not ideally suited as a security layer due to the potential
for a mismatch with annotated controller path matching, which can also match trailing
slashes and path extensions transparently, along with other path matching options. Many
of these options have been deprecated but the potential for a mismatch remains.
Generally, we recommend using Spring Security which includes a dedicated
https://docs.spring.io/spring-security/reference/servlet/integrations/mvc.html#mvc-requestmatcher[MvcRequestMatcher]
to align with Spring MVC path matching and also has a security firewall that blocks many
unwanted characters in URL paths.
WARNING: Interceptors are not ideally suited as a security layer due to the potential for
a mismatch with annotated controller path matching. Generally, we recommend using Spring
Security, or alternatively a similar approach integrated with the Servlet filter chain,
and applied as early as possible.
NOTE: The XML config declares interceptors as `MappedInterceptor` beans, and those are in
turn detected by any `HandlerMapping` bean, including those from other frameworks.
@@ -228,11 +228,11 @@ level, xref:web/webmvc/mvc-servlet/exceptionhandlers.adoc[HandlerExceptionResolv
See xref:web/webmvc/mvc-controller/ann-methods/responseentity.adoc[ResponseEntity].
| `ErrorResponse`
| To render an RFC 7807 error response with details in the body,
| To render an RFC 9457 error response with details in the body,
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
| `ProblemDetail`
| To render an RFC 7807 error response with details in the body,
| To render an RFC 9457 error response with details in the body,
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
| `String`
@@ -37,6 +37,13 @@ Kotlin::
all controller methods. This is the effect of `@RestController`, which is nothing more
than a meta-annotation marked with `@Controller` and `@ResponseBody`.
A `Resource` object can be returned for file content, copying the `InputStream`
content of the provided resource to the response `OutputStream`. Note that the
`InputStream` should be lazily retrieved by the `Resource` handle in order to reliably
close it after it has been copied to the response. If you are using `InputStreamResource`
for such a purpose, make sure to construct it with an on-demand `InputStreamSource`
(e.g. through a lambda expression that retrieves the actual `InputStream`).
You can use `@ResponseBody` with reactive types.
See xref:web/webmvc/mvc-ann-async.adoc[Asynchronous Requests] and xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-reactive-types[Reactive Types] for more details.
@@ -32,6 +32,16 @@ Kotlin::
----
======
The body will usually be provided as a value object to be rendered to a corresponding
response representation (e.g. JSON) by one of the registered `HttpMessageConverters`.
A `ResponseEntity<Resource>` can be returned for file content, copying the `InputStream`
content of the provided resource to the response `OutputStream`. Note that the
`InputStream` should be lazily retrieved by the `Resource` handle in order to reliably
close it after it has been copied to the response. If you are using `InputStreamResource`
for such a purpose, make sure to construct it with an on-demand `InputStreamSource`
(e.g. through a lambda expression that retrieves the actual `InputStream`).
Spring MVC supports using a single value xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-reactive-types[reactive type]
to produce the `ResponseEntity` asynchronously, and/or single and multi-value reactive
types for the body. This allows the following types of async responses:
@@ -23,11 +23,11 @@ supported for all return values.
| For returning a response with headers and no body.
| `ErrorResponse`
| To render an RFC 7807 error response with details in the body,
| To render an RFC 9457 error response with details in the body,
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
| `ProblemDetail`
| To render an RFC 7807 error response with details in the body,
| To render an RFC 9457 error response with details in the body,
see xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses]
| `String`
@@ -1,34 +1,29 @@
[[mvc-handlermapping-interceptor]]
= Interception
All `HandlerMapping` implementations support handler interceptors that are useful when
you want to apply specific functionality to certain requests -- for example, checking for
a principal. Interceptors must implement `HandlerInterceptor` from the
`org.springframework.web.servlet` package with three methods that should provide enough
flexibility to do all kinds of pre-processing and post-processing:
All `HandlerMapping` implementations support handler interception which is useful when
you want to apply functionality across requests. A `HandlerInterceptor` can implement the
following:
* `preHandle(..)`: Before the actual handler is run
* `postHandle(..)`: After the handler is run
* `afterCompletion(..)`: After the complete request has finished
* `preHandle(..)` -- callback before the actual handler is run that returns a boolean.
If the method returns `true`, execution continues; if it returns `false`, the rest of the
execution chain is bypassed and the handler is not called.
* `postHandle(..)` -- callback after the handler is run.
* `afterCompletion(..)` -- callback after the complete request has finished.
The `preHandle(..)` method returns a boolean value. You can use this method to break or
continue the processing of the execution chain. When this method returns `true`, the
handler execution chain continues. When it returns false, the `DispatcherServlet`
assumes the interceptor itself has taken care of requests (and, for example, rendered an
appropriate view) and does not continue executing the other interceptors and the actual
handler in the execution chain.
NOTE: For `@ResponseBody` and `ResponseEntity` controller methods, the response is written
and committed within the `HandlerAdapter`, before `postHandle` is called. That means it is
too late to change the response, such as to add an extra header. You can implement
`ResponseBodyAdvice` and declare it as an
xref:web/webmvc/mvc-controller/ann-advice.adoc[Controller Advice] bean or configure it
directly on `RequestMappingHandlerAdapter`.
See xref:web/webmvc/mvc-config/interceptors.adoc[Interceptors] in the section on MVC configuration for examples of how to
configure interceptors. You can also register them directly by using setters on individual
`HandlerMapping` implementations.
`postHandle` method is less useful with `@ResponseBody` and `ResponseEntity` methods for
which the response is written and committed within the `HandlerAdapter` and before
`postHandle`. That means it is too late to make any changes to the response, such as adding
an extra header. For such scenarios, you can implement `ResponseBodyAdvice` and either
declare it as an xref:web/webmvc/mvc-controller/ann-advice.adoc[Controller Advice] bean or configure it directly on
`RequestMappingHandlerAdapter`.
WARNING: Interceptors are not ideally suited as a security layer due to the potential for
a mismatch with annotated controller path matching. Generally, we recommend using Spring
Security, or alternatively a similar approach integrated with the Servlet filter chain,
and applied as early as possible.
+17 -17
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.5"))
api(platform("io.netty:netty-bom:4.1.108.Final"))
api(platform("io.micrometer:micrometer-bom:1.12.6"))
api(platform("io.netty:netty-bom:4.1.109.Final"))
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
api(platform("io.projectreactor:reactor-bom:2023.0.5"))
api(platform("io.projectreactor:reactor-bom:2023.0.6"))
api(platform("io.rsocket:rsocket-bom:1.1.3"))
api(platform("org.apache.groovy:groovy-bom:4.0.20"))
api(platform("org.apache.groovy:groovy-bom:4.0.21"))
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
api(platform("org.assertj:assertj-bom:3.25.3"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.7"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.7"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.9"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.9"))
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3"))
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.0"))
api(platform("org.junit:junit-bom:5.10.2"))
api(platform("org.mockito:mockito-bom:5.11.0"))
api(platform("org.mockito:mockito-bom:5.12.0"))
constraints {
api("com.fasterxml:aalto-xml:1.3.2")
@@ -55,9 +55,9 @@ dependencies {
api("io.r2dbc:r2dbc-spi:1.0.0.RELEASE")
api("io.reactivex.rxjava3:rxjava:3.1.8")
api("io.smallrye.reactive:mutiny:1.10.0")
api("io.undertow:undertow-core:2.3.12.Final")
api("io.undertow:undertow-servlet:2.3.12.Final")
api("io.undertow:undertow-websockets-jsr:2.3.12.Final")
api("io.undertow:undertow-core:2.3.13.Final")
api("io.undertow:undertow-servlet:2.3.13.Final")
api("io.undertow:undertow-websockets-jsr:2.3.13.Final")
api("io.vavr:vavr:0.10.4")
api("jakarta.activation:jakarta.activation-api:2.0.1")
api("jakarta.annotation:jakarta.annotation-api:2.0.0")
@@ -103,13 +103,13 @@ dependencies {
api("org.apache.httpcomponents.client5:httpclient5:5.3.1")
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.2.4")
api("org.apache.poi:poi-ooxml:5.2.5")
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.20")
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.20")
api("org.apache.tomcat:tomcat-util:10.1.20")
api("org.apache.tomcat:tomcat-websocket:10.1.20")
api("org.aspectj:aspectjrt:1.9.22")
api("org.aspectj:aspectjtools:1.9.22")
api("org.aspectj:aspectjweaver:1.9.22")
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.24")
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.24")
api("org.apache.tomcat:tomcat-util:10.1.24")
api("org.apache.tomcat:tomcat-websocket:10.1.24")
api("org.aspectj:aspectjrt:1.9.22.1")
api("org.aspectj:aspectjtools:1.9.22.1")
api("org.aspectj:aspectjweaver:1.9.22.1")
api("org.awaitility:awaitility:4.2.0")
api("org.bouncycastle:bcpkix-jdk18on:1.72")
api("org.codehaus.jettison:jettison:1.5.4")
+1 -1
View File
@@ -1,4 +1,4 @@
version=6.1.6-SNAPSHOT
version=6.1.7
org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m
+2
View File
@@ -6,6 +6,7 @@ tasks.findByName("dokkaHtmlPartial")?.configure {
classpath.from(sourceSets["main"].runtimeClasspath)
externalDocumentationLink {
url.set(new URL("https://docs.spring.io/spring-framework/docs/current/javadoc-api/"))
packageListUrl.set(new URL("https://docs.spring.io/spring-framework/docs/current/javadoc-api/element-list"))
}
externalDocumentationLink {
url.set(new URL("https://projectreactor.io/docs/core/release/api/"))
@@ -21,6 +22,7 @@ tasks.findByName("dokkaHtmlPartial")?.configure {
}
externalDocumentationLink {
url.set(new URL("https://javadoc.io/doc/jakarta.servlet/jakarta.servlet-api/latest/"))
packageListUrl.set(new URL("https://javadoc.io/doc/jakarta.servlet/jakarta.servlet-api/latest/element-list"))
}
externalDocumentationLink {
url.set(new URL("https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/"))
+3 -3
View File
@@ -1,6 +1,6 @@
plugins {
id "com.gradle.enterprise" version "3.16.2"
id "io.spring.ge.conventions" version "0.0.15"
id "com.gradle.develocity" version "3.17.2"
id "io.spring.ge.conventions" version "0.0.17"
id "org.gradle.toolchains.foojay-resolver-convention" version "0.7.0"
}
@@ -39,7 +39,7 @@ rootProject.children.each {project ->
}
settings.gradle.projectsLoaded {
gradleEnterprise {
develocity {
buildScan {
File buildDir = settings.gradle.rootProject
.getLayout().getBuildDirectory().getAsFile().get()
@@ -170,25 +170,30 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
@Override
public ClassFilter getClassFilter() {
obtainPointcutExpression();
checkExpression();
return this;
}
@Override
public MethodMatcher getMethodMatcher() {
obtainPointcutExpression();
checkExpression();
return this;
}
/**
* Check whether this pointcut is ready to match,
* lazily building the underlying AspectJ pointcut expression.
* Check whether this pointcut is ready to match.
*/
private PointcutExpression obtainPointcutExpression() {
private void checkExpression() {
if (getExpression() == null) {
throw new IllegalStateException("Must set property 'expression' before attempting to match");
}
}
/**
* Lazily build the underlying AspectJ pointcut expression.
*/
private PointcutExpression obtainPointcutExpression() {
if (this.pointcutExpression == null) {
this.pointcutClassLoader = determinePointcutClassLoader();
this.pointcutExpression = buildPointcutExpression(this.pointcutClassLoader);
@@ -265,10 +270,9 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
@Override
public boolean matches(Class<?> targetClass) {
PointcutExpression pointcutExpression = obtainPointcutExpression();
try {
try {
return pointcutExpression.couldMatchJoinPointsInType(targetClass);
return obtainPointcutExpression().couldMatchJoinPointsInType(targetClass);
}
catch (ReflectionWorldException ex) {
logger.debug("PointcutExpression matching rejected target class - trying fallback expression", ex);
@@ -279,6 +283,9 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
}
}
}
catch (IllegalArgumentException | IllegalStateException ex) {
throw ex;
}
catch (Throwable ex) {
logger.debug("PointcutExpression matching rejected target class", ex);
}
@@ -287,7 +294,6 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
@Override
public boolean matches(Method method, Class<?> targetClass, boolean hasIntroductions) {
obtainPointcutExpression();
ShadowMatch shadowMatch = getTargetShadowMatch(method, targetClass);
// Special handling for this, target, @this, @target, @annotation
@@ -325,7 +331,6 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
obtainPointcutExpression();
ShadowMatch shadowMatch = getTargetShadowMatch(method, targetClass);
// Bind Spring AOP proxy to AspectJ "this" and Spring AOP target to AspectJ target,
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@ package org.springframework.aop.aspectj.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.StringTokenizer;
@@ -56,8 +55,6 @@ import org.springframework.lang.Nullable;
*/
public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFactory {
private static final String AJC_MAGIC = "ajc$";
private static final Class<?>[] ASPECTJ_ANNOTATION_CLASSES = new Class<?>[] {
Pointcut.class, Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class};
@@ -68,37 +65,11 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
protected final ParameterNameDiscoverer parameterNameDiscoverer = new AspectJAnnotationParameterNameDiscoverer();
/**
* We consider something to be an AspectJ aspect suitable for use by the Spring AOP system
* if it has the @Aspect annotation, and was not compiled by ajc. The reason for this latter test
* is that aspects written in the code-style (AspectJ language) also have the annotation present
* when compiled by ajc with the -1.5 flag, yet they cannot be consumed by Spring AOP.
*/
@Override
public boolean isAspect(Class<?> clazz) {
return (hasAspectAnnotation(clazz) && !compiledByAjc(clazz));
}
private boolean hasAspectAnnotation(Class<?> clazz) {
return (AnnotationUtils.findAnnotation(clazz, Aspect.class) != null);
}
/**
* We need to detect this as "code-style" AspectJ aspects should not be
* interpreted by Spring AOP.
*/
static boolean compiledByAjc(Class<?> clazz) {
// The AJTypeSystem goes to great lengths to provide a uniform appearance between code-style and
// annotation-style aspects. Therefore there is no 'clean' way to tell them apart. Here we rely on
// an implementation detail of the AspectJ compiler.
for (Field field : clazz.getDeclaredFields()) {
if (field.getName().startsWith(AJC_MAGIC)) {
return true;
}
}
return false;
}
@Override
public void validate(Class<?> aspectClass) throws AopConfigException {
AjType<?> ajType = AjTypeSystem.getAjType(aspectClass);
@@ -115,6 +86,7 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
}
}
/**
* Find and return the first AspectJ annotation on the given method
* (there <i>should</i> only be one anyway...).
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
package org.springframework.aop.aspectj.annotation;
import java.lang.reflect.Field;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
@@ -34,6 +36,8 @@ import org.springframework.util.ClassUtils;
*/
class AspectJAdvisorBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor {
private static final String AJC_MAGIC = "ajc$";
private static final boolean aspectjPresent = ClassUtils.isPresent("org.aspectj.lang.annotation.Pointcut",
AspectJAdvisorBeanRegistrationAotProcessor.class.getClassLoader());
@@ -43,13 +47,22 @@ class AspectJAdvisorBeanRegistrationAotProcessor implements BeanRegistrationAotP
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
if (aspectjPresent) {
Class<?> beanClass = registeredBean.getBeanClass();
if (AbstractAspectJAdvisorFactory.compiledByAjc(beanClass)) {
if (compiledByAjc(beanClass)) {
return new AspectJAdvisorContribution(beanClass);
}
}
return null;
}
private static boolean compiledByAjc(Class<?> clazz) {
for (Field field : clazz.getDeclaredFields()) {
if (field.getName().startsWith(AJC_MAGIC)) {
return true;
}
}
return false;
}
private static class AspectJAdvisorContribution implements BeanRegistrationAotContribution {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -124,10 +124,16 @@ public class AspectMetadata implements Serializable {
* Extract contents from String of form {@code pertarget(contents)}.
*/
private String findPerClause(Class<?> aspectClass) {
String str = aspectClass.getAnnotation(Aspect.class).value();
int beginIndex = str.indexOf('(') + 1;
int endIndex = str.length() - 1;
return str.substring(beginIndex, endIndex);
Aspect ann = aspectClass.getAnnotation(Aspect.class);
if (ann == null) {
return "";
}
String value = ann.value();
int beginIndex = value.indexOf('(');
if (beginIndex < 0) {
return "";
}
return value.substring(beginIndex + 1, value.length() - 1);
}
@@ -369,8 +369,7 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
private void validateIntroductionAdvisor(IntroductionAdvisor advisor) {
advisor.validateInterfaces();
// If the advisor passed validation, we can make the change.
Class<?>[] ifcs = advisor.getInterfaces();
for (Class<?> ifc : ifcs) {
for (Class<?> ifc : advisor.getInterfaces()) {
addInterface(ifc);
}
}
@@ -23,8 +23,6 @@ import java.util.Map;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.aspectj.weaver.tools.PointcutPrimitive;
import org.aspectj.weaver.tools.UnsupportedPointcutPrimitiveException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import test.annotation.EmptySpringAnnotation;
@@ -41,7 +39,6 @@ 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;
@@ -63,7 +60,7 @@ class AspectJExpressionPointcutTests {
@BeforeEach
public void setup() throws NoSuchMethodException {
void setup() throws NoSuchMethodException {
getAge = TestBean.class.getMethod("getAge");
setAge = TestBean.class.getMethod("setAge", int.class);
setSomeNumber = TestBean.class.getMethod("setSomeNumber", Number.class);
@@ -174,7 +171,7 @@ class AspectJExpressionPointcutTests {
void testFriendlyErrorOnNoLocationClassMatching() {
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
assertThatIllegalStateException()
.isThrownBy(() -> pc.matches(ITestBean.class))
.isThrownBy(() -> pc.getClassFilter().matches(ITestBean.class))
.withMessageContaining("expression");
}
@@ -182,7 +179,7 @@ class AspectJExpressionPointcutTests {
void testFriendlyErrorOnNoLocation2ArgMatching() {
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
assertThatIllegalStateException()
.isThrownBy(() -> pc.matches(getAge, ITestBean.class))
.isThrownBy(() -> pc.getMethodMatcher().matches(getAge, ITestBean.class))
.withMessageContaining("expression");
}
@@ -190,13 +187,13 @@ class AspectJExpressionPointcutTests {
void testFriendlyErrorOnNoLocation3ArgMatching() {
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
assertThatIllegalStateException()
.isThrownBy(() -> pc.matches(getAge, ITestBean.class, (Object[]) null))
.isThrownBy(() -> pc.getMethodMatcher().matches(getAge, ITestBean.class, (Object[]) null))
.withMessageContaining("expression");
}
@Test
void testMatchWithArgs() throws Exception {
void testMatchWithArgs() {
String expression = "execution(void org.springframework.beans.testfixture.beans.TestBean.setSomeNumber(Number)) && args(Double)";
Pointcut pointcut = getPointcut(expression);
@@ -246,7 +243,7 @@ class AspectJExpressionPointcutTests {
@Test
void testInvalidExpression() {
String expression = "execution(void org.springframework.beans.testfixture.beans.TestBean.setSomeNumber(Number) && args(Double)";
assertThatIllegalArgumentException().isThrownBy(getPointcut(expression)::getClassFilter); // call to getClassFilter forces resolution
assertThatIllegalArgumentException().isThrownBy(() -> getPointcut(expression).getClassFilter().matches(Object.class));
}
private TestBean getAdvisedProxy(String pointcutExpression, CallCountingInterceptor interceptor) {
@@ -276,9 +273,7 @@ class AspectJExpressionPointcutTests {
@Test
void testWithUnsupportedPointcutPrimitive() {
String expression = "call(int org.springframework.beans.testfixture.beans.TestBean.getAge())";
assertThatExceptionOfType(UnsupportedPointcutPrimitiveException.class)
.isThrownBy(() -> getPointcut(expression).getClassFilter()) // call to getClassFilter forces resolution...
.satisfies(ex -> assertThat(ex.getUnsupportedPrimitive()).isEqualTo(PointcutPrimitive.CALL));
assertThat(getPointcut(expression).getClassFilter().matches(Object.class)).isFalse();
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@
package org.springframework.beans.factory.aot;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.function.Predicate;
@@ -35,6 +34,7 @@ import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.InstanceSupplier;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RegisteredBean.InstantiationDescriptor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.ResolvableType;
import org.springframework.javapoet.ClassName;
@@ -62,7 +62,7 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
private final BeanDefinitionMethodGeneratorFactory beanDefinitionMethodGeneratorFactory;
private final Supplier<Executable> constructorOrFactoryMethod;
private final Supplier<InstantiationDescriptor> instantiationDescriptor;
DefaultBeanRegistrationCodeFragments(BeanRegistrationsCode beanRegistrationsCode,
@@ -72,17 +72,18 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
this.beanRegistrationsCode = beanRegistrationsCode;
this.registeredBean = registeredBean;
this.beanDefinitionMethodGeneratorFactory = beanDefinitionMethodGeneratorFactory;
this.constructorOrFactoryMethod = SingletonSupplier.of(registeredBean::resolveConstructorOrFactoryMethod);
this.instantiationDescriptor = SingletonSupplier.of(registeredBean::resolveInstantiationDescriptor);
}
@Override
public ClassName getTarget(RegisteredBean registeredBean) {
if (hasInstanceSupplier()) {
throw new IllegalStateException("Default code generation is not supported for bean definitions "
+ "declaring an instance supplier callback: " + registeredBean.getMergedBeanDefinition());
String resourceDescription = registeredBean.getMergedBeanDefinition().getResourceDescription();
throw new IllegalStateException("Error processing bean with name '" + registeredBean.getBeanName() + "'" +
(resourceDescription != null ? " defined in " + resourceDescription : "") + ": instance supplier is not supported");
}
Class<?> target = extractDeclaringClass(registeredBean.getBeanType(), this.constructorOrFactoryMethod.get());
Class<?> target = extractDeclaringClass(registeredBean, this.instantiationDescriptor.get());
while (target.getName().startsWith("java.") && registeredBean.isInnerBean()) {
RegisteredBean parent = registeredBean.getParent();
Assert.state(parent != null, "No parent available for inner bean");
@@ -91,14 +92,14 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
return (target.isArray() ? ClassName.get(target.getComponentType()) : ClassName.get(target));
}
private Class<?> extractDeclaringClass(ResolvableType beanType, Executable executable) {
Class<?> declaringClass = ClassUtils.getUserClass(executable.getDeclaringClass());
if (executable instanceof Constructor<?>
&& AccessControl.forMember(executable).isPublic()
private Class<?> extractDeclaringClass(RegisteredBean registeredBean, InstantiationDescriptor instantiationDescriptor) {
Class<?> declaringClass = ClassUtils.getUserClass(instantiationDescriptor.targetClass());
if (instantiationDescriptor.executable() instanceof Constructor<?>
&& AccessControl.forMember(instantiationDescriptor.executable()).isPublic()
&& FactoryBean.class.isAssignableFrom(declaringClass)) {
return extractTargetClassFromFactoryBean(declaringClass, beanType);
return extractTargetClassFromFactoryBean(declaringClass, registeredBean.getBeanType());
}
return executable.getDeclaringClass();
return declaringClass;
}
/**
@@ -238,9 +239,9 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
throw new IllegalStateException("Default code generation is not supported for bean definitions declaring "
+ "an instance supplier callback: " + this.registeredBean.getMergedBeanDefinition());
}
return new InstanceSupplierCodeGenerator(generationContext,
beanRegistrationCode.getClassName(), beanRegistrationCode.getMethods(), allowDirectSupplierShortcut)
.generateCode(this.registeredBean, this.constructorOrFactoryMethod.get());
return new InstanceSupplierCodeGenerator(generationContext, beanRegistrationCode.getClassName(),
beanRegistrationCode.getMethods(), allowDirectSupplierShortcut).generateCode(
this.registeredBean, this.instantiationDescriptor.get());
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -46,6 +46,7 @@ import org.springframework.beans.factory.support.AutowireCandidateResolver;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.InstanceSupplier;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RegisteredBean.InstantiationDescriptor;
import org.springframework.core.KotlinDetector;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
@@ -120,14 +121,29 @@ public class InstanceSupplierCodeGenerator {
* @param registeredBean the bean to handle
* @param constructorOrFactoryMethod the executable to use to create the bean
* @return the generated code
* @deprecated in favor of {@link #generateCode(RegisteredBean, InstantiationDescriptor)}
*/
@Deprecated(since = "6.1.7")
public CodeBlock generateCode(RegisteredBean registeredBean, Executable constructorOrFactoryMethod) {
return generateCode(registeredBean, new InstantiationDescriptor(
constructorOrFactoryMethod, constructorOrFactoryMethod.getDeclaringClass()));
}
/**
* Generate the instance supplier code.
* @param registeredBean the bean to handle
* @param instantiationDescriptor the executable to use to create the bean
* @return the generated code
* @since 6.1.7
*/
public CodeBlock generateCode(RegisteredBean registeredBean, InstantiationDescriptor instantiationDescriptor) {
Executable constructorOrFactoryMethod = instantiationDescriptor.executable();
registerRuntimeHintsIfNecessary(registeredBean, constructorOrFactoryMethod);
if (constructorOrFactoryMethod instanceof Constructor<?> constructor) {
return generateCodeForConstructor(registeredBean, constructor);
}
if (constructorOrFactoryMethod instanceof Method method) {
return generateCodeForFactoryMethod(registeredBean, method);
return generateCodeForFactoryMethod(registeredBean, method, instantiationDescriptor.targetClass());
}
throw new IllegalStateException(
"No suitable executor found for " + registeredBean.getBeanName());
@@ -253,21 +269,21 @@ public class InstanceSupplierCodeGenerator {
declaringClass.getSimpleName(), args);
}
private CodeBlock generateCodeForFactoryMethod(RegisteredBean registeredBean, Method factoryMethod) {
private CodeBlock generateCodeForFactoryMethod(RegisteredBean registeredBean, Method factoryMethod, Class<?> targetClass) {
String beanName = registeredBean.getBeanName();
Class<?> declaringClass = ClassUtils.getUserClass(factoryMethod.getDeclaringClass());
Class<?> targetClassToUse = ClassUtils.getUserClass(targetClass);
boolean dependsOnBean = !Modifier.isStatic(factoryMethod.getModifiers());
Visibility accessVisibility = getAccessVisibility(registeredBean, factoryMethod);
if (accessVisibility != Visibility.PRIVATE) {
return generateCodeForAccessibleFactoryMethod(
beanName, factoryMethod, declaringClass, dependsOnBean);
beanName, factoryMethod, targetClassToUse, dependsOnBean);
}
return generateCodeForInaccessibleFactoryMethod(beanName, factoryMethod, declaringClass);
return generateCodeForInaccessibleFactoryMethod(beanName, factoryMethod, targetClassToUse);
}
private CodeBlock generateCodeForAccessibleFactoryMethod(String beanName,
Method factoryMethod, Class<?> declaringClass, boolean dependsOnBean) {
Method factoryMethod, Class<?> targetClass, boolean dependsOnBean) {
this.generationContext.getRuntimeHints().reflection().registerMethod(
factoryMethod, ExecutableMode.INTROSPECT);
@@ -276,20 +292,20 @@ public class InstanceSupplierCodeGenerator {
Class<?> suppliedType = ClassUtils.resolvePrimitiveIfNecessary(factoryMethod.getReturnType());
CodeBlock.Builder code = CodeBlock.builder();
code.add("$T.<$T>forFactoryMethod($T.class, $S)", BeanInstanceSupplier.class,
suppliedType, declaringClass, factoryMethod.getName());
suppliedType, targetClass, factoryMethod.getName());
code.add(".withGenerator(($L) -> $T.$L())", REGISTERED_BEAN_PARAMETER_NAME,
declaringClass, factoryMethod.getName());
targetClass, factoryMethod.getName());
return code.build();
}
GeneratedMethod getInstanceMethod = generateGetInstanceSupplierMethod(method ->
buildGetInstanceMethodForFactoryMethod(method, beanName, factoryMethod,
declaringClass, dependsOnBean, PRIVATE_STATIC));
targetClass, dependsOnBean, PRIVATE_STATIC));
return generateReturnStatement(getInstanceMethod);
}
private CodeBlock generateCodeForInaccessibleFactoryMethod(
String beanName, Method factoryMethod, Class<?> declaringClass) {
String beanName, Method factoryMethod, Class<?> targetClass) {
this.generationContext.getRuntimeHints().reflection().registerMethod(factoryMethod, ExecutableMode.INVOKE);
GeneratedMethod getInstanceMethod = generateGetInstanceSupplierMethod(method -> {
@@ -298,19 +314,19 @@ public class InstanceSupplierCodeGenerator {
method.addModifiers(PRIVATE_STATIC);
method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, suppliedType));
method.addStatement(generateInstanceSupplierForFactoryMethod(
factoryMethod, suppliedType, declaringClass, factoryMethod.getName()));
factoryMethod, suppliedType, targetClass, factoryMethod.getName()));
});
return generateReturnStatement(getInstanceMethod);
}
private void buildGetInstanceMethodForFactoryMethod(MethodSpec.Builder method,
String beanName, Method factoryMethod, Class<?> declaringClass,
String beanName, Method factoryMethod, Class<?> targetClass,
boolean dependsOnBean, javax.lang.model.element.Modifier... modifiers) {
String factoryMethodName = factoryMethod.getName();
Class<?> suppliedType = ClassUtils.resolvePrimitiveIfNecessary(factoryMethod.getReturnType());
CodeWarnings codeWarnings = new CodeWarnings();
codeWarnings.detectDeprecation(declaringClass, factoryMethod, suppliedType)
codeWarnings.detectDeprecation(targetClass, factoryMethod, suppliedType)
.detectDeprecation(Arrays.stream(factoryMethod.getParameters()).map(Parameter::getType));
method.addJavadoc("Get the bean instance supplier for '$L'.", beanName);
@@ -320,41 +336,41 @@ public class InstanceSupplierCodeGenerator {
CodeBlock.Builder code = CodeBlock.builder();
code.add(generateInstanceSupplierForFactoryMethod(
factoryMethod, suppliedType, declaringClass, factoryMethodName));
factoryMethod, suppliedType, targetClass, factoryMethodName));
boolean hasArguments = factoryMethod.getParameterCount() > 0;
CodeBlock arguments = hasArguments ?
new AutowiredArgumentsCodeGenerator(declaringClass, factoryMethod)
new AutowiredArgumentsCodeGenerator(targetClass, factoryMethod)
.generateCode(factoryMethod.getParameterTypes())
: NO_ARGS;
CodeBlock newInstance = generateNewInstanceCodeForMethod(
dependsOnBean, declaringClass, factoryMethodName, arguments);
dependsOnBean, targetClass, factoryMethodName, arguments);
code.add(generateWithGeneratorCode(hasArguments, newInstance));
method.addStatement(code.build());
}
private CodeBlock generateInstanceSupplierForFactoryMethod(Method factoryMethod,
Class<?> suppliedType, Class<?> declaringClass, String factoryMethodName) {
Class<?> suppliedType, Class<?> targetClass, String factoryMethodName) {
if (factoryMethod.getParameterCount() == 0) {
return CodeBlock.of("return $T.<$T>forFactoryMethod($T.class, $S)",
BeanInstanceSupplier.class, suppliedType, declaringClass, factoryMethodName);
BeanInstanceSupplier.class, suppliedType, targetClass, factoryMethodName);
}
CodeBlock parameterTypes = generateParameterTypesCode(factoryMethod.getParameterTypes(), 0);
return CodeBlock.of("return $T.<$T>forFactoryMethod($T.class, $S, $L)",
BeanInstanceSupplier.class, suppliedType, declaringClass, factoryMethodName, parameterTypes);
BeanInstanceSupplier.class, suppliedType, targetClass, factoryMethodName, parameterTypes);
}
private CodeBlock generateNewInstanceCodeForMethod(boolean dependsOnBean,
Class<?> declaringClass, String factoryMethodName, CodeBlock args) {
Class<?> targetClass, String factoryMethodName, CodeBlock args) {
if (!dependsOnBean) {
return CodeBlock.of("$T.$L($L)", declaringClass, factoryMethodName, args);
return CodeBlock.of("$T.$L($L)", targetClass, factoryMethodName, args);
}
return CodeBlock.of("$L.getBeanFactory().getBean($T.class).$L($L)",
REGISTERED_BEAN_PARAMETER_NAME, declaringClass, factoryMethodName, args);
REGISTERED_BEAN_PARAMETER_NAME, targetClass, factoryMethodName, args);
}
private CodeBlock generateReturnStatement(GeneratedMethod generatedMethod) {
@@ -822,11 +822,11 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
/**
* This implementation attempts to query the FactoryBean's generic parameter metadata
* if present to determine the object type. If not present, i.e. the FactoryBean is
* declared as a raw type, checks the FactoryBean's {@code getObjectType} method
* declared as a raw type, it checks the FactoryBean's {@code getObjectType} method
* on a plain instance of the FactoryBean, without bean properties applied yet.
* If this doesn't return a type yet, and {@code allowInit} is {@code true} a
* full creation of the FactoryBean is used as fallback (through delegation to the
* superclass's implementation).
* If this doesn't return a type yet and {@code allowInit} is {@code true}, full
* creation of the FactoryBean is attempted as fallback (through delegation to the
* superclass implementation).
* <p>The shortcut check for a FactoryBean is only applied in case of a singleton
* FactoryBean. If the FactoryBean instance itself is not kept as singleton,
* it will be fully created to check the type of its exposed object.
@@ -1156,9 +1156,11 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
"Bean class isn't public, and non-public access not allowed: " + beanClass.getName());
}
Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
if (instanceSupplier != null) {
return obtainFromSupplier(instanceSupplier, beanName, mbd);
if (args == null) {
Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
if (instanceSupplier != null) {
return obtainFromSupplier(instanceSupplier, beanName, mbd);
}
}
if (mbd.getFactoryMethodName() != null) {
@@ -316,6 +316,17 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
}
catch (BeanCreationException ex) {
if (requiredType != null) {
// Wrap exception with current bean metadata but only if specifically
// requested (indicated by required type), not for depends-on cascades.
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Failed to initialize dependency '" + ex.getBeanName() + "' of " +
requiredType.getSimpleName() + " bean '" + beanName + "': " +
ex.getMessage(), ex);
}
throw ex;
}
}
}
@@ -1087,7 +1098,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
@Override
public void setApplicationStartup(ApplicationStartup applicationStartup) {
Assert.notNull(applicationStartup, "applicationStartup must not be null");
Assert.notNull(applicationStartup, "ApplicationStartup must not be null");
this.applicationStartup = applicationStartup;
}
@@ -1686,7 +1697,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
* already. The implementation is allowed to instantiate the target factory bean if
* {@code allowInit} is {@code true} and the type cannot be determined another way;
* otherwise it is restricted to introspecting signatures and related metadata.
* <p>If no {@link FactoryBean#OBJECT_TYPE_ATTRIBUTE} if set on the bean definition
* <p>If no {@link FactoryBean#OBJECT_TYPE_ATTRIBUTE} is set on the bean definition
* and {@code allowInit} is {@code true}, the default implementation will create
* the FactoryBean via {@code getBean} to call its {@code getObjectType} method.
* Subclasses are encouraged to optimize this, typically by inspecting the generic
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,8 @@
package org.springframework.beans.factory.support;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Supplier;
@@ -41,6 +43,8 @@ import org.springframework.util.StringUtils;
* In the case of inner-beans, the bean name may have been generated.
*
* @author Phillip Webb
* @author Stephane Nicoll
* @author Juergen Hoeller
* @since 6.0
*/
public final class RegisteredBean {
@@ -206,12 +210,32 @@ public final class RegisteredBean {
/**
* Resolve the constructor or factory method to use for this bean.
* @return the {@link java.lang.reflect.Constructor} or {@link java.lang.reflect.Method}
* @deprecated in favor of {@link #resolveInstantiationDescriptor()}
*/
@Deprecated(since = "6.1.7")
public Executable resolveConstructorOrFactoryMethod() {
return new ConstructorResolver((AbstractAutowireCapableBeanFactory) getBeanFactory())
.resolveConstructorOrFactoryMethod(getBeanName(), getMergedBeanDefinition());
}
/**
* Resolve the {@linkplain InstantiationDescriptor descriptor} to use to
* instantiate this bean. It defines the {@link java.lang.reflect.Constructor}
* or {@link java.lang.reflect.Method} to use as well as additional metadata.
* @since 6.1.7
*/
public InstantiationDescriptor resolveInstantiationDescriptor() {
Executable executable = resolveConstructorOrFactoryMethod();
if (executable instanceof Method method && !Modifier.isStatic(method.getModifiers())) {
String factoryBeanName = getMergedBeanDefinition().getFactoryBeanName();
if (factoryBeanName != null && this.beanFactory.containsBean(factoryBeanName)) {
return new InstantiationDescriptor(executable,
this.beanFactory.getMergedBeanDefinition(factoryBeanName).getResolvableType().toClass());
}
}
return new InstantiationDescriptor(executable, executable.getDeclaringClass());
}
/**
* Resolve an autowired argument.
* @param descriptor the descriptor for the dependency (field/method/constructor)
@@ -238,6 +262,24 @@ public final class RegisteredBean {
}
/**
* Descriptor for how a bean should be instantiated. While the {@code targetClass}
* is usually the declaring class of the {@code executable} (in case of a constructor
* or a locally declared factory method), there are cases where retaining the actual
* concrete class is necessary (e.g. for an inherited factory method).
* @param executable the {@link Executable} ({@link java.lang.reflect.Constructor}
* or {@link java.lang.reflect.Method}) to invoke
* @param targetClass the target {@link Class} of the executable
* @since 6.1.7
*/
public record InstantiationDescriptor(Executable executable, Class<?> targetClass) {
public InstantiationDescriptor(Executable executable) {
this(executable, executable.getDeclaringClass());
}
}
/**
* Resolver used to obtain inner-bean details.
*/
@@ -1967,6 +1967,42 @@ class DefaultListableBeanFactoryTests {
lbf.getBean(TestBean.class, 67));
}
@Test
void getBeanByTypeInstanceWithConstructorIgnoresInstanceSupplier() {
RootBeanDefinition bd1 = createConstructorDependencyBeanDefinition(99);
bd1.setInstanceSupplier(() -> new ConstructorDependency(new TestBean("test")));
lbf.registerBeanDefinition("bd1", bd1);
ConstructorDependency defaultInstance = lbf.getBean(ConstructorDependency.class);
assertThat(defaultInstance.beanName).isEqualTo("bd1");
assertThat(defaultInstance.spouseAge).isEqualTo(0);
ConstructorDependency argsInstance = lbf.getBean(ConstructorDependency.class, 42);
assertThat(argsInstance.beanName).isEqualTo("bd1");
assertThat(argsInstance.spouseAge).isEqualTo(42);
}
@Test
void getBeanByTypeInstanceWithFactoryMethodIgnoresInstanceSupplier() {
RootBeanDefinition bd1 = new RootBeanDefinition(TestBean.class);
bd1.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bd1.setFactoryBeanName("config");
bd1.setFactoryMethodName("create");
bd1.setInstanceSupplier(() -> new TestBean("test"));
lbf.registerBeanDefinition("config", new RootBeanDefinition(BeanWithFactoryMethod.class));
lbf.registerBeanDefinition("bd1", bd1);
TestBean defaultInstance = lbf.getBean(TestBean.class);
assertThat(defaultInstance.getBeanName()).isEqualTo("bd1");
assertThat(defaultInstance.getName()).isEqualTo("test");
assertThat(defaultInstance.getAge()).isEqualTo(0);
TestBean argsInstance = lbf.getBean(TestBean.class, "another", 42);
assertThat(argsInstance.getBeanName()).isEqualTo("bd1");
assertThat(argsInstance.getName()).isEqualTo("another");
assertThat(argsInstance.getAge()).isEqualTo(42);
}
@Test
@SuppressWarnings("rawtypes")
void beanProviderSerialization() throws Exception {
@@ -3173,6 +3209,10 @@ class DefaultListableBeanFactoryTests {
return tb;
}
public TestBean create(String name, int age) {
return new TestBean(name, age);
}
public TestBean createWithArgs(String arg) {
TestBean tb = new TestBean();
tb.setName(arg);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -161,7 +161,8 @@ class BeanDefinitionMethodGeneratorTests {
@Test
void generateWithBeanClassAndFactoryMethodNameSetsTargetTypeAndBeanClass() {
this.beanFactory.registerSingleton("factory", new SimpleBeanConfiguration());
this.beanFactory.registerBeanDefinition("factory",
new RootBeanDefinition(SimpleBeanConfiguration.class));
RootBeanDefinition beanDefinition = new RootBeanDefinition(SimpleBean.class);
beanDefinition.setFactoryBeanName("factory");
beanDefinition.setFactoryMethodName("simpleBean");
@@ -182,7 +183,8 @@ class BeanDefinitionMethodGeneratorTests {
@Test
void generateWithTargetTypeAndFactoryMethodNameSetsOnlyBeanClass() {
this.beanFactory.registerSingleton("factory", new SimpleBeanConfiguration());
this.beanFactory.registerBeanDefinition("factory",
new RootBeanDefinition(SimpleBeanConfiguration.class));
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setTargetType(SimpleBean.class);
beanDefinition.setFactoryBeanName("factory");
@@ -690,8 +692,8 @@ class BeanDefinitionMethodGeneratorTests {
this.methodGeneratorFactory, registeredBean, null,
List.of());
assertThatIllegalStateException().isThrownBy(() -> generator.generateBeanDefinitionMethod(
this.generationContext, this.beanRegistrationsCode)).withMessageStartingWith(
"Default code generation is not supported for bean definitions declaring an instance supplier callback");
this.generationContext, this.beanRegistrationsCode)).withMessage(
"Error processing bean with name 'testBean': instance supplier is not supported");
}
@Test
@@ -727,8 +729,8 @@ class BeanDefinitionMethodGeneratorTests {
this.methodGeneratorFactory, registeredBean, null,
List.of(aotContribution));
assertThatIllegalStateException().isThrownBy(() -> generator.generateBeanDefinitionMethod(
this.generationContext, this.beanRegistrationsCode)).withMessageStartingWith(
"Default code generation is not supported for bean definitions declaring an instance supplier callback");
this.generationContext, this.beanRegistrationsCode)).withMessage(
"Error processing bean with name 'testBean': instance supplier is not supported");
}
@Test
@@ -31,6 +31,7 @@ import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.testfixture.beans.factory.DummyFactory;
import org.springframework.beans.testfixture.beans.factory.StringFactoryBean;
import org.springframework.beans.testfixture.beans.factory.aot.DefaultSimpleBeanContract;
import org.springframework.beans.testfixture.beans.factory.aot.GenericFactoryBean;
import org.springframework.beans.testfixture.beans.factory.aot.MockBeanRegistrationCode;
import org.springframework.beans.testfixture.beans.factory.aot.MockBeanRegistrationsCode;
@@ -38,6 +39,7 @@ import org.springframework.beans.testfixture.beans.factory.aot.NumberFactoryBean
import org.springframework.beans.testfixture.beans.factory.aot.SimpleBean;
import org.springframework.beans.testfixture.beans.factory.aot.SimpleBeanArrayFactoryBean;
import org.springframework.beans.testfixture.beans.factory.aot.SimpleBeanConfiguration;
import org.springframework.beans.testfixture.beans.factory.aot.SimpleBeanContract;
import org.springframework.beans.testfixture.beans.factory.aot.SimpleBeanFactoryBean;
import org.springframework.core.ResolvableType;
import org.springframework.javapoet.ClassName;
@@ -46,6 +48,7 @@ import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -63,6 +66,28 @@ class DefaultBeanRegistrationCodeFragmentsTests {
private final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
@Test
public void getTargetWithInstanceSupplier() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(SimpleBean.class);
beanDefinition.setInstanceSupplier(SimpleBean::new);
RegisteredBean registeredBean = registerTestBean(beanDefinition);
BeanRegistrationCodeFragments codeFragments = createInstance(registeredBean);
assertThatIllegalStateException().isThrownBy(() -> codeFragments.getTarget(registeredBean))
.withMessageContaining("Error processing bean with name 'testBean': instance supplier is not supported");
}
@Test
public void getTargetWithInstanceSupplierAndResourceDescription() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(SimpleBean.class);
beanDefinition.setInstanceSupplier(SimpleBean::new);
beanDefinition.setResourceDescription("my test resource");
RegisteredBean registeredBean = registerTestBean(beanDefinition);
BeanRegistrationCodeFragments codeFragments = createInstance(registeredBean);
assertThatIllegalStateException().isThrownBy(() -> codeFragments.getTarget(registeredBean))
.withMessageContaining("Error processing bean with name 'testBean' defined in my test resource: "
+ "instance supplier is not supported");
}
@Test
void getTargetOnConstructor() {
RegisteredBean registeredBean = registerTestBean(SimpleBean.class,
@@ -126,6 +151,21 @@ class DefaultBeanRegistrationCodeFragmentsTests {
SimpleBeanConfiguration.class);
}
@Test // gh-32609
void getTargetOnMethodFromInterface() {
this.beanFactory.registerBeanDefinition("configuration",
new RootBeanDefinition(DefaultSimpleBeanContract.class));
Method method = ReflectionUtils.findMethod(SimpleBeanContract.class, "simpleBean");
assertThat(method).isNotNull();
RootBeanDefinition beanDefinition = new RootBeanDefinition(SimpleBean.class);
applyConstructorOrFactoryMethod(beanDefinition, method);
beanDefinition.setFactoryBeanName("configuration");
this.beanFactory.registerBeanDefinition("testBean", beanDefinition);
RegisteredBean registeredBean = RegisteredBean.of(this.beanFactory, "testBean");
assertTarget(createInstance(registeredBean).getTarget(registeredBean),
DefaultSimpleBeanContract.class);
}
@Test
void getTargetOnMethodWithInnerBeanInJavaPackage() {
RegisteredBean registeredBean = registerTestBean(SimpleBean.class);
@@ -190,7 +230,7 @@ class DefaultBeanRegistrationCodeFragmentsTests {
}
@Test
void customizedGetTargetDoesNotResolveConstructorOrFactoryMethod() {
void customizedGetTargetDoesNotResolveInstantiationDescriptor() {
RegisteredBean registeredBean = spy(registerTestBean(SimpleBean.class));
BeanRegistrationCodeFragments customCodeFragments = createCustomCodeFragments(registeredBean, codeFragments -> new BeanRegistrationCodeFragmentsDecorator(codeFragments) {
@Override
@@ -199,11 +239,11 @@ class DefaultBeanRegistrationCodeFragmentsTests {
}
});
assertTarget(customCodeFragments.getTarget(registeredBean), String.class);
verify(registeredBean, never()).resolveConstructorOrFactoryMethod();
verify(registeredBean, never()).resolveInstantiationDescriptor();
}
@Test
void customizedGenerateInstanceSupplierCodeDoesNotResolveConstructorOrFactoryMethod() {
void customizedGenerateInstanceSupplierCodeDoesNotResolveInstantiationDescriptor() {
RegisteredBean registeredBean = spy(registerTestBean(SimpleBean.class));
BeanRegistrationCodeFragments customCodeFragments = createCustomCodeFragments(registeredBean, codeFragments -> new BeanRegistrationCodeFragmentsDecorator(codeFragments) {
@Override
@@ -214,7 +254,7 @@ class DefaultBeanRegistrationCodeFragmentsTests {
});
assertThat(customCodeFragments.generateInstanceSupplierCode(this.generationContext,
new MockBeanRegistrationCode(this.generationContext), false)).hasToString("// Hello");
verify(registeredBean, never()).resolveConstructorOrFactoryMethod();
verify(registeredBean, never()).resolveInstantiationDescriptor();
}
private BeanRegistrationCodeFragments createCustomCodeFragments(RegisteredBean registeredBean, UnaryOperator<BeanRegistrationCodeFragments> customFragments) {
@@ -240,13 +280,16 @@ class DefaultBeanRegistrationCodeFragmentsTests {
return RegisteredBean.of(this.beanFactory, "testBean");
}
private RegisteredBean registerTestBean(ResolvableType beanType,
@Nullable Executable constructorOrFactoryMethod) {
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setTargetType(beanType);
this.beanFactory.registerBeanDefinition("testBean",
applyConstructorOrFactoryMethod(beanDefinition, constructorOrFactoryMethod));
return registerTestBean(applyConstructorOrFactoryMethod(
beanDefinition, constructorOrFactoryMethod));
}
private RegisteredBean registerTestBean(RootBeanDefinition beanDefinition) {
this.beanFactory.registerBeanDefinition("testBean", beanDefinition);
return RegisteredBean.of(this.beanFactory, "testBean");
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@
package org.springframework.beans.factory.aot;
import java.lang.reflect.Executable;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
@@ -38,10 +37,14 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.InstanceSupplier;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RegisteredBean.InstantiationDescriptor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.beans.testfixture.beans.TestBeanWithPrivateConstructor;
import org.springframework.beans.testfixture.beans.factory.aot.DefaultSimpleBeanContract;
import org.springframework.beans.testfixture.beans.factory.aot.DeferredTypeBuilder;
import org.springframework.beans.testfixture.beans.factory.aot.SimpleBean;
import org.springframework.beans.testfixture.beans.factory.aot.SimpleBeanContract;
import org.springframework.beans.testfixture.beans.factory.generator.InnerComponentConfiguration;
import org.springframework.beans.testfixture.beans.factory.generator.InnerComponentConfiguration.EnvironmentAwareComponent;
import org.springframework.beans.testfixture.beans.factory.generator.InnerComponentConfiguration.NoDependencyComponent;
@@ -185,6 +188,23 @@ class InstanceSupplierCodeGeneratorTests {
.satisfies(hasMethodWithMode(ExecutableMode.INTROSPECT));
}
@Test
void generateWhenHasFactoryMethodOnInterface() {
BeanDefinition beanDefinition = BeanDefinitionBuilder
.rootBeanDefinition(SimpleBean.class)
.setFactoryMethodOnBean("simpleBean", "config").getBeanDefinition();
this.beanFactory.registerBeanDefinition("config", BeanDefinitionBuilder
.rootBeanDefinition(DefaultSimpleBeanContract.class).getBeanDefinition());
compile(beanDefinition, (instanceSupplier, compiled) -> {
Object bean = getBean(beanDefinition, instanceSupplier);
assertThat(bean).isInstanceOf(SimpleBean.class);
assertThat(compiled.getSourceFile()).contains(
"getBeanFactory().getBean(DefaultSimpleBeanContract.class).simpleBean()");
});
assertThat(getReflectionHints().getTypeHint(SimpleBeanContract.class))
.satisfies(hasMethodWithMode(ExecutableMode.INTROSPECT));
}
@Test
void generateWhenHasPrivateStaticFactoryMethodWithNoArg() {
BeanDefinition beanDefinition = BeanDefinitionBuilder
@@ -402,9 +422,9 @@ class InstanceSupplierCodeGeneratorTests {
InstanceSupplierCodeGenerator generator = new InstanceSupplierCodeGenerator(
this.generationContext, generateClass.getName(),
generateClass.getMethods(), false);
Executable constructorOrFactoryMethod = registeredBean.resolveConstructorOrFactoryMethod();
assertThat(constructorOrFactoryMethod).isNotNull();
CodeBlock generatedCode = generator.generateCode(registeredBean, constructorOrFactoryMethod);
InstantiationDescriptor instantiationDescriptor = registeredBean.resolveInstantiationDescriptor();
assertThat(instantiationDescriptor).isNotNull();
CodeBlock generatedCode = generator.generateCode(registeredBean, instantiationDescriptor);
typeBuilder.set(type -> {
type.addModifiers(Modifier.PUBLIC);
type.addSuperinterface(ParameterizedTypeName.get(Supplier.class, InstanceSupplier.class));
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -116,9 +116,9 @@ class InstanceSupplierCodeGeneratorKotlinTests {
generationContext, generateClass.name,
generateClass.methods, false
)
val constructorOrFactoryMethod = registeredBean.resolveConstructorOrFactoryMethod()
Assertions.assertThat(constructorOrFactoryMethod).isNotNull()
val generatedCode = generator.generateCode(registeredBean, constructorOrFactoryMethod)
val instantiationDescriptor = registeredBean.resolveInstantiationDescriptor()
Assertions.assertThat(instantiationDescriptor).isNotNull()
val generatedCode = generator.generateCode(registeredBean, instantiationDescriptor)
typeBuilder.set { type: TypeSpec.Builder ->
type.addModifiers(Modifier.PUBLIC)
type.addSuperinterface(
@@ -0,0 +1,25 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.testfixture.beans.factory.aot;
public class DefaultSimpleBeanContract implements SimpleBeanContract {
public SimpleBean anotherSimpleBean() {
return new SimpleBean();
}
}
@@ -0,0 +1,30 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.testfixture.beans.factory.aot;
/**
* Showcase a factory method that is defined on an interface.
*
* @author Stephane Nicoll
*/
public interface SimpleBeanContract {
default SimpleBean simpleBean() {
return new SimpleBean();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,7 +27,7 @@ import org.springframework.util.ClassUtils;
/**
* {@link RuntimeHintsRegistrar} implementation that makes sure {@link SchedulerFactoryBean}
* reflection entries are registered.
* reflection hints are registered.
*
* @author Sebastien Deleuze
* @author Stephane Nicoll
@@ -37,7 +37,7 @@ class SchedulerFactoryBeanRuntimeHints implements RuntimeHintsRegistrar {
private static final String SCHEDULER_FACTORY_CLASS_NAME = "org.quartz.impl.StdSchedulerFactory";
private final ReflectiveRuntimeHintsRegistrar reflectiveRegistrar = new ReflectiveRuntimeHintsRegistrar();
private static final ReflectiveRuntimeHintsRegistrar registrar = new ReflectiveRuntimeHintsRegistrar();
@Override
@@ -49,7 +49,7 @@ class SchedulerFactoryBeanRuntimeHints implements RuntimeHintsRegistrar {
.registerType(TypeReference.of(SCHEDULER_FACTORY_CLASS_NAME), this::typeHint)
.registerTypes(TypeReference.listOf(ResourceLoaderClassLoadHelper.class,
LocalTaskExecutorThreadPool.class, LocalDataSourceJobStore.class), this::typeHint);
this.reflectiveRegistrar.registerRuntimeHints(hints, LocalTaskExecutorThreadPool.class);
registrar.registerRuntimeHints(hints, LocalTaskExecutorThreadPool.class);
}
private void typeHint(Builder typeHint) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.aop.scope.ScopedProxyFactoryBean;
import org.springframework.asm.Opcodes;
import org.springframework.asm.Type;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
@@ -37,6 +38,7 @@ import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.support.SimpleInstantiationStrategy;
import org.springframework.cglib.core.ClassGenerator;
import org.springframework.cglib.core.ClassLoaderAwareGeneratorStrategy;
import org.springframework.cglib.core.CodeGenerationException;
import org.springframework.cglib.core.SpringNamingPolicy;
import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.CallbackFilter;
@@ -106,12 +108,19 @@ class ConfigurationClassEnhancer {
}
return configClass;
}
Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
if (logger.isTraceEnabled()) {
logger.trace(String.format("Successfully enhanced %s; enhanced class name is: %s",
configClass.getName(), enhancedClass.getName()));
try {
Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
if (logger.isTraceEnabled()) {
logger.trace(String.format("Successfully enhanced %s; enhanced class name is: %s",
configClass.getName(), enhancedClass.getName()));
}
return enhancedClass;
}
catch (CodeGenerationException ex) {
throw new BeanDefinitionStoreException("Could not enhance configuration class [" + configClass.getName() +
"]. Consider declaring @Configuration(proxyBeanMethods=false) without inter-bean references " +
"between @Bean methods on the configuration class, avoiding the need for CGLIB enhancement.", ex);
}
return enhancedClass;
}
/**
@@ -225,7 +234,6 @@ class ConfigurationClassEnhancer {
};
return new TransformingClassGenerator(cg, transformer);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -75,6 +75,7 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProce
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RegisteredBean.InstantiationDescriptor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationStartupAware;
import org.springframework.context.EnvironmentAware;
@@ -387,11 +388,11 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
});
// Detect any custom bean name generation strategy supplied through the enclosing application context
SingletonBeanRegistry sbr = null;
if (registry instanceof SingletonBeanRegistry _sbr) {
sbr = _sbr;
SingletonBeanRegistry singletonRegistry = null;
if (registry instanceof SingletonBeanRegistry sbr) {
singletonRegistry = sbr;
if (!this.localBeanNameGeneratorSet) {
BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
BeanNameGenerator generator = (BeanNameGenerator) singletonRegistry.getSingleton(
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
if (generator != null) {
this.componentScanBeanNameGenerator = generator;
@@ -452,8 +453,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
while (!candidates.isEmpty());
// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
if (singletonRegistry != null && !singletonRegistry.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
singletonRegistry.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
}
// Store the PropertySourceDescriptors to contribute them Ahead-of-time if necessary
@@ -552,6 +553,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
}
@Override
@Nullable
public PropertyValues postProcessProperties(@Nullable PropertyValues pvs, Object bean, String beanName) {
// Inject the BeanFactory before AutowiredAnnotationBeanPostProcessor's
// postProcessProperties method attempts to autowire other configuration beans.
@@ -647,9 +649,9 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
}
return mappings;
}
}
private static class PropertySourcesAotContribution implements BeanFactoryInitializationAotContribution {
private static final String ENVIRONMENT_VARIABLE = "environment";
@@ -761,17 +763,18 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
return nonNull.get();
}
}
}
private static class ConfigurationClassProxyBeanRegistrationCodeFragments extends BeanRegistrationCodeFragmentsDecorator {
private final RegisteredBean registeredBean;
private final Class<?> proxyClass;
public ConfigurationClassProxyBeanRegistrationCodeFragments(BeanRegistrationCodeFragments codeFragments,
RegisteredBean registeredBean) {
public ConfigurationClassProxyBeanRegistrationCodeFragments(
BeanRegistrationCodeFragments codeFragments, RegisteredBean registeredBean) {
super(codeFragments);
this.registeredBean = registeredBean;
this.proxyClass = registeredBean.getBeanType().toClass();
@@ -780,6 +783,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
@Override
public CodeBlock generateSetBeanDefinitionPropertiesCode(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode, RootBeanDefinition beanDefinition, Predicate<String> attributeFilter) {
CodeBlock.Builder code = CodeBlock.builder();
code.add(super.generateSetBeanDefinitionPropertiesCode(generationContext,
beanRegistrationCode, beanDefinition, attributeFilter));
@@ -790,29 +794,29 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
@Override
public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode,
boolean allowDirectSupplierShortcut) {
BeanRegistrationCode beanRegistrationCode, boolean allowDirectSupplierShortcut) {
Executable executableToUse = proxyExecutable(generationContext.getRuntimeHints(),
this.registeredBean.resolveConstructorOrFactoryMethod());
InstantiationDescriptor instantiationDescriptor = proxyInstantiationDescriptor(
generationContext.getRuntimeHints(), this.registeredBean.resolveInstantiationDescriptor());
return new InstanceSupplierCodeGenerator(generationContext,
beanRegistrationCode.getClassName(), beanRegistrationCode.getMethods(), allowDirectSupplierShortcut)
.generateCode(this.registeredBean, executableToUse);
.generateCode(this.registeredBean, instantiationDescriptor);
}
private Executable proxyExecutable(RuntimeHints runtimeHints, Executable userExecutable) {
private InstantiationDescriptor proxyInstantiationDescriptor(RuntimeHints runtimeHints, InstantiationDescriptor instantiationDescriptor) {
Executable userExecutable = instantiationDescriptor.executable();
if (userExecutable instanceof Constructor<?> userConstructor) {
try {
runtimeHints.reflection().registerConstructor(userConstructor, ExecutableMode.INTROSPECT);
return this.proxyClass.getConstructor(userExecutable.getParameterTypes());
Constructor<?> constructor = this.proxyClass.getConstructor(userExecutable.getParameterTypes());
return new InstantiationDescriptor(constructor);
}
catch (NoSuchMethodException ex) {
throw new IllegalStateException("No matching constructor found on proxy " + this.proxyClass, ex);
}
}
return userExecutable;
return instantiationDescriptor;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,7 +39,8 @@ import org.springframework.beans.factory.support.RegisteredBean;
*/
class ReflectiveProcessorBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor {
private static final ReflectiveRuntimeHintsRegistrar REGISTRAR = new ReflectiveRuntimeHintsRegistrar();
private static final ReflectiveRuntimeHintsRegistrar registrar = new ReflectiveRuntimeHintsRegistrar();
@Override
public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
@@ -49,7 +50,9 @@ class ReflectiveProcessorBeanFactoryInitializationAotProcessor implements BeanFa
return new ReflectiveProcessorBeanFactoryInitializationAotContribution(beanTypes);
}
private static class ReflectiveProcessorBeanFactoryInitializationAotContribution implements BeanFactoryInitializationAotContribution {
private static class ReflectiveProcessorBeanFactoryInitializationAotContribution
implements BeanFactoryInitializationAotContribution {
private final Class<?>[] types;
@@ -60,9 +63,8 @@ class ReflectiveProcessorBeanFactoryInitializationAotProcessor implements BeanFa
@Override
public void applyTo(GenerationContext generationContext, BeanFactoryInitializationCode beanFactoryInitializationCode) {
RuntimeHints runtimeHints = generationContext.getRuntimeHints();
REGISTRAR.registerRuntimeHints(runtimeHints, this.types);
registrar.registerRuntimeHints(runtimeHints, this.types);
}
}
}
@@ -949,7 +949,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
// Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
for (String weaverAwareName : weaverAwareNames) {
getBean(weaverAwareName);
beanFactory.getBean(weaverAwareName, LoadTimeWeaverAware.class);
}
// Stop using the temporary ClassLoader for type matching.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -126,6 +126,7 @@ public abstract class AbstractRefreshableApplicationContext extends AbstractAppl
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
beanFactory.setApplicationStartup(getApplicationStartup());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
this.beanFactory = beanFactory;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.context.support;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
@@ -428,16 +429,37 @@ public class GenericApplicationContext extends AbstractApplicationContext implem
PostProcessorRegistrationDelegate.loadBeanPostProcessors(
this.beanFactory, SmartInstantiationAwareBeanPostProcessor.class);
List<String> lazyBeans = new ArrayList<>();
// First round: non-lazy singleton beans in definition order,
// matching preInstantiateSingletons.
for (String beanName : this.beanFactory.getBeanDefinitionNames()) {
Class<?> beanType = this.beanFactory.getType(beanName);
if (beanType != null) {
ClassHintUtils.registerProxyIfNecessary(beanType, runtimeHints);
for (SmartInstantiationAwareBeanPostProcessor bpp : bpps) {
Class<?> newBeanType = bpp.determineBeanType(beanType, beanName);
if (newBeanType != beanType) {
ClassHintUtils.registerProxyIfNecessary(newBeanType, runtimeHints);
beanType = newBeanType;
}
BeanDefinition bd = getBeanDefinition(beanName);
if (bd.isSingleton() && !bd.isLazyInit()) {
preDetermineBeanType(beanName, bpps, runtimeHints);
}
else {
lazyBeans.add(beanName);
}
}
// Second round: lazy singleton beans and scoped beans.
for (String beanName : lazyBeans) {
preDetermineBeanType(beanName, bpps, runtimeHints);
}
}
private void preDetermineBeanType(String beanName, List<SmartInstantiationAwareBeanPostProcessor> bpps,
RuntimeHints runtimeHints) {
Class<?> beanType = this.beanFactory.getType(beanName);
if (beanType != null) {
ClassHintUtils.registerProxyIfNecessary(beanType, runtimeHints);
for (SmartInstantiationAwareBeanPostProcessor bpp : bpps) {
Class<?> newBeanType = bpp.determineBeanType(beanType, beanName);
if (newBeanType != beanType) {
ClassHintUtils.registerProxyIfNecessary(newBeanType, runtimeHints);
beanType = newBeanType;
}
}
}
@@ -411,7 +411,7 @@ public class ScheduledAnnotationBeanPostProcessor
}
catch (RuntimeException ex) {
throw new IllegalArgumentException(
"Invalid initialDelayString value \"" + initialDelayString + "\" - cannot parse into long");
"Invalid initialDelayString value \"" + initialDelayString + "\"; " + ex);
}
}
}
@@ -463,7 +463,7 @@ public class ScheduledAnnotationBeanPostProcessor
}
catch (RuntimeException ex) {
throw new IllegalArgumentException(
"Invalid fixedDelayString value \"" + fixedDelayString + "\" - cannot parse into long");
"Invalid fixedDelayString value \"" + fixedDelayString + "\"; " + ex);
}
tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, delayToUse)));
}
@@ -489,7 +489,7 @@ public class ScheduledAnnotationBeanPostProcessor
}
catch (RuntimeException ex) {
throw new IllegalArgumentException(
"Invalid fixedRateString value \"" + fixedRateString + "\" - cannot parse into long");
"Invalid fixedRateString value \"" + fixedRateString + "\"; " + ex);
}
tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, delayToUse)));
}
@@ -101,8 +101,8 @@ class ConfigurationClassPostProcessorAotContributionTests {
initializer.accept(freshBeanFactory);
freshContext.refresh();
assertThat(freshBeanFactory.getBeanPostProcessors()).filteredOn(ImportAwareAotBeanPostProcessor.class::isInstance)
.singleElement().satisfies(postProcessor -> assertPostProcessorEntry(postProcessor, ImportAwareConfiguration.class,
ImportConfiguration.class));
.singleElement().satisfies(postProcessor ->
assertPostProcessorEntry(postProcessor, ImportAwareConfiguration.class, ImportConfiguration.class));
freshContext.close();
});
}
@@ -245,6 +245,7 @@ class ConfigurationClassPostProcessorAotContributionTests {
}
}
@Nested
class PropertySourceTests {
@@ -410,9 +411,9 @@ class ConfigurationClassPostProcessorAotContributionTests {
@PropertySource("classpath:org/springframework/context/annotation/p?.properties")
static class PropertySourceWithWildcardLocationPatternConfiguration {
}
}
@Nested
class ConfigurationClassProxyTests {
@@ -432,15 +433,14 @@ class ConfigurationClassPostProcessorAotContributionTests {
getRegisteredBean(CglibConfiguration.class))).isNotNull();
}
private RegisteredBean getRegisteredBean(Class<?> bean) {
this.beanFactory.registerBeanDefinition("test", new RootBeanDefinition(bean));
this.processor.postProcessBeanFactory(this.beanFactory);
return RegisteredBean.of(this.beanFactory, "test");
}
}
@Nullable
private BeanFactoryInitializationAotContribution getContribution(Class<?>... types) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
@@ -458,8 +458,8 @@ class ConfigurationClassPostProcessorAotContributionTests {
.containsExactly(entry(key.getName(), value.getName()));
}
static class CustomPropertySourcesFactory extends DefaultPropertySourceFactory {
static class CustomPropertySourcesFactory extends DefaultPropertySourceFactory {
}
}
@@ -29,7 +29,9 @@ import org.springframework.beans.factory.support.AbstractBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.ResolvableType;
import org.springframework.core.type.AnnotationMetadata;
import static org.assertj.core.api.Assertions.assertThat;
@@ -39,8 +41,9 @@ import static org.assertj.core.api.Assertions.assertThat;
* {@link FactoryBean FactoryBeans} defined in the configuration.
*
* @author Phillip Webb
* @author Juergen Hoeller
*/
class ConfigurationWithFactoryBeanBeanEarlyDeductionTests {
class ConfigurationWithFactoryBeanEarlyDeductionTests {
@Test
void preFreezeDirect() {
@@ -82,6 +85,16 @@ class ConfigurationWithFactoryBeanBeanEarlyDeductionTests {
assertPostFreeze(AttributeClassConfiguration.class);
}
@Test
void preFreezeTargetType() {
assertPreFreeze(TargetTypeConfiguration.class);
}
@Test
void postFreezeTargetType() {
assertPostFreeze(TargetTypeConfiguration.class);
}
@Test
void preFreezeUnresolvedGenericFactoryBean() {
// Covers the case where a @Configuration is picked up via component scanning
@@ -105,14 +118,13 @@ class ConfigurationWithFactoryBeanBeanEarlyDeductionTests {
}
}
private void assertPostFreeze(Class<?> configurationClass) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
configurationClass);
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(configurationClass);
assertContainsMyBeanName(context);
}
private void assertPreFreeze(Class<?> configurationClass,
BeanFactoryPostProcessor... postProcessors) {
private void assertPreFreeze(Class<?> configurationClass, BeanFactoryPostProcessor... postProcessors) {
NameCollectingBeanFactoryPostProcessor postProcessor = new NameCollectingBeanFactoryPostProcessor();
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
try (context) {
@@ -132,41 +144,38 @@ class ConfigurationWithFactoryBeanBeanEarlyDeductionTests {
assertThat(names).containsExactly("myBean");
}
private static class NameCollectingBeanFactoryPostProcessor
implements BeanFactoryPostProcessor {
private static class NameCollectingBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
private String[] names;
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
this.names = beanFactory.getBeanNamesForType(MyBean.class, true, false);
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
ResolvableType typeToMatch = ResolvableType.forClassWithGenerics(MyBean.class, String.class);
this.names = beanFactory.getBeanNamesForType(typeToMatch, true, false);
}
public String[] getNames() {
return this.names;
}
}
@Configuration
static class DirectConfiguration {
@Bean
MyBean myBean() {
return new MyBean();
MyBean<String> myBean() {
return new MyBean<>();
}
}
@Configuration
static class GenericMethodConfiguration {
@Bean
FactoryBean<MyBean> myBean() {
return new TestFactoryBean<>(new MyBean());
FactoryBean<MyBean<String>> myBean() {
return new TestFactoryBean<>(new MyBean<>());
}
}
@Configuration
@@ -176,13 +185,11 @@ class ConfigurationWithFactoryBeanBeanEarlyDeductionTests {
MyFactoryBean myBean() {
return new MyFactoryBean();
}
}
@Configuration
@Import(AttributeClassRegistrar.class)
static class AttributeClassConfiguration {
}
static class AttributeClassRegistrar implements ImportBeanDefinitionRegistrar {
@@ -191,16 +198,32 @@ class ConfigurationWithFactoryBeanBeanEarlyDeductionTests {
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
BeanDefinition definition = BeanDefinitionBuilder.genericBeanDefinition(
RawWithAbstractObjectTypeFactoryBean.class).getBeanDefinition();
definition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, MyBean.class);
definition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE,
ResolvableType.forClassWithGenerics(MyBean.class, String.class));
registry.registerBeanDefinition("myBean", definition);
}
}
@Configuration
@Import(TargetTypeRegistrar.class)
static class TargetTypeConfiguration {
}
static class TargetTypeRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
RootBeanDefinition definition = new RootBeanDefinition(RawWithAbstractObjectTypeFactoryBean.class);
definition.setTargetType(ResolvableType.forClassWithGenerics(FactoryBean.class,
ResolvableType.forClassWithGenerics(MyBean.class, String.class)));
registry.registerBeanDefinition("myBean", definition);
}
}
abstract static class AbstractMyBean {
}
static class MyBean extends AbstractMyBean {
static class MyBean<T> extends AbstractMyBean {
}
static class TestFactoryBean<T> implements FactoryBean<T> {
@@ -220,31 +243,26 @@ class ConfigurationWithFactoryBeanBeanEarlyDeductionTests {
public Class<?> getObjectType() {
return this.instance.getClass();
}
}
static class MyFactoryBean extends TestFactoryBean<MyBean> {
static class MyFactoryBean extends TestFactoryBean<MyBean<String>> {
public MyFactoryBean() {
super(new MyBean());
super(new MyBean<>());
}
}
static class RawWithAbstractObjectTypeFactoryBean implements FactoryBean<Object> {
private final Object object = new MyBean();
@Override
public Object getObject() {
return object;
public Object getObject() throws Exception {
throw new IllegalStateException();
}
@Override
public Class<?> getObjectType() {
return MyBean.class;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -64,9 +64,9 @@ class KotlinApplicationListenerMethodAdapterTests {
private class StaticApplicationListenerMethodAdapter(method: Method, private val targetBean: Any) :
ApplicationListenerMethodAdapter("unused", targetBean.javaClass, method) {
public override fun getTargetBean(): Any {
return targetBean
return targetBean
}
}
}
@Suppress("RedundantSuspendModifier", "UNUSED_PARAMETER")
private class SampleEvents {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -124,7 +124,7 @@ class InstrumentedMethodTests {
}
@Test
void classGetConstructorShouldMatchInstrospectPublicConstructorsHint() {
void classGetConstructorShouldMatchIntrospectPublicConstructorsHint() {
hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_PUBLIC_CONSTRUCTORS);
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCONSTRUCTOR, this.stringGetConstructor);
}
@@ -148,7 +148,7 @@ class InstrumentedMethodTests {
}
@Test
void classGetConstructorShouldMatchInstrospectConstructorHint() {
void classGetConstructorShouldMatchIntrospectConstructorHint() {
hints.reflection().registerType(String.class,typeHint ->
typeHint.withConstructor(Collections.emptyList(), ExecutableMode.INTROSPECT));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCONSTRUCTOR, this.stringGetConstructor);
@@ -210,7 +210,7 @@ class InstrumentedMethodTests {
}
@Test
void classGetDeclaredConstructorShouldMatchInstrospectConstructorHint() {
void classGetDeclaredConstructorShouldMatchIntrospectConstructorHint() {
hints.reflection().registerType(String.class, typeHint ->
typeHint.withConstructor(TypeReference.listOf(byte[].class, byte.class), ExecutableMode.INTROSPECT));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDCONSTRUCTOR, this.stringGetDeclaredConstructor);
@@ -354,13 +354,13 @@ class InstrumentedMethodTests {
}
@Test
void classGetMethodsShouldMatchInstrospectDeclaredMethodsHint() {
void classGetMethodsShouldMatchIntrospectDeclaredMethodsHint() {
hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_DECLARED_METHODS);
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHODS, this.stringGetMethods);
}
@Test
void classGetMethodsShouldMatchInstrospectPublicMethodsHint() {
void classGetMethodsShouldMatchIntrospectPublicMethodsHint() {
hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_PUBLIC_METHODS);
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHODS, this.stringGetMethods);
}
@@ -396,7 +396,7 @@ class InstrumentedMethodTests {
}
@Test
void classGetMethodShouldMatchInstrospectPublicMethodsHint() {
void classGetMethodShouldMatchIntrospectPublicMethodsHint() {
hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_PUBLIC_METHODS);
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetToStringMethod);
}
@@ -434,13 +434,13 @@ class InstrumentedMethodTests {
}
@Test
void classGetMethodShouldNotMatchInstrospectPublicMethodsHintWhenPrivate() {
void classGetMethodShouldNotMatchIntrospectPublicMethodsHintWhenPrivate() {
hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_PUBLIC_METHODS);
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetScaleMethod);
}
@Test
void classGetMethodShouldMatchInstrospectDeclaredMethodsHintWhenPrivate() {
void classGetMethodShouldMatchIntrospectDeclaredMethodsHintWhenPrivate() {
hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_DECLARED_METHODS);
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetScaleMethod);
}
@@ -62,7 +62,7 @@ public class BindingReflectionHintsRegistrar {
* @param hints the hints instance to use
* @param types the types to register
*/
public void registerReflectionHints(ReflectionHints hints, @Nullable Type... types) {
public void registerReflectionHints(ReflectionHints hints, Type... types) {
Set<Type> seen = new LinkedHashSet<>();
for (Type type : types) {
registerReflectionHints(hints, seen, type);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,8 +39,7 @@ import org.springframework.core.annotation.AliasFor;
* @see ReflectiveRuntimeHintsRegistrar
* @see RegisterReflectionForBinding @RegisterReflectionForBinding
*/
@Target({ ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.CONSTRUCTOR,
ElementType.FIELD, ElementType.METHOD })
@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Reflective {
@@ -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,8 +77,7 @@ public @interface RegisterReflectionForBinding {
/**
* Classes for which reflection hints should be registered.
* <p>At least one class must be specified either via {@link #value} or
* {@link #classes}.
* <p>At least one class must be specified either via {@link #value} or {@code classes}.
* @see #value()
*/
@AliasFor("value")
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -48,8 +48,8 @@ class SpringFactoriesLoaderRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = (classLoader != null ? classLoader
: SpringFactoriesLoaderRuntimeHints.class.getClassLoader());
ClassLoader classLoaderToUse = (classLoader != null ? classLoader :
SpringFactoriesLoaderRuntimeHints.class.getClassLoader());
for (String resourceLocation : RESOURCE_LOCATIONS) {
registerHints(hints, classLoaderToUse, resourceLocation);
}
@@ -65,6 +65,7 @@ class SpringFactoriesLoaderRuntimeHints implements RuntimeHintsRegistrar {
private void registerHints(RuntimeHints hints, ClassLoader classLoader,
String factoryClassName, List<String> implementationClassNames) {
Class<?> factoryClass = resolveClassName(classLoader, factoryClassName);
if (factoryClass == null) {
if (logger.isTraceEnabled()) {
@@ -102,6 +103,7 @@ class SpringFactoriesLoaderRuntimeHints implements RuntimeHintsRegistrar {
}
}
private static class ExtendedSpringFactoriesLoader extends SpringFactoriesLoader {
ExtendedSpringFactoriesLoader(@Nullable ClassLoader classLoader, Map<String, List<String>> factories) {
@@ -111,7 +113,6 @@ class SpringFactoriesLoaderRuntimeHints implements RuntimeHintsRegistrar {
static Map<String, List<String>> accessLoadFactoriesResource(ClassLoader classLoader, String resourceLocation) {
return SpringFactoriesLoader.loadFactoriesResource(classLoader, resourceLocation);
}
}
}
@@ -75,7 +75,7 @@ public class ReflectUtils {
Throwable throwable = null;
try {
classLoaderDefineClass = ClassLoader.class.getDeclaredMethod("defineClass",
String.class, byte[].class, Integer.TYPE, Integer.TYPE, ProtectionDomain.class);
String.class, byte[].class, Integer.TYPE, Integer.TYPE, ProtectionDomain.class);
}
catch (Throwable t) {
classLoaderDefineClass = null;
@@ -544,7 +544,15 @@ public class ReflectUtils {
// No defineClass variant available at all?
if (c == null) {
throw new CodeGenerationException(t);
throw new CodeGenerationException(t) {
@Override
public String getMessage() {
return "No compatible defineClass mechanism detected: " +
"JVM should be started with --add-opens=java.base/java.lang=ALL-UNNAMED " +
"for ClassLoader.defineClass to be accessible. On the module path, " +
"you may not be able to define this CGLIB-generated class at all.";
}
};
}
// Force static initializers to run.
@@ -57,7 +57,7 @@ public class MethodProxy {
proxy.createInfo = new CreateInfo(c1, c2);
// SPRING PATCH BEGIN
if (!c1.isInterface() && c1 != Object.class && !Factory.class.isAssignableFrom(c2)) {
if (c1 != Object.class && c1.isAssignableFrom(c2.getSuperclass()) && !Factory.class.isAssignableFrom(c2)) {
// Try early initialization for overridden methods on specifically purposed subclasses
try {
proxy.init();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -418,7 +418,10 @@ final class TypeMappedAnnotations implements MergedAnnotations {
Annotation[] repeatedAnnotations = repeatableContainers.findRepeatedAnnotations(annotation);
if (repeatedAnnotations != null) {
return doWithAnnotations(type, aggregateIndex, source, repeatedAnnotations);
MergedAnnotation<A> result = doWithAnnotations(type, aggregateIndex, source, repeatedAnnotations);
if (result != null) {
return result;
}
}
AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(
annotation.annotationType(), repeatableContainers, annotationFilter);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -48,7 +48,7 @@ public class PropertiesPropertySource extends MapPropertySource {
@Override
public String[] getPropertyNames() {
synchronized (this.source) {
return super.getPropertyNames();
return ((Map<?, ?>) this.source).keySet().stream().filter(k -> k instanceof String).toArray(String[]::new);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,16 +23,22 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* {@link Resource} implementation for a given {@link InputStream}.
* {@link Resource} implementation for a given {@link InputStream} or a given
* {@link InputStreamSource} (which can be supplied as a lambda expression)
* for a lazy {@link InputStream} on demand.
*
* <p>Should only be used if no other specific {@code Resource} implementation
* is applicable. In particular, prefer {@link ByteArrayResource} or any of the
* file-based {@code Resource} implementations where possible.
* file-based {@code Resource} implementations if possible. If you need to obtain
* a custom stream multiple times, use a custom {@link AbstractResource} subclass
* with a corresponding {@code getInputStream()} implementation.
*
* <p>In contrast to other {@code Resource} implementations, this is a descriptor
* for an <i>already opened</i> resource - therefore returning {@code true} from
* {@link #isOpen()}. Do not use an {@code InputStreamResource} if you need to
* keep the resource descriptor somewhere, or if you need to read from a stream
* multiple times.
* {@link #isOpen()}. Do not use an {@code InputStreamResource} if you need to keep
* the resource descriptor somewhere, or if you need to read from a stream multiple
* times. This also applies when constructed with an {@code InputStreamSource}
* which lazily obtains the stream but only allows for single access as well.
*
* @author Juergen Hoeller
* @author Sam Brannen
@@ -44,30 +50,62 @@ import org.springframework.util.Assert;
*/
public class InputStreamResource extends AbstractResource {
private final InputStream inputStream;
private final InputStreamSource inputStreamSource;
private final String description;
private final Object equality;
private boolean read = false;
/**
* Create a new InputStreamResource.
* Create a new {@code InputStreamResource} with a lazy {@code InputStream}
* for single use.
* @param inputStreamSource an on-demand source for a single-use InputStream
* @since 6.1.7
*/
public InputStreamResource(InputStreamSource inputStreamSource) {
this(inputStreamSource, "resource loaded from InputStreamSource");
}
/**
* Create a new {@code InputStreamResource} with a lazy {@code InputStream}
* for single use.
* @param inputStreamSource an on-demand source for a single-use InputStream
* @param description where the InputStream comes from
* @since 6.1.7
*/
public InputStreamResource(InputStreamSource inputStreamSource, @Nullable String description) {
Assert.notNull(inputStreamSource, "InputStreamSource must not be null");
this.inputStreamSource = inputStreamSource;
this.description = (description != null ? description : "");
this.equality = inputStreamSource;
}
/**
* Create a new {@code InputStreamResource} for an existing {@code InputStream}.
* <p>Consider retrieving the InputStream on demand if possible, reducing its
* lifetime and reliably opening it and closing it through regular
* {@link InputStreamSource#getInputStream()} usage.
* @param inputStream the InputStream to use
* @see #InputStreamResource(InputStreamSource)
*/
public InputStreamResource(InputStream inputStream) {
this(inputStream, "resource loaded through InputStream");
}
/**
* Create a new InputStreamResource.
* Create a new {@code InputStreamResource} for an existing {@code InputStream}.
* @param inputStream the InputStream to use
* @param description where the InputStream comes from
* @see #InputStreamResource(InputStreamSource, String)
*/
public InputStreamResource(InputStream inputStream, @Nullable String description) {
Assert.notNull(inputStream, "InputStream must not be null");
this.inputStream = inputStream;
this.inputStreamSource = () -> inputStream;
this.description = (description != null ? description : "");
this.equality = inputStream;
}
@@ -98,7 +136,7 @@ public class InputStreamResource extends AbstractResource {
"do not use InputStreamResource if a stream needs to be read multiple times");
}
this.read = true;
return this.inputStream;
return this.inputStreamSource.getInputStream();
}
/**
@@ -117,7 +155,7 @@ public class InputStreamResource extends AbstractResource {
@Override
public boolean equals(@Nullable Object other) {
return (this == other || (other instanceof InputStreamResource that &&
this.inputStream.equals(that.inputStream)));
this.equality.equals(that.equality)));
}
/**
@@ -125,7 +163,7 @@ public class InputStreamResource extends AbstractResource {
*/
@Override
public int hashCode() {
return this.inputStream.hashCode();
return this.equality.hashCode();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -38,11 +38,12 @@ import java.io.InputStream;
* @see InputStreamResource
* @see ByteArrayResource
*/
@FunctionalInterface
public interface InputStreamSource {
/**
* Return an {@link InputStream} for the content of an underlying resource.
* <p>It is expected that each call creates a <i>fresh</i> stream.
* <p>It is usually expected that every such call creates a <i>fresh</i> stream.
* <p>This requirement is particularly important when you consider an API such
* as JavaMail, which needs to be able to read the stream multiple times when
* creating mail attachments. For such a use case, it is <i>required</i>
@@ -51,6 +52,7 @@ public interface InputStreamSource {
* @throws java.io.FileNotFoundException if the underlying resource does not exist
* @throws IOException if the content stream could not be opened
* @see Resource#isReadable()
* @see Resource#isOpen()
*/
InputStream getInputStream() throws IOException;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -49,6 +49,7 @@ public class UrlResource extends AbstractFileResolvingResource {
private static final String AUTHORIZATION = "Authorization";
/**
* Original URI, if available; used for URI and File access.
*/
@@ -310,7 +311,8 @@ public class UrlResource extends AbstractFileResolvingResource {
/**
* This delegate creates a {@code java.net.URL}, applying the given path
* relative to the path of the underlying URL of this resource descriptor.
* A leading slash will get dropped; a "#" symbol will get encoded.
* <p>A leading slash will get dropped; a "#" symbol will get encoded.
* Note that this method effectively cleans the combined path as of 6.1.
* @since 5.2
* @see #createRelative(String)
* @see ResourceUtils#toRelativeURL(URL, String)
@@ -423,6 +423,17 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
}
}
else {
String urlString = url.toString();
String cleanedPath = StringUtils.cleanPath(urlString);
if (!cleanedPath.equals(urlString)) {
// Prefer cleaned URL, aligned with UrlResource#createRelative(String)
try {
return new UrlResource(ResourceUtils.toURI(cleanedPath));
}
catch (URISyntaxException | MalformedURLException ex) {
// Fallback to regular URL construction below...
}
}
return new UrlResource(url);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -59,7 +59,7 @@ public class MultiValueMapAdapter<K, V> implements MultiValueMap<K, V>, Serializ
@Nullable
public V getFirst(K key) {
List<V> values = this.targetMap.get(key);
return (values != null && !values.isEmpty() ? values.get(0) : null);
return (!CollectionUtils.isEmpty(values) ? values.get(0) : null);
}
@Override
@@ -95,7 +95,7 @@ public class MultiValueMapAdapter<K, V> implements MultiValueMap<K, V>, Serializ
public Map<K, V> toSingleValueMap() {
Map<K, V> singleValueMap = CollectionUtils.newLinkedHashMap(this.targetMap.size());
this.targetMap.forEach((key, values) -> {
if (values != null && !values.isEmpty()) {
if (!CollectionUtils.isEmpty(values)) {
singleValueMap.put(key, values.get(0));
}
});
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,7 +34,7 @@ import org.springframework.lang.Nullable;
* <p>Consider using Spring's Resource abstraction in the core package
* for handling all kinds of file resources in a uniform manner.
* {@link org.springframework.core.io.ResourceLoader}'s {@code getResource()}
* method can resolve any location to an {@link org.springframework.core.io.Resource}
* method can resolve any location to a {@link org.springframework.core.io.Resource}
* object, which in turn allows one to obtain a {@code java.io.File} in the
* file system through its {@code getFile()} method.
*
@@ -101,6 +101,7 @@ public abstract class ResourceUtils {
* @return whether the location qualifies as a URL
* @see #CLASSPATH_URL_PREFIX
* @see java.net.URL
* @see #toURL(String)
*/
public static boolean isUrl(@Nullable String resourceLocation) {
if (resourceLocation == null) {
@@ -126,6 +127,7 @@ public abstract class ResourceUtils {
* "classpath:" pseudo URL, a "file:" URL, or a plain file path
* @return a corresponding URL object
* @throws FileNotFoundException if the resource cannot be resolved to a URL
* @see #toURL(String)
*/
public static URL getURL(String resourceLocation) throws FileNotFoundException {
Assert.notNull(resourceLocation, "Resource location must not be null");
@@ -166,6 +168,7 @@ public abstract class ResourceUtils {
* @return a corresponding File object
* @throws FileNotFoundException if the resource cannot be resolved to
* a file in the file system
* @see #getFile(URL)
*/
public static File getFile(String resourceLocation) throws FileNotFoundException {
Assert.notNull(resourceLocation, "Resource location must not be null");
@@ -197,6 +200,7 @@ public abstract class ResourceUtils {
* @return a corresponding File object
* @throws FileNotFoundException if the URL cannot be resolved to
* a file in the file system
* @see #getFile(URL, String)
*/
public static File getFile(URL resourceUrl) throws FileNotFoundException {
return getFile(resourceUrl, "URL");
@@ -237,6 +241,7 @@ public abstract class ResourceUtils {
* @throws FileNotFoundException if the URL cannot be resolved to
* a file in the file system
* @since 2.5
* @see #getFile(URI, String)
*/
public static File getFile(URI resourceUri) throws FileNotFoundException {
return getFile(resourceUri, "URI");
@@ -268,6 +273,7 @@ public abstract class ResourceUtils {
* i.e. has protocol "file", "vfsfile" or "vfs".
* @param url the URL to check
* @return whether the URL has been identified as a file system URL
* @see #isJarURL(URL)
*/
public static boolean isFileURL(URL url) {
String protocol = url.getProtocol();
@@ -281,6 +287,7 @@ public abstract class ResourceUtils {
* "vfszip", or "wsjar".
* @param url the URL to check
* @return whether the URL has been identified as a JAR URL
* @see #isJarFileURL(URL)
*/
public static boolean isJarURL(URL url) {
String protocol = url.getProtocol();
@@ -295,6 +302,7 @@ public abstract class ResourceUtils {
* @param url the URL to check
* @return whether the URL has been identified as a JAR file URL
* @since 4.1
* @see #extractJarFileURL(URL)
*/
public static boolean isJarFileURL(URL url) {
return (URL_PROTOCOL_FILE.equals(url.getProtocol()) &&
@@ -307,6 +315,7 @@ public abstract class ResourceUtils {
* @param jarUrl the original URL
* @return the URL for the actual jar file
* @throws MalformedURLException if no valid jar file URL could be extracted
* @see #extractArchiveURL(URL)
*/
public static URL extractJarFileURL(URL jarUrl) throws MalformedURLException {
String urlFile = jarUrl.getFile();
@@ -368,6 +377,7 @@ public abstract class ResourceUtils {
* @return the URI instance
* @throws URISyntaxException if the URL wasn't a valid URI
* @see java.net.URL#toURI()
* @see #toURI(String)
*/
public static URI toURI(URL url) throws URISyntaxException {
return toURI(url.toString());
@@ -379,18 +389,21 @@ public abstract class ResourceUtils {
* @param location the location String to convert into a URI instance
* @return the URI instance
* @throws URISyntaxException if the location wasn't a valid URI
* @see #toURI(URL)
*/
public static URI toURI(String location) throws URISyntaxException {
return new URI(StringUtils.replace(location, " ", "%20"));
}
/**
* Create a URL instance for the given location String,
* Create a clean URL instance for the given location String,
* going through URI construction and then URL conversion.
* @param location the location String to convert into a URL instance
* @return the URL instance
* @throws MalformedURLException if the location wasn't a valid URL
* @since 6.0
* @see java.net.URI#toURL()
* @see #toURI(String)
*/
@SuppressWarnings("deprecation") // on JDK 20
public static URL toURL(String location) throws MalformedURLException {
@@ -406,13 +419,15 @@ public abstract class ResourceUtils {
}
/**
* Create a URL instance for the given root URL and relative path,
* Create a clean URL instance for the given root URL and relative path,
* going through URI construction and then URL conversion.
* @param root the root URL to start from
* @param relativePath the relative path to apply
* @return the relative URL instance
* @throws MalformedURLException if the end result is not a valid URL
* @since 6.0
* @see #toURL(String)
* @see StringUtils#applyRelativePath
*/
public static URL toRelativeURL(URL root, String relativePath) throws MalformedURLException {
// # can appear in filenames, java.net.URL should not treat it as a fragment
@@ -426,6 +441,7 @@ public abstract class ResourceUtils {
* given connection, preferring {@code false} but leaving the flag at
* its JVM default value for jar resources (typically {@code true}).
* @param con the URLConnection to set the flag on
* @see URLConnection#setUseCaches
*/
public static void useCachesIfNecessary(URLConnection con) {
if (!(con instanceof JarURLConnection)) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -177,18 +177,13 @@ public abstract class StreamUtils {
long bytesToCopy = end - start + 1;
byte[] buffer = new byte[(int) Math.min(StreamUtils.BUFFER_SIZE, bytesToCopy)];
while (bytesToCopy > 0) {
int bytesRead = in.read(buffer);
int bytesRead = (bytesToCopy < buffer.length ? in.read(buffer, 0, (int) bytesToCopy) :
in.read(buffer));
if (bytesRead == -1) {
break;
}
else if (bytesRead <= bytesToCopy) {
out.write(buffer, 0, bytesRead);
bytesToCopy -= bytesRead;
}
else {
out.write(buffer, 0, (int) bytesToCopy);
bytesToCopy = 0;
}
out.write(buffer, 0, bytesRead);
bytesToCopy -= bytesRead;
}
return (end - start + 1 - bytesToCopy);
}
@@ -202,7 +197,9 @@ public abstract class StreamUtils {
* @since 4.3
*/
public static int drain(@Nullable InputStream in) throws IOException {
Assert.notNull(in, "No InputStream specified");
if (in == null) {
return 0;
}
return (int) in.transferTo(OutputStream.nullOutputStream());
}
@@ -123,7 +123,7 @@ public abstract class StringUtils {
* @see #hasText(CharSequence)
*/
public static boolean hasLength(@Nullable CharSequence str) {
return (str != null && str.length() > 0);
return (str != null && !str.isEmpty()); // as of JDK 15
}
/**
@@ -853,7 +853,7 @@ public abstract class StringUtils {
if (!localeValue.contains("_") && !localeValue.contains(" ")) {
validateLocalePart(localeValue);
Locale resolved = Locale.forLanguageTag(localeValue);
if (resolved.getLanguage().length() > 0) {
if (!resolved.getLanguage().isEmpty()) {
return resolved;
}
}
@@ -1182,7 +1182,7 @@ public abstract class StringUtils {
if (trimTokens) {
token = token.trim();
}
if (!ignoreEmptyTokens || token.length() > 0) {
if (!ignoreEmptyTokens || !token.isEmpty()) {
tokens.add(token);
}
}
@@ -1244,7 +1244,7 @@ public abstract class StringUtils {
result.add(deleteAny(str.substring(pos, delPos), charsToDelete));
pos = delPos + delimiter.length();
}
if (str.length() > 0 && pos <= str.length()) {
if (!str.isEmpty() && pos <= str.length()) {
// Add rest of String, but not in case of empty input.
result.add(deleteAny(str.substring(pos), charsToDelete));
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.AnnotatedElement;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Stream;
@@ -35,6 +36,8 @@ import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
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.springframework.core.annotation.MergedAnnotations.SearchStrategy.INHERITED_ANNOTATIONS;
import static org.springframework.core.annotation.MergedAnnotations.SearchStrategy.TYPE_HIERARCHY;
/**
* Tests for {@link MergedAnnotations} and {@link RepeatableContainers} that
@@ -49,184 +52,168 @@ class MergedAnnotationsRepeatableAnnotationTests {
@Test
void inheritedAnnotationsWhenNonRepeatableThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() ->
getAnnotations(null, NonRepeatable.class, SearchStrategy.INHERITED_ANNOTATIONS, getClass()))
.satisfies(this::nonRepeatableRequirements);
assertThatIllegalArgumentException()
.isThrownBy(() -> getAnnotations(null, NonRepeatable.class, INHERITED_ANNOTATIONS, getClass()))
.satisfies(this::nonRepeatableRequirements);
}
@Test
void inheritedAnnotationsWhenContainerMissingValueAttributeThrowsException() {
assertThatAnnotationConfigurationException().isThrownBy(() ->
getAnnotations(ContainerMissingValueAttribute.class, InvalidRepeatable.class,
SearchStrategy.INHERITED_ANNOTATIONS, getClass()))
.satisfies(this::missingValueAttributeRequirements);
assertThatAnnotationConfigurationException()
.isThrownBy(() -> getAnnotations(ContainerMissingValueAttribute.class, InvalidRepeatable.class,
INHERITED_ANNOTATIONS, getClass()))
.satisfies(this::missingValueAttributeRequirements);
}
@Test
void inheritedAnnotationsWhenWhenNonArrayValueAttributeThrowsException() {
assertThatAnnotationConfigurationException().isThrownBy(() ->
getAnnotations(ContainerWithNonArrayValueAttribute.class, InvalidRepeatable.class,
SearchStrategy.INHERITED_ANNOTATIONS, getClass()))
.satisfies(this::nonArrayValueAttributeRequirements);
assertThatAnnotationConfigurationException()
.isThrownBy(() -> getAnnotations(ContainerWithNonArrayValueAttribute.class, InvalidRepeatable.class,
INHERITED_ANNOTATIONS, getClass()))
.satisfies(this::nonArrayValueAttributeRequirements);
}
@Test
void inheritedAnnotationsWhenWrongComponentTypeThrowsException() {
assertThatAnnotationConfigurationException().isThrownBy(() ->
getAnnotations(ContainerWithArrayValueAttributeButWrongComponentType.class,
InvalidRepeatable.class, SearchStrategy.INHERITED_ANNOTATIONS, getClass()))
.satisfies(this::wrongComponentTypeRequirements);
assertThatAnnotationConfigurationException()
.isThrownBy(() -> getAnnotations(ContainerWithArrayValueAttributeButWrongComponentType.class,
InvalidRepeatable.class, INHERITED_ANNOTATIONS, getClass()))
.satisfies(this::wrongComponentTypeRequirements);
}
@Test
void inheritedAnnotationsWhenOnClassReturnsAnnotations() {
Set<PeteRepeat> annotations = getAnnotations(null, PeteRepeat.class,
SearchStrategy.INHERITED_ANNOTATIONS, RepeatableClass.class);
assertThat(annotations.stream().map(PeteRepeat::value)).containsExactly("A", "B",
"C");
INHERITED_ANNOTATIONS, RepeatableClass.class);
assertThat(annotations.stream().map(PeteRepeat::value)).containsExactly("A", "B", "C");
}
@Test
void inheritedAnnotationsWhenWhenOnSuperclassReturnsAnnotations() {
Set<PeteRepeat> annotations = getAnnotations(null, PeteRepeat.class,
SearchStrategy.INHERITED_ANNOTATIONS, SubRepeatableClass.class);
assertThat(annotations.stream().map(PeteRepeat::value)).containsExactly("A", "B",
"C");
INHERITED_ANNOTATIONS, SubRepeatableClass.class);
assertThat(annotations.stream().map(PeteRepeat::value)).containsExactly("A", "B", "C");
}
@Test
void inheritedAnnotationsWhenComposedOnClassReturnsAnnotations() {
Set<PeteRepeat> annotations = getAnnotations(null, PeteRepeat.class,
SearchStrategy.INHERITED_ANNOTATIONS, ComposedRepeatableClass.class);
assertThat(annotations.stream().map(PeteRepeat::value)).containsExactly("A", "B",
"C");
INHERITED_ANNOTATIONS, ComposedRepeatableClass.class);
assertThat(annotations.stream().map(PeteRepeat::value)).containsExactly("A", "B", "C");
}
@Test
void inheritedAnnotationsWhenComposedMixedWithContainerOnClassReturnsAnnotations() {
Set<PeteRepeat> annotations = getAnnotations(null, PeteRepeat.class,
SearchStrategy.INHERITED_ANNOTATIONS,
ComposedRepeatableMixedWithContainerClass.class);
assertThat(annotations.stream().map(PeteRepeat::value)).containsExactly("A", "B",
"C");
INHERITED_ANNOTATIONS, ComposedRepeatableMixedWithContainerClass.class);
assertThat(annotations.stream().map(PeteRepeat::value)).containsExactly("A", "B", "C");
}
@Test
void inheritedAnnotationsWhenComposedContainerForRepeatableOnClassReturnsAnnotations() {
Set<PeteRepeat> annotations = getAnnotations(null, PeteRepeat.class,
SearchStrategy.INHERITED_ANNOTATIONS, ComposedContainerClass.class);
assertThat(annotations.stream().map(PeteRepeat::value)).containsExactly("A", "B",
"C");
INHERITED_ANNOTATIONS, ComposedContainerClass.class);
assertThat(annotations.stream().map(PeteRepeat::value)).containsExactly("A", "B", "C");
}
@Test
void inheritedAnnotationsWhenNoninheritedComposedRepeatableOnClassReturnsAnnotations() {
Set<Noninherited> annotations = getAnnotations(null, Noninherited.class,
SearchStrategy.INHERITED_ANNOTATIONS, NoninheritedRepeatableClass.class);
assertThat(annotations.stream().map(Noninherited::value)).containsExactly("A",
"B", "C");
INHERITED_ANNOTATIONS, NoninheritedRepeatableClass.class);
assertThat(annotations.stream().map(Noninherited::value)).containsExactly("A", "B", "C");
}
@Test
void inheritedAnnotationsWhenNoninheritedComposedRepeatableOnSuperclassReturnsAnnotations() {
Set<Noninherited> annotations = getAnnotations(null, Noninherited.class,
SearchStrategy.INHERITED_ANNOTATIONS,
SubNoninheritedRepeatableClass.class);
INHERITED_ANNOTATIONS, SubNoninheritedRepeatableClass.class);
assertThat(annotations).isEmpty();
}
@Test
void typeHierarchyWhenNonRepeatableThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() ->
getAnnotations(null, NonRepeatable.class, SearchStrategy.TYPE_HIERARCHY, getClass()))
.satisfies(this::nonRepeatableRequirements);
assertThatIllegalArgumentException()
.isThrownBy(() -> getAnnotations(null, NonRepeatable.class, TYPE_HIERARCHY, getClass()))
.satisfies(this::nonRepeatableRequirements);
}
@Test
void typeHierarchyWhenContainerMissingValueAttributeThrowsException() {
assertThatAnnotationConfigurationException().isThrownBy(() ->
getAnnotations(ContainerMissingValueAttribute.class, InvalidRepeatable.class,
SearchStrategy.TYPE_HIERARCHY, getClass()))
.satisfies(this::missingValueAttributeRequirements);
assertThatAnnotationConfigurationException()
.isThrownBy(() -> getAnnotations(ContainerMissingValueAttribute.class, InvalidRepeatable.class,
TYPE_HIERARCHY, getClass()))
.satisfies(this::missingValueAttributeRequirements);
}
@Test
void typeHierarchyWhenWhenNonArrayValueAttributeThrowsException() {
assertThatAnnotationConfigurationException().isThrownBy(() ->
getAnnotations(ContainerWithNonArrayValueAttribute.class, InvalidRepeatable.class,
SearchStrategy.TYPE_HIERARCHY, getClass()))
.satisfies(this::nonArrayValueAttributeRequirements);
assertThatAnnotationConfigurationException()
.isThrownBy(() -> getAnnotations(ContainerWithNonArrayValueAttribute.class, InvalidRepeatable.class,
TYPE_HIERARCHY, getClass()))
.satisfies(this::nonArrayValueAttributeRequirements);
}
@Test
void typeHierarchyWhenWrongComponentTypeThrowsException() {
assertThatAnnotationConfigurationException().isThrownBy(() ->
getAnnotations(ContainerWithArrayValueAttributeButWrongComponentType.class,
InvalidRepeatable.class, SearchStrategy.TYPE_HIERARCHY, getClass()))
.satisfies(this::wrongComponentTypeRequirements);
assertThatAnnotationConfigurationException()
.isThrownBy(() -> getAnnotations(ContainerWithArrayValueAttributeButWrongComponentType.class,
InvalidRepeatable.class, TYPE_HIERARCHY, getClass()))
.satisfies(this::wrongComponentTypeRequirements);
}
@Test
void typeHierarchyWhenOnClassReturnsAnnotations() {
Set<PeteRepeat> annotations = getAnnotations(null, PeteRepeat.class,
SearchStrategy.TYPE_HIERARCHY, RepeatableClass.class);
assertThat(annotations.stream().map(PeteRepeat::value)).containsExactly("A", "B",
"C");
TYPE_HIERARCHY, RepeatableClass.class);
assertThat(annotations.stream().map(PeteRepeat::value)).containsExactly("A", "B", "C");
}
@Test
void typeHierarchyWhenWhenOnSuperclassReturnsAnnotations() {
void typeHierarchyWhenOnSuperclassReturnsAnnotations() {
Set<PeteRepeat> annotations = getAnnotations(null, PeteRepeat.class,
SearchStrategy.TYPE_HIERARCHY, SubRepeatableClass.class);
assertThat(annotations.stream().map(PeteRepeat::value)).containsExactly("A", "B",
"C");
TYPE_HIERARCHY, SubRepeatableClass.class);
assertThat(annotations.stream().map(PeteRepeat::value)).containsExactly("A", "B", "C");
}
@Test
void typeHierarchyWhenComposedOnClassReturnsAnnotations() {
Set<PeteRepeat> annotations = getAnnotations(null, PeteRepeat.class,
SearchStrategy.TYPE_HIERARCHY, ComposedRepeatableClass.class);
assertThat(annotations.stream().map(PeteRepeat::value)).containsExactly("A", "B",
"C");
TYPE_HIERARCHY, ComposedRepeatableClass.class);
assertThat(annotations.stream().map(PeteRepeat::value)).containsExactly("A", "B", "C");
}
@Test
void typeHierarchyWhenComposedMixedWithContainerOnClassReturnsAnnotations() {
Set<PeteRepeat> annotations = getAnnotations(null, PeteRepeat.class,
SearchStrategy.TYPE_HIERARCHY,
ComposedRepeatableMixedWithContainerClass.class);
assertThat(annotations.stream().map(PeteRepeat::value)).containsExactly("A", "B",
"C");
TYPE_HIERARCHY, ComposedRepeatableMixedWithContainerClass.class);
assertThat(annotations.stream().map(PeteRepeat::value)).containsExactly("A", "B", "C");
}
@Test
void typeHierarchyWhenComposedContainerForRepeatableOnClassReturnsAnnotations() {
Set<PeteRepeat> annotations = getAnnotations(null, PeteRepeat.class,
SearchStrategy.TYPE_HIERARCHY, ComposedContainerClass.class);
assertThat(annotations.stream().map(PeteRepeat::value)).containsExactly("A", "B",
"C");
TYPE_HIERARCHY, ComposedContainerClass.class);
assertThat(annotations.stream().map(PeteRepeat::value)).containsExactly("A", "B", "C");
}
@Test
void typeHierarchyAnnotationsWhenNoninheritedComposedRepeatableOnClassReturnsAnnotations() {
Set<Noninherited> annotations = getAnnotations(null, Noninherited.class,
SearchStrategy.TYPE_HIERARCHY, NoninheritedRepeatableClass.class);
assertThat(annotations.stream().map(Noninherited::value)).containsExactly("A",
"B", "C");
TYPE_HIERARCHY, NoninheritedRepeatableClass.class);
assertThat(annotations.stream().map(Noninherited::value)).containsExactly("A", "B", "C");
}
@Test
void typeHierarchyAnnotationsWhenNoninheritedComposedRepeatableOnSuperclassReturnsAnnotations() {
Set<Noninherited> annotations = getAnnotations(null, Noninherited.class,
SearchStrategy.TYPE_HIERARCHY, SubNoninheritedRepeatableClass.class);
assertThat(annotations.stream().map(Noninherited::value)).containsExactly("A",
"B", "C");
TYPE_HIERARCHY, SubNoninheritedRepeatableClass.class);
assertThat(annotations.stream().map(Noninherited::value)).containsExactly("A", "B", "C");
}
@Test
void typeHierarchyAnnotationsWithLocalComposedAnnotationWhoseRepeatableMetaAnnotationsAreFiltered() {
Class<WithRepeatedMetaAnnotationsClass> element = WithRepeatedMetaAnnotationsClass.class;
SearchStrategy searchStrategy = SearchStrategy.TYPE_HIERARCHY;
SearchStrategy searchStrategy = TYPE_HIERARCHY;
AnnotationFilter annotationFilter = PeteRepeat.class.getName()::equals;
Set<PeteRepeat> annotations = getAnnotations(null, PeteRepeat.class, searchStrategy, element, annotationFilter);
@@ -240,6 +227,44 @@ class MergedAnnotationsRepeatableAnnotationTests {
assertThat(annotationTypes).containsExactly(WithRepeatedMetaAnnotations.class, Noninherited.class, Noninherited.class);
}
@Test // gh-32731
void searchFindsRepeatableContainerAnnotationAndRepeatedAnnotations() {
Class<?> clazz = StandardRepeatablesWithContainerWithMultipleAttributesTestCase.class;
// NO RepeatableContainers
MergedAnnotations mergedAnnotations = MergedAnnotations.from(clazz, TYPE_HIERARCHY, RepeatableContainers.none());
ContainerWithMultipleAttributes container = mergedAnnotations
.get(ContainerWithMultipleAttributes.class)
.synthesize(MergedAnnotation::isPresent).orElse(null);
assertThat(container).as("container").isNotNull();
assertThat(container.name()).isEqualTo("enigma");
RepeatableWithContainerWithMultipleAttributes[] repeatedAnnotations = container.value();
assertThat(Arrays.stream(repeatedAnnotations).map(RepeatableWithContainerWithMultipleAttributes::value))
.containsExactly("A", "B");
Set<RepeatableWithContainerWithMultipleAttributes> set =
mergedAnnotations.stream(RepeatableWithContainerWithMultipleAttributes.class)
.collect(MergedAnnotationCollectors.toAnnotationSet());
// Only finds the locally declared repeated annotation.
assertThat(set.stream().map(RepeatableWithContainerWithMultipleAttributes::value))
.containsExactly("C");
// Standard RepeatableContainers
mergedAnnotations = MergedAnnotations.from(clazz, TYPE_HIERARCHY, RepeatableContainers.standardRepeatables());
container = mergedAnnotations
.get(ContainerWithMultipleAttributes.class)
.synthesize(MergedAnnotation::isPresent).orElse(null);
assertThat(container).as("container").isNotNull();
assertThat(container.name()).isEqualTo("enigma");
repeatedAnnotations = container.value();
assertThat(Arrays.stream(repeatedAnnotations).map(RepeatableWithContainerWithMultipleAttributes::value))
.containsExactly("A", "B");
set = mergedAnnotations.stream(RepeatableWithContainerWithMultipleAttributes.class)
.collect(MergedAnnotationCollectors.toAnnotationSet());
// Finds the locally declared repeated annotation plus the 2 in the container.
assertThat(set.stream().map(RepeatableWithContainerWithMultipleAttributes::value))
.containsExactly("A", "B", "C");
}
private <A extends Annotation> Set<A> getAnnotations(Class<? extends Annotation> container,
Class<A> repeatable, SearchStrategy searchStrategy, AnnotatedElement element) {
@@ -255,32 +280,37 @@ class MergedAnnotationsRepeatableAnnotationTests {
}
private void nonRepeatableRequirements(Exception ex) {
assertThat(ex.getMessage()).startsWith(
"Annotation type must be a repeatable annotation").contains(
"failed to resolve container type for",
NonRepeatable.class.getName());
assertThat(ex)
.hasMessageStartingWith("Annotation type must be a repeatable annotation")
.hasMessageContaining("failed to resolve container type for", NonRepeatable.class.getName());
}
private void missingValueAttributeRequirements(Exception ex) {
assertThat(ex.getMessage()).startsWith(
"Invalid declaration of container type").contains(
assertThat(ex)
.hasMessageStartingWith("Invalid declaration of container type")
.hasMessageContaining(
ContainerMissingValueAttribute.class.getName(),
"for repeatable annotation", InvalidRepeatable.class.getName());
assertThat(ex).hasCauseInstanceOf(NoSuchMethodException.class);
"for repeatable annotation",
InvalidRepeatable.class.getName())
.hasCauseInstanceOf(NoSuchMethodException.class);
}
private void nonArrayValueAttributeRequirements(Exception ex) {
assertThat(ex.getMessage()).startsWith("Container type").contains(
ContainerWithNonArrayValueAttribute.class.getName(),
"must declare a 'value' attribute for an array of type",
InvalidRepeatable.class.getName());
assertThat(ex)
.hasMessageStartingWith("Container type")
.hasMessageContaining(
ContainerWithNonArrayValueAttribute.class.getName(),
"must declare a 'value' attribute for an array of type",
InvalidRepeatable.class.getName());
}
private void wrongComponentTypeRequirements(Exception ex) {
assertThat(ex.getMessage()).startsWith("Container type").contains(
ContainerWithArrayValueAttributeButWrongComponentType.class.getName(),
"must declare a 'value' attribute for an array of type",
InvalidRepeatable.class.getName());
assertThat(ex)
.hasMessageStartingWith("Container type")
.hasMessageContaining(
ContainerWithArrayValueAttributeButWrongComponentType.class.getName(),
"must declare a 'value' attribute for an array of type",
InvalidRepeatable.class.getName());
}
private static ThrowableTypeAssert<AnnotationConfigurationException> assertThatAnnotationConfigurationException() {
@@ -289,33 +319,28 @@ class MergedAnnotationsRepeatableAnnotationTests {
@Retention(RetentionPolicy.RUNTIME)
@interface NonRepeatable {
}
@Retention(RetentionPolicy.RUNTIME)
@interface ContainerMissingValueAttribute {
// InvalidRepeatable[] value();
}
@Retention(RetentionPolicy.RUNTIME)
@interface ContainerWithNonArrayValueAttribute {
InvalidRepeatable value();
}
@Retention(RetentionPolicy.RUNTIME)
@interface ContainerWithArrayValueAttributeButWrongComponentType {
String[] value();
}
@Retention(RetentionPolicy.RUNTIME)
@interface InvalidRepeatable {
}
@Retention(RetentionPolicy.RUNTIME)
@@ -323,7 +348,6 @@ class MergedAnnotationsRepeatableAnnotationTests {
@interface PeteRepeats {
PeteRepeat[] value();
}
@Retention(RetentionPolicy.RUNTIME)
@@ -332,7 +356,6 @@ class MergedAnnotationsRepeatableAnnotationTests {
@interface PeteRepeat {
String value();
}
@PeteRepeat("shadowed")
@@ -343,7 +366,6 @@ class MergedAnnotationsRepeatableAnnotationTests {
@AliasFor(annotation = PeteRepeat.class)
String value();
}
@PeteRepeat("shadowed")
@@ -354,7 +376,6 @@ class MergedAnnotationsRepeatableAnnotationTests {
@AliasFor(annotation = PeteRepeat.class)
String value();
}
@PeteRepeats({ @PeteRepeat("B"), @PeteRepeat("C") })
@@ -362,37 +383,31 @@ class MergedAnnotationsRepeatableAnnotationTests {
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface ComposedContainer {
}
@PeteRepeat("A")
@PeteRepeats({ @PeteRepeat("B"), @PeteRepeat("C") })
static class RepeatableClass {
}
static class SubRepeatableClass extends RepeatableClass {
}
@ForPetesSake("B")
@ForTheLoveOfFoo("C")
@PeteRepeat("A")
static class ComposedRepeatableClass {
}
@ForPetesSake("C")
@PeteRepeats(@PeteRepeat("A"))
@PeteRepeat("B")
static class ComposedRepeatableMixedWithContainerClass {
}
@PeteRepeat("A")
@ComposedContainer
static class ComposedContainerClass {
}
@Target(ElementType.TYPE)
@@ -400,7 +415,6 @@ class MergedAnnotationsRepeatableAnnotationTests {
@interface Noninheriteds {
Noninherited[] value();
}
@Target(ElementType.TYPE)
@@ -413,7 +427,6 @@ class MergedAnnotationsRepeatableAnnotationTests {
@AliasFor("value")
String name() default "";
}
@Noninherited(name = "shadowed")
@@ -423,17 +436,14 @@ class MergedAnnotationsRepeatableAnnotationTests {
@AliasFor(annotation = Noninherited.class)
String name() default "";
}
@ComposedNoninherited(name = "C")
@Noninheriteds({ @Noninherited(value = "A"), @Noninherited(name = "B") })
static class NoninheritedRepeatableClass {
}
static class SubNoninheritedRepeatableClass extends NoninheritedRepeatableClass {
}
@Retention(RetentionPolicy.RUNTIME)
@@ -449,4 +459,27 @@ class MergedAnnotationsRepeatableAnnotationTests {
static class WithRepeatedMetaAnnotationsClass {
}
@Retention(RetentionPolicy.RUNTIME)
@interface ContainerWithMultipleAttributes {
RepeatableWithContainerWithMultipleAttributes[] value();
String name() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(ContainerWithMultipleAttributes.class)
@interface RepeatableWithContainerWithMultipleAttributes {
String value() default "";
}
@ContainerWithMultipleAttributes(name = "enigma", value = {
@RepeatableWithContainerWithMultipleAttributes("A"),
@RepeatableWithContainerWithMultipleAttributes("B")
})
@RepeatableWithContainerWithMultipleAttributes("C")
static class StandardRepeatablesWithContainerWithMultipleAttributesTestCase {
}
}
@@ -16,7 +16,9 @@
package org.springframework.core.env;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
@@ -300,6 +302,12 @@ class StandardEnvironmentTests {
assertThat(systemProperties.get(DISALLOWED_PROPERTY_NAME)).isEqualTo(DISALLOWED_PROPERTY_VALUE);
assertThat(systemProperties.get(STRING_PROPERTY_NAME)).isEqualTo(NON_STRING_PROPERTY_VALUE);
assertThat(systemProperties.get(NON_STRING_PROPERTY_NAME)).isEqualTo(STRING_PROPERTY_VALUE);
PropertiesPropertySource systemPropertySource = (PropertiesPropertySource)
environment.getPropertySources().get(StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME);
Set<String> expectedKeys = new HashSet<>(System.getProperties().stringPropertyNames());
expectedKeys.add(STRING_PROPERTY_NAME); // filtered out by stringPropertyNames due to non-String value
assertThat(Set.of(systemPropertySource.getPropertyNames())).isEqualTo(expectedKeys);
}
finally {
System.clearProperty(ALLOWED_PROPERTY_NAME);
@@ -316,6 +324,7 @@ class StandardEnvironmentTests {
assertThat(System.getenv()).isSameAs(systemEnvironment);
}
@Nested
class GetActiveProfiles {
@@ -365,6 +374,7 @@ class StandardEnvironmentTests {
}
}
@Nested
class AcceptsProfilesTests {
@@ -447,9 +457,9 @@ class StandardEnvironmentTests {
environment.addActiveProfile("p2");
assertThat(environment.acceptsProfiles(Profiles.of("p1 & p2"))).isTrue();
}
}
@Nested
class MatchesProfilesTests {
@@ -549,7 +559,6 @@ class StandardEnvironmentTests {
assertThat(environment.matchesProfiles("p2 & (foo | p1)")).isTrue();
assertThat(environment.matchesProfiles("foo", "(p2 & p1)")).isTrue();
}
}
}
@@ -34,6 +34,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Stream;
import okhttp3.mockwebserver.Dispatcher;
@@ -189,14 +190,21 @@ class ResourceTests {
String content = FileCopyUtils.copyToString(new InputStreamReader(resource1.getInputStream()));
assertThat(content).isEqualTo(testString);
assertThat(new InputStreamResource(is)).isEqualTo(resource1);
assertThat(new InputStreamResource(() -> is)).isNotEqualTo(resource1);
assertThatIllegalStateException().isThrownBy(resource1::getInputStream);
Resource resource2 = new InputStreamResource(new ByteArrayInputStream(testBytes));
assertThat(resource2.getContentAsByteArray()).containsExactly(testBytes);
assertThatIllegalStateException().isThrownBy(resource2::getContentAsByteArray);
Resource resource3 = new InputStreamResource(new ByteArrayInputStream(testBytes));
AtomicBoolean obtained = new AtomicBoolean();
Resource resource3 = new InputStreamResource(() -> {
obtained.set(true);
return new ByteArrayInputStream(testBytes);
});
assertThat(obtained).isFalse();
assertThat(resource3.getContentAsString(StandardCharsets.US_ASCII)).isEqualTo(testString);
assertThat(obtained).isTrue();
assertThatIllegalStateException().isThrownBy(() -> resource3.getContentAsString(StandardCharsets.US_ASCII));
}
@@ -206,6 +214,10 @@ class ResourceTests {
Resource resource = new InputStreamResource(is);
assertThat(resource.exists()).isTrue();
assertThat(resource.isOpen()).isTrue();
resource = new InputStreamResource(() -> is);
assertThat(resource.exists()).isTrue();
assertThat(resource.isOpen()).isTrue();
}
@Test
@@ -213,6 +225,9 @@ class ResourceTests {
InputStream is = new ByteArrayInputStream("testString".getBytes());
Resource resource = new InputStreamResource(is, "my description");
assertThat(resource.getDescription()).contains("my description");
resource = new InputStreamResource(() -> is, "my description");
assertThat(resource.getDescription()).contains("my description");
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -38,6 +38,7 @@ import static org.mockito.Mockito.never;
* Tests for {@link StreamUtils}.
*
* @author Phillip Webb
* @author Juergen Hoeller
*/
class StreamUtilsTests {
@@ -45,6 +46,7 @@ class StreamUtilsTests {
private String string = "";
@BeforeEach
void setup() {
new Random().nextBytes(bytes);
@@ -53,6 +55,7 @@ class StreamUtilsTests {
}
}
@Test
void copyToByteArray() throws Exception {
InputStream inputStream = new ByteArrayInputStream(bytes);
@@ -91,11 +94,30 @@ class StreamUtilsTests {
}
@Test
void copyRange() throws Exception {
void copyRangeWithinBuffer() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamUtils.copyRange(new ByteArrayInputStream(bytes), out, 0, 100);
byte[] range = Arrays.copyOfRange(bytes, 0, 101);
assertThat(out.toByteArray()).isEqualTo(range);
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
StreamUtils.copyRange(in, out, 0, 100);
assertThat(in.available()).isEqualTo(bytes.length - 101);
assertThat(out.toByteArray()).isEqualTo(Arrays.copyOfRange(bytes, 0, 101));
}
@Test
void copyRangeBeyondBuffer() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
StreamUtils.copyRange(in, out, 0, 8200);
assertThat(in.available()).isEqualTo(1);
assertThat(out.toByteArray()).isEqualTo(Arrays.copyOfRange(bytes, 0, 8201));
}
@Test
void copyRangeBeyondAvailable() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
StreamUtils.copyRange(in, out, 0, 8300);
assertThat(in.available()).isEqualTo(0);
assertThat(out.toByteArray()).isEqualTo(Arrays.copyOfRange(bytes, 0, 8202));
}
@Test
@@ -127,4 +149,5 @@ class StreamUtilsTests {
ordered.verify(source).write(bytes, 1, 2);
ordered.verify(source, never()).close();
}
}
@@ -440,24 +440,29 @@ public abstract class ReflectionHelper {
}
/**
* Package up the arguments so that they correctly match what is expected in requiredParameterTypes.
* <p>For example, if requiredParameterTypes is {@code (int, String[])} because the second parameter
* was declared {@code String...}, then if arguments is {@code [1,"a","b"]} then it must be
* repackaged as {@code [1,new String[]{"a","b"}]} in order to match the expected types.
* Package up the supplied {@code args} so that they correctly match what is
* expected in {@code requiredParameterTypes}.
* <p>For example, if {@code requiredParameterTypes} is {@code (int, String[])}
* because the second parameter was declared as {@code String...}, then if
* {@code args} is {@code [1, "a", "b"]} it must be repackaged as
* {@code [1, new String[] {"a", "b"}]} in order to match the expected types.
* @param requiredParameterTypes the types of the parameters for the invocation
* @param args the arguments to be setup ready for the invocation
* @return a repackaged array of arguments where any varargs setup has been done
* @param args the arguments to be set up for the invocation
* @return a repackaged array of arguments where any varargs setup has performed
*/
public static Object[] setupArgumentsForVarargsInvocation(Class<?>[] requiredParameterTypes, Object... args) {
// Check if array already built for final argument
Assert.notEmpty(requiredParameterTypes, "Required parameter types array must not be empty");
int parameterCount = requiredParameterTypes.length;
Class<?> lastRequiredParameterType = requiredParameterTypes[parameterCount - 1];
Assert.isTrue(lastRequiredParameterType.isArray(),
"The last required parameter type must be an array to support varargs invocation");
int argumentCount = args.length;
Object lastArgument = (argumentCount > 0 ? args[argumentCount - 1] : null);
// Check if repackaging is needed...
if (parameterCount != args.length ||
requiredParameterTypes[parameterCount - 1] !=
(args[argumentCount - 1] != null ? args[argumentCount - 1].getClass() : null)) {
if (parameterCount != argumentCount || !lastRequiredParameterType.isInstance(lastArgument)) {
// Create an array for the leading arguments plus the varargs array argument.
Object[] newArgs = new Object[parameterCount];
// Copy all leading arguments to the new array, omitting the varargs array argument.
@@ -469,7 +474,7 @@ public abstract class ReflectionHelper {
if (argumentCount >= parameterCount) {
varargsArraySize = argumentCount - (parameterCount - 1);
}
Class<?> componentType = requiredParameterTypes[parameterCount - 1].componentType();
Class<?> componentType = lastRequiredParameterType.componentType();
Object varargsArray = Array.newInstance(componentType, varargsArraySize);
for (int i = 0; i < varargsArraySize; i++) {
Array.set(varargsArray, i, args[parameterCount - 1 + i]);
@@ -478,6 +483,7 @@ public abstract class ReflectionHelper {
newArgs[newArgs.length - 1] = varargsArray;
return newArgs;
}
return args;
}
@@ -4221,16 +4221,27 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
assertThat(tc.s).isEqualTo("aaabbbccc");
tc.reset();
// TODO Fails related to conversion service converting a String[] to satisfy Object...
// expression = parser.parseExpression("sixteen(stringArray)");
// assertCantCompile(expression);
// expression.getValue(tc);
// assertEquals("aaabbbccc", tc.s);
// assertCanCompile(expression);
// tc.reset();
// expression.getValue(tc);
// assertEquals("aaabbbccc", tc.s);
// tc.reset();
expression = parser.parseExpression("sixteen(seventeen)");
assertCantCompile(expression);
expression.getValue(tc);
assertThat(tc.s).isEqualTo("aaabbbccc");
assertCanCompile(expression);
tc.reset();
// see TODO below
// expression.getValue(tc);
// assertThat(tc.s).isEqualTo("aaabbbccc");
// tc.reset();
// TODO Determine why the String[] is passed as the first element of the Object... varargs array instead of the entire varargs array.
// expression = parser.parseExpression("sixteen(stringArray)");
// assertCantCompile(expression);
// expression.getValue(tc);
// assertThat(tc.s).isEqualTo("aaabbbccc");
// assertCanCompile(expression);
// tc.reset();
// expression.getValue(tc);
// assertThat(tc.s).isEqualTo("aaabbbccc");
// tc.reset();
// varargs int
expression = parser.parseExpression("twelve(1,2,3)");
@@ -6089,6 +6100,10 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
}
}
}
public String[] seventeen() {
return new String[] { "aaa", "bbb", "ccc" };
}
}
@@ -40,6 +40,8 @@ import org.springframework.expression.spel.support.ReflectionHelper.ArgumentsMat
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.InstanceOfAssertFactories.array;
import static org.springframework.expression.spel.support.ReflectionHelper.ArgumentsMatchKind.CLOSE;
import static org.springframework.expression.spel.support.ReflectionHelper.ArgumentsMatchKind.EXACT;
import static org.springframework.expression.spel.support.ReflectionHelper.ArgumentsMatchKind.REQUIRES_CONVERSION;
@@ -251,15 +253,72 @@ class ReflectionHelperTests extends AbstractExpressionTests {
}
@Test
void setupArgumentsForVarargsInvocation() {
Object[] newArray = ReflectionHelper.setupArgumentsForVarargsInvocation(
new Class<?>[] {String[].class}, "a", "b", "c");
void setupArgumentsForVarargsInvocationPreconditions() {
assertThatIllegalArgumentException()
.isThrownBy(() -> ReflectionHelper.setupArgumentsForVarargsInvocation(new Class[] {}, "a"))
.withMessage("Required parameter types array must not be empty");
assertThat(newArray).hasSize(1);
Object firstParam = newArray[0];
assertThat(firstParam.getClass().componentType()).isEqualTo(String.class);
Object[] firstParamArray = (Object[]) firstParam;
assertThat(firstParamArray).containsExactly("a", "b", "c");
assertThatIllegalArgumentException()
.isThrownBy(() -> ReflectionHelper.setupArgumentsForVarargsInvocation(
new Class<?>[] { Integer.class, Integer.class }, 123))
.withMessage("The last required parameter type must be an array to support varargs invocation");
}
@Test
void setupArgumentsForVarargsInvocation() {
Object[] newArray;
newArray = ReflectionHelper.setupArgumentsForVarargsInvocation(new Class<?>[] { String[].class }, "a", "b", "c");
assertThat(newArray)
.singleElement()
.asInstanceOf(array(String[].class))
.containsExactly("a", "b", "c");
newArray = ReflectionHelper.setupArgumentsForVarargsInvocation(new Class<?>[] { Object[].class }, "a", "b", "c");
assertThat(newArray)
.singleElement()
.asInstanceOf(array(Object[].class))
.containsExactly("a", "b", "c");
newArray = ReflectionHelper.setupArgumentsForVarargsInvocation(
new Class<?>[] { Integer.class, Integer.class, String[].class }, 123, 456, "a", "b", "c");
assertThat(newArray).satisfiesExactly(
one -> assertThat(one).isEqualTo(123),
two -> assertThat(two).isEqualTo(456),
three -> assertThat(three).asInstanceOf(array(String[].class)).containsExactly("a", "b", "c"));
newArray = ReflectionHelper.setupArgumentsForVarargsInvocation(new Class<?>[] { String[].class });
assertThat(newArray)
.singleElement()
.asInstanceOf(array(String[].class))
.isEmpty();
newArray = ReflectionHelper.setupArgumentsForVarargsInvocation(
new Class<?>[] { String[].class }, new Object[] { new String[] { "a", "b", "c" } });
assertThat(newArray)
.singleElement()
.asInstanceOf(array(String[].class))
.containsExactly("a", "b", "c");
newArray = ReflectionHelper.setupArgumentsForVarargsInvocation(
new Class<?>[] { Object[].class }, new Object[] { new String[] { "a", "b", "c" } });
assertThat(newArray)
.singleElement()
.asInstanceOf(array(Object[].class))
.containsExactly("a", "b", "c");
newArray = ReflectionHelper.setupArgumentsForVarargsInvocation(new Class<?>[] { String[].class }, "a");
assertThat(newArray)
.singleElement()
.asInstanceOf(array(String[].class))
.containsExactly("a");
newArray = ReflectionHelper.setupArgumentsForVarargsInvocation(new Class<?>[] { String[].class }, new Object[] { null });
assertThat(newArray)
.singleElement()
.asInstanceOf(array(String[].class))
.singleElement()
.isNull();
}
@Test
@@ -433,12 +433,12 @@ public class TableMetaDataContext {
private final boolean quoting;
public QuoteHandler(@Nullable String identifierQuoteString) {
QuoteHandler(@Nullable String identifierQuoteString) {
this.identifierQuoteString = identifierQuoteString;
this.quoting = StringUtils.hasText(identifierQuoteString);
}
public void appendTo(StringBuilder stringBuilder, @Nullable String item) {
void appendTo(StringBuilder stringBuilder, @Nullable String item) {
if (this.quoting) {
stringBuilder.append(this.identifierQuoteString)
.append(item).append(this.identifierQuoteString);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,9 +24,9 @@ import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.lang.Nullable;
/**
* {@link RuntimeHintsRegistrar} implementation that registers reflection hints for
* {@code EmbeddedDataSourceProxy#shutdown} in order to allow it to be used as a bean
* destroy method.
* {@link RuntimeHintsRegistrar} implementation that registers reflection hints
* for {@code EmbeddedDataSourceProxy#shutdown} in order to allow it to be used
* as a bean destroy method.
*
* @author Sebastien Deleuze
* @since 6.0
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,7 +32,7 @@ import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
/**
* The default implementation of Spring's {@link SqlRowSet} interface, wrapping a
* The common implementation of Spring's {@link SqlRowSet} interface, wrapping a
* {@link java.sql.ResultSet}, catching any {@link SQLException SQLExceptions} and
* translating them to a corresponding Spring {@link InvalidResultSetAccessException}.
*
@@ -39,9 +39,9 @@ import static org.mockito.Mockito.mock;
*/
class ResultSetWrappingRowSetTests {
private ResultSet resultSet = mock();
private final ResultSet resultSet = mock();
private ResultSetWrappingSqlRowSet rowSet = new ResultSetWrappingSqlRowSet(resultSet);
private final ResultSetWrappingSqlRowSet rowSet = new ResultSetWrappingSqlRowSet(resultSet);
@Test
@@ -198,6 +198,7 @@ class ResultSetWrappingRowSetTests {
doTest(rset, rowset, "test", true);
}
private void doTest(Method rsetMethod, Method rowsetMethod, Object arg, Object ret) throws Exception {
if (arg instanceof String) {
given(resultSet.findColumn((String) arg)).willReturn(1);
@@ -207,9 +208,9 @@ class ResultSetWrappingRowSetTests {
given(rsetMethod.invoke(resultSet, arg)).willReturn(ret).willThrow(new SQLException("test"));
}
rowsetMethod.invoke(rowSet, arg);
assertThatExceptionOfType(InvocationTargetException.class).isThrownBy(() ->
rowsetMethod.invoke(rowSet, arg)).
satisfies(ex -> assertThat(ex.getTargetException()).isExactlyInstanceOf(InvalidResultSetAccessException.class));
assertThatExceptionOfType(InvocationTargetException.class)
.isThrownBy(() -> rowsetMethod.invoke(rowSet, arg))
.satisfies(ex -> assertThat(ex.getTargetException()).isExactlyInstanceOf(InvalidResultSetAccessException.class));
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -189,11 +189,6 @@ public class JmsMessagingTemplate extends AbstractMessagingTemplate<Destination>
}
}
@Override
public void convertAndSend(Object payload) throws MessagingException {
convertAndSend(payload, null);
}
@Override
public void convertAndSend(Object payload, @Nullable MessagePostProcessor postProcessor) throws MessagingException {
Destination defaultDestination = getDefaultDestination();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -290,7 +290,7 @@ public interface JmsOperations {
* <p>This method should be used carefully, since it will block the thread
* until the message becomes available or until the timeout value is exceeded.
* <p>This will only work with a default destination specified!
* @return the message produced for the consumer or {@code null} if the timeout expires.
* @return the message produced for the consumer, or {@code null} if the timeout expires
* @throws JmsException checked JMSException converted to unchecked
*/
@Nullable
@@ -303,7 +303,7 @@ public interface JmsOperations {
* <p>This method should be used carefully, since it will block the thread
* until the message becomes available or until the timeout value is exceeded.
* @param destination the destination to receive a message from
* @return the message produced for the consumer or {@code null} if the timeout expires.
* @return the message produced for the consumer, or {@code null} if the timeout expires
* @throws JmsException checked JMSException converted to unchecked
*/
@Nullable
@@ -317,7 +317,7 @@ public interface JmsOperations {
* until the message becomes available or until the timeout value is exceeded.
* @param destinationName the name of the destination to send this message to
* (to be resolved to an actual destination by a DestinationResolver)
* @return the message produced for the consumer or {@code null} if the timeout expires.
* @return the message produced for the consumer, or {@code null} if the timeout expires
* @throws JmsException checked JMSException converted to unchecked
*/
@Nullable
@@ -332,7 +332,7 @@ public interface JmsOperations {
* <p>This will only work with a default destination specified!
* @param messageSelector the JMS message selector expression (or {@code null} if none).
* See the JMS specification for a detailed definition of selector expressions.
* @return the message produced for the consumer or {@code null} if the timeout expires.
* @return the message produced for the consumer, or {@code null} if the timeout expires
* @throws JmsException checked JMSException converted to unchecked
*/
@Nullable
@@ -347,7 +347,7 @@ public interface JmsOperations {
* @param destination the destination to receive a message from
* @param messageSelector the JMS message selector expression (or {@code null} if none).
* See the JMS specification for a detailed definition of selector expressions.
* @return the message produced for the consumer or {@code null} if the timeout expires.
* @return the message produced for the consumer, or {@code null} if the timeout expires
* @throws JmsException checked JMSException converted to unchecked
*/
@Nullable
@@ -363,7 +363,7 @@ public interface JmsOperations {
* (to be resolved to an actual destination by a DestinationResolver)
* @param messageSelector the JMS message selector expression (or {@code null} if none).
* See the JMS specification for a detailed definition of selector expressions.
* @return the message produced for the consumer or {@code null} if the timeout expires.
* @return the message produced for the consumer, or {@code null} if the timeout expires
* @throws JmsException checked JMSException converted to unchecked
*/
@Nullable
@@ -344,6 +344,7 @@ public abstract class AbstractPollingMessageListenerContainer extends AbstractMe
}
catch (Throwable throwable) {
observation.error(throwable);
throw throwable;
}
// Rethrow JMSException to indicate an infrastructure problem
// that may have to trigger recovery...
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -168,8 +168,7 @@ public abstract class AbstractMessageSendingTemplate<D> implements MessageSendin
Map<String, Object> headersToUse = processHeadersToSend(headers);
if (headersToUse != null) {
messageHeaders = (headersToUse instanceof MessageHeaders _messageHeaders ?
_messageHeaders : new MessageHeaders(headersToUse));
messageHeaders = (headersToUse instanceof MessageHeaders mh ? mh : new MessageHeaders(headersToUse));
}
MessageConverter converter = getMessageConverter();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -83,7 +83,10 @@ public class MessageMappingReflectiveProcessor implements ReflectiveProcessor {
for (Parameter parameter : method.getParameters()) {
MethodParameter methodParameter = MethodParameter.forParameter(parameter);
if (Message.class.isAssignableFrom(methodParameter.getParameterType())) {
this.bindingRegistrar.registerReflectionHints(hints, getMessageType(methodParameter));
Type messageType = getMessageType(methodParameter);
if (messageType != null) {
this.bindingRegistrar.registerReflectionHints(hints, messageType);
}
}
else if (couldBePayload(methodParameter)) {
this.bindingRegistrar.registerReflectionHints(hints, methodParameter.getGenericParameterType());

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