Compare commits

...

74 Commits

Author SHA1 Message Date
Spring Builds 68537d6d5b Release v6.0.6 2023-03-02 17:29:33 +00:00
Sam Brannen 57b838ddda Upgrade to spring-asciidoctor-backends 0.0.5 2023-03-02 16:50:48 +01:00
Sam Brannen 8c784085d2 Update copyright dates 2023-03-02 16:22:53 +01:00
Sam Brannen c0a1e1718e Polishing 2023-03-02 16:22:42 +01:00
Sam Brannen fe29e734ae Revise documentation for @AspectJ argument name resolution algorithm
Closes gh-30026
2023-03-02 15:26:21 +01:00
rstoyanchev 50c3a62589 Upgrade to Reactor Netty 2022.0.4
Closes gh-30063
2023-03-02 13:58:57 +00:00
Juergen Hoeller a936a6a8ce Javadoc-only reference to SubscribeMapping from simp package (-> package dependency cycle)
See gh-30002
2023-03-02 13:57:17 +01:00
Juergen Hoeller 8d112b8514 Test for explicit URI decoding in convertClassLoaderURL
See gh-30031
2023-03-02 13:33:53 +01:00
Juergen Hoeller f8cb0fa2a0 Custom resolution of preferred constructors for createBean(Class)
Avoids side effects of traditional AUTOWIRE_CONSTRUCTOR algorithm.

Closes gh-30041
2023-03-02 12:54:26 +01:00
rstoyanchev c56c16d7ba Polishing contribution
Closes gh-30010
2023-03-02 11:28:16 +00:00
Patrick Strawderman df1f8139cc Use Content-Length for optimal read to byte[]
If content-length is available, pass it to readNBytes in
ByteArrayHttpMessageConverter. When the content length is less than
the internal buffer size in InputStream (8192), this avoids a copy,
as readNBytes will return the buffer directly. When the content length
is greater than the buffer size used in InputStream, passing the
content-length at least avoids over-allocating the final buffer (e.g.,
if the content length were 8193 bytes, 1 byte more than the default
buffer size).

If the content length isn't present or is too large to represent as
an integer, fall back to the default behavior of readAllBytes by
passing in Integer.MAX_VALUE.

See gh-30010
2023-03-02 11:19:57 +00:00
rstoyanchev 9624ea392a Resolve baseUrl to String vs temporary URI
Along the lines of what was suggested in #30047.

Closes gh-30062
2023-03-02 10:20:05 +00:00
Violeta Georgieva 682a4d5353 Prefer request hostName and hostPort in ReactorServerHttpRequest
See gh-30062
2023-03-02 10:20:05 +00:00
Juergen Hoeller 4419d56178 Explicit URI decoding for FileSystemResource in convertClassLoaderURL
Also raising the log level to warn for file system retrieval failures.

Closes gh-30031
2023-03-02 10:18:13 +01:00
Sam Brannen 244c97993b Update documentation for @AspectJ argument name resolution algorithm
Closes gh-30026
2023-03-01 17:06:38 +01:00
Sam Brannen 9d28fe90f5 Simplify AbstractAspectJAdvisorFactory internals 2023-03-01 16:04:43 +01:00
Sébastien Deleuze d2a4ac519c Update build badge and link 2023-03-01 15:51:07 +01:00
Sébastien Deleuze dbbebf541d Refine reflection hints handling for anonymous class
Before this commit, anonymous classes could throw an
unexpected NullPointerException in
ReflectionsHint#registerType and lambdas entries could
be created in the related generated reflect-config.json.

This commit refines how anonymous classes are handled by
explicitly checking for null class and canonical name in
ReflectionTypeReference#of, while skipping such class in
ReflectionHints#registerType in order to keep a lenient
behavior.

Closes gh-29774
2023-03-01 15:41:30 +01:00
rstoyanchev fe73c630da Use URI String if necessary in ReactorClientHttpConnector
Closes gh-30053
2023-03-01 11:47:49 +00:00
Sam Brannen 3456fd054f Remove obsolete MetadataAwareAspectInstanceFactory Javadoc
We no longer have any JDK 5 related limitations imposed on us as was
the case when MetadataAwareAspectInstanceFactory was introduced; however,
MetadataAwareAspectInstanceFactory will remain as a specialized
sub-interface of AspectInstanceFactory.
2023-02-28 16:42:57 +01:00
Sam Brannen 5fd75dd27b Add missing package-info.java file for autoproxy.target package 2023-02-28 16:38:03 +01:00
Sam Brannen 6c29a5779e Fix .gitignore pattern for Maven "target" folders
Rationale: changes in org.springframework.aop.framework.autoproxy.target
were previously ignored since the "target" package was ignored by the
previous "eager" pattern for "target" folders.
2023-02-28 16:36:32 +01:00
Sam Brannen e9413b93c6 Apply "instanceof pattern matching" in BeanFactoryAdvisorRetrievalHelper 2023-02-28 16:33:56 +01:00
Radek Kraus 4cc02fe3bc Protect JMS connection creation against prepareConnection errors
This commit uses a local variable for the creation of a new JMS
Connection so that a rare failure in prepareConnection(...) does not
leave the connection field in a partially initialized state.

If such a JMSException occurs, the intermediary connection is closed.
This commit further defends against close() failures at that point,
by logging the close exception at DEBUG level. As a result, the original
JMSException is always re-thrown.

Closes gh-29116
See gh-29115
2023-02-28 16:10:05 +01:00
Sam Brannen b1cf832c28 Apply "instanceof pattern matching" in spring-tx
This commit applies "instanceof pattern matching" to the rest of the
code in spring-tx.

See gh-30019
2023-02-28 16:03:37 +01:00
Sam Brannen 34e5ce9360 Fix Checkstyle violations
See gh-30019
2023-02-28 16:03:37 +01:00
diguage 375114defa Apply "instanceof pattern matching" in spring-tx
Closes gh-30019
2023-02-28 15:33:59 +01:00
Sam Brannen 00c2c1d2a1 Polish "Support @Value for record injection"
See gh-28774
2023-02-28 13:12:15 +01:00
nanfeng 7c9fc575ff Support @Value for record injection
Add @Value support for record injection by disabling populateBean() for
records, since records are immutable.

See gh-28770
Closes gh-28774
2023-02-28 13:01:13 +01:00
rstoyanchev c859211f7a Polishing contribution
Closes gh-29691
2023-02-27 17:37:28 +00:00
Baljit Singh 4dbe9d6709 Use ContextView in ServerWebExchangeContextFilter 2023-02-27 17:20:16 +00:00
rstoyanchev acedbfbaba Polishing contribution
Closes gh-30014
2023-02-27 17:18:53 +00:00
Sergiu Prodan 5d6f151031 Update Javadoc of ServletRequestPathFilter
See gh-30014
2023-02-27 17:10:27 +00:00
Sam Brannen a09495d4e9 Polish contribution
See gh-30034
2023-02-27 16:38:55 +01:00
1993heqiang 5cab6a1f3a Fix "Configuring a Global Date and Time Format" example
Closes gh-30034
2023-02-27 16:33:38 +01:00
Sébastien Deleuze a02a017e6e Add tests for SpEL on Kotlin suspending function parameters
This was broken at some point but seems to work now.
This commit adds related tests.

Closes gh-26867
2023-02-27 16:09:43 +01:00
Sébastien Deleuze 9f2f93129b Move HttpServiceProxyFactoryExtensions.kt to spring-web module
HttpServiceProxyFactoryExtensions.kt has been mistakenly created
in spring-webflux module instead of spring-web, breaking JPMS for
WebFlux users.

This commit moves this file and related tests to the spring-web
module.

Closes gh-30042
2023-02-27 14:48:28 +01:00
Johnny Lim 475ac6ef5d Fix KotlinCoroutinesUtilsTests.invokeNonSuspendingFunctionWithContext()
Closes gh-30029
2023-02-27 14:02:49 +01:00
Sébastien Deleuze 79f43041ad Catch defensively validator exceptions in AOT processing
An ArrayIndexOutOfBoundsException is thrown by
Validator.getConstraintsForClass when processing Kotlin beans
with extensions functions (Kotlin or Hibernate Validator bug).

This commit catches those exceptions and report them as warning
without the full stactrace, and report as well other
ones thrown as errors with the full stracktrace.

Closes gh-30037
2023-02-27 12:06:37 +01:00
Sam Brannen edb4a3467a Update copyright headers 2023-02-26 18:37:18 +01:00
Sam Brannen 024d02225c Apply "instanceof pattern matching" to remaining Validation classes 2023-02-26 18:37:18 +01:00
Sam Brannen 9305a64a50 Polish contribution
See gh-29994
2023-02-26 18:30:14 +01:00
divcon 40672c3715 Apply "instance of patten matching" in context and messaging modules
Closes gh-29994
2023-02-26 18:30:14 +01:00
Sam Brannen 7c50464bba Polishing 2023-02-26 18:30:14 +01:00
Patrick Strawderman 0d10d4beee Initialize ArrayList with exact size in HttpHeaders 2023-02-24 17:09:58 +00:00
Sébastien Deleuze f60bec986f Add missing @Nullable annotations to LogMessage methods
Closes gh-30006
2023-02-24 17:45:50 +01:00
Sébastien Deleuze 6825a842b5 Support package private methods on CGLIB proxies with AOT
Closes gh-29582
Closes gh-29764
2023-02-24 17:00:41 +01:00
Sam Brannen 7ace9aa429 Polish AspectJAdviceParameterNameDiscoverer
This commit also converts PointcutBody to a record.
2023-02-24 11:40:34 +01:00
Sam Brannen 8979ac789f Fix layout in "Determining Argument Names" section 2023-02-24 11:40:34 +01:00
Sébastien Deleuze 9cb4c5565a Fix proxy hint Kotlin extensions
Closes gh-30025
2023-02-24 10:46:27 +01:00
Sam Brannen 08f38c52c7 Revise AspectJ examples in the reference manual
The extensive use of shared pointcuts can be confusing to users who
read a given AspectJ example without having read the "Sharing Common
Pointcut Definitions" section of the AOP chapter.

The primary purpose of this commit is therefore to reduce the extensive
use of shared pointcuts in AspectJ examples by favoring "inline
pointcuts" over shared "named pointcuts" and by cross referencing the
declaration of shared pointcuts when they are used.

Since the affected parts of the documentation are intertwined, this
commit also refactors the examples to use a common "com.xyz" package
namespace for greater clarity.

In order to avoid the need to escape asterisks (\*) in examples, this
commit also removes the `subs="quotes"` configuration for code blocks.

Closes gh-30003
2023-02-24 10:21:32 +01:00
Sam Brannen afa936e985 Polish AOP chapter 2023-02-24 10:21:32 +01:00
AlexElin 3677d3597b Improve @Lazy's javadoc by adding @code tag 2023-02-23 17:27:01 +00:00
Sam Brannen 76bc7deb8e Polish AOP chapter 2023-02-22 17:30:20 +01:00
Sam Brannen 6c3cb5d2e0 Revise chomp and fold settings in reference documentation
This commit overrides the `chomp` and `fold` settings for
spring-asciidoctor-backends on a per-block basis.

Closes gh-30001
2023-02-22 12:49:20 +01:00
Sam Brannen 3ce71932c0 Polish reference docs 2023-02-22 12:48:40 +01:00
Sam Brannen 3a9c7524f0 Polishing 2023-02-22 11:21:00 +01:00
Sam Brannen ce66b251ab Fix Javadoc for MockRestRequestMatchers 2023-02-21 22:13:41 +01:00
Sébastien Deleuze 626a7fc52a Add native support for @SubscribeMapping and @MessageExceptionHandler
Closes gh-30002
2023-02-21 18:47:30 +01:00
Sébastien Deleuze 3d8455b257 Add unit tests for CoroutinesUtils
Closes gh-29968
2023-02-21 10:20:00 +01:00
Sébastien Deleuze fd38c23699 Refine CoroutinesUtils#invokeSuspendingFunction contract
This commit refines CoroutinesUtils#invokeSuspendingFunction in order
to clarify the behavior when used on a non suspending function, and
support usages with or without the Continuation argument.

Closes gh-30005
2023-02-21 10:20:00 +01:00
Sam Brannen e47418c948 Polish attributes.adoc 2023-02-20 17:23:07 +01:00
Sam Brannen d6de374424 Restore package & import statements in AOP examples in ref docs
See gh-30001
2023-02-20 17:23:07 +01:00
Sam Brannen b437b7be34 Introduce test for perthis with @Aspect and shared pointcut
This commit modifies PerThisAspect (which is used indirectly in
AspectJAutoProxyCreatorTests.perThisAspect()) so that it uses a shared
@Pointcut for both the @Aspect and @Around declarations, in order to
refute claims made in gh-29998 that the documentation in the reference
manual is incorrect.
2023-02-20 17:23:07 +01:00
Sam Brannen 2d56505ea9 Polishing 2023-02-20 16:49:27 +01:00
Sam Brannen 8a44b6445d Revise queryParam() and header() support in MockRestRequestMatchers
See gh-29953
See gh-29964
2023-02-19 18:52:44 +01:00
Sam Brannen 6d24e62e83 Polishing 2023-02-19 17:43:31 +01:00
Johnny Lim 2d62be8590 Fix Javadoc since for MockRestRequestMatchers queryParam() and header() (#29986)
See gh-29953
See gh-29964
2023-02-19 16:04:53 +01:00
Sam Brannen 2e1374b459 Update copyright headers 2023-02-19 13:41:36 +01:00
Sam Brannen bb6150e44e Use correct Kotlin file extension in update_copyright_headers.sh 2023-02-19 13:41:36 +01:00
Sam Brannen 38a4f23f16 Polishing 2023-02-19 13:41:36 +01:00
Johnny Lim 6739ca82ce Polishing
See gh-23846
See gh-29955
Closes gh-29992
2023-02-19 13:41:36 +01:00
Arjen Poutsma 88e6544d9d Fix regression in WebFlux support for WebDAV methods
This commit ensures that WebFlux's RequestMethodsRequestCondition
supports HTTP methods that are not in the RequestMethod enum.

- RequestMethod::resolve is introduced, to convert from a HttpMethod
(name) to enum values.
- RequestMethod::asHttpMethod is introduced, to convert from enum value
to HttpMethod.
- HttpMethod::valueOf replaced Map-based lookup to a switch statement
- Enabled tests that check for WebDAV methods

See gh-27697
Closes gh-29981
2023-02-17 12:46:26 +01:00
Spring Builds 1999c78350 Next development version (v6.0.6-SNAPSHOT) 2023-02-15 16:20:19 +00:00
160 changed files with 2835 additions and 2001 deletions
+1 -1
View File
@@ -29,7 +29,7 @@ spring-test/test-output/
# Maven artifacts
pom.xml
target/
/target/
# Eclipse artifacts, including WTP generated manifests
bin
+1 -1
View File
@@ -1,4 +1,4 @@
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [![Build Status](https://ci.spring.io/api/v1/teams/spring-framework/pipelines/spring-framework-5.3.x/jobs/build/badge)](https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-5.3.x?groups=Build") [![Revved up by Gradle Enterprise](https://img.shields.io/badge/Revved%20up%20by-Gradle%20Enterprise-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.spring.io/scans?search.rootProjectNames=spring)
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [![Build Status](https://ci.spring.io/api/v1/teams/spring-framework/pipelines/spring-framework-6.0.x/jobs/build/badge)](https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-6.0.x?groups=Build") [![Revved up by Gradle Enterprise](https://img.shields.io/badge/Revved%20up%20by-Gradle%20Enterprise-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.spring.io/scans?search.rootProjectNames=spring)
This is the home of the Spring Framework: the foundation for all [Spring projects](https://spring.io/projects). Collectively the Spring Framework and the family of Spring projects are often referred to simply as "Spring".
+1 -1
View File
@@ -28,7 +28,7 @@ javadoc {
}
dependencies {
asciidoctorExtensions "io.spring.asciidoctor.backends:spring-asciidoctor-backends:0.0.4"
asciidoctorExtensions "io.spring.asciidoctor.backends:spring-asciidoctor-backends:0.0.5"
}
/**
@@ -1,6 +1,11 @@
// Spring Portfolio
:docs-site: https://docs.spring.io
:docs-spring-boot: {docs-site}/spring-boot/docs/current/reference
:docs-spring-gemfire: {docs-site}/spring-gemfire/docs/current/reference
:docs-spring-security: {docs-site}/spring-security/reference
// spring-asciidoctor-backends Settings
:chomp: default headers packages
:fold: all
:docs-site: https://docs.spring.io
// Spring Framework
:docs-spring-framework: {docs-site}/spring-framework/docs/{spring-version}
:api-spring-framework: {docs-spring-framework}/javadoc-api/org/springframework
@@ -8,10 +13,6 @@
:docs-kotlin: {docdir}/../../main/kotlin/org/springframework/docs
:docs-resources: {docdir}/../../main/resources
:spring-framework-main-code: https://github.com/spring-projects/spring-framework/tree/main
// Spring portfolio Links
:docs-spring-boot: {docs-site}/spring-boot/docs/current/reference
:docs-spring-gemfire: {docs-site}/spring-gemfire/docs/current/reference
:docs-spring-security: {docs-site}/spring-security/reference
// Third-party Links
:docs-graalvm: https://www.graalvm.org/22.3/reference-manual
:gh-rsocket: https://github.com/rsocket
File diff suppressed because it is too large Load Diff
@@ -16,9 +16,9 @@ Applying such optimizations early implies the following restrictions:
* The classpath is fixed and fully defined at build time.
* The beans defined in your application cannot change at runtime, meaning:
** `@Profile`, in particular profile-specific configuration needs to be chosen at build time.
** Environment properties that impact the presence of a bean (`@Conditional`) are only considered at build time.
* Bean definitions with instance suppliers (lambdas or method references) can't be transformed Ahead of Time (see https://github.com/spring-projects/spring-framework/issues/29555[spring-framework#29555] related issue)
* The return type of methods annotated with `@Bean` should be the most specific one in order to allow proper hint inference (typically the concrete class, not an interface).
** `Environment` properties that impact the presence of a bean (`@Conditional`) are only considered at build time.
* Bean definitions with instance suppliers (lambdas or method references) cannot be transformed ahead-of-time (see related https://github.com/spring-projects/spring-framework/issues/29555[spring-framework#29555] issue).
* The return type of methods annotated with `@Bean` should be the most specific type possible (typically the concrete class, not an interface) in order to support proper type inference without invoking the corresponding `@Bean` method at build time.
When these restrictions are in place, it becomes possible to perform ahead-of-time processing at build time and generate additional assets.
A Spring AOT processed application typically generates:
@@ -123,7 +123,7 @@ easy to do in Spring. You do not actually have to do anything or know anything a
the Spring internals (or even about classes such as the `FieldRetrievingFactoryBean`).
The following example enumeration shows how easy injecting an enum value is:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
.Java
----
package jakarta.persistence;
@@ -134,7 +134,7 @@ The following example enumeration shows how easy injecting an enum value is:
EXTENDED
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
.Kotlin
----
package jakarta.persistence
@@ -148,7 +148,7 @@ The following example enumeration shows how easy injecting an enum value is:
Now consider the following setter of type `PersistenceContextType` and the corresponding bean definition:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
.Java
----
package example;
@@ -162,7 +162,7 @@ Now consider the following setter of type `PersistenceContextType` and the corre
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
.Kotlin
----
package example
@@ -796,7 +796,7 @@ element results in a single `SimpleDateFormat` bean definition). Spring features
number of convenience classes that support this scenario. In the following example, we
use the `NamespaceHandlerSupport` class:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
.Java
----
package org.springframework.samples.xml;
@@ -810,7 +810,7 @@ use the `NamespaceHandlerSupport` class:
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
.Kotlin
----
package org.springframework.samples.xml
@@ -847,7 +847,7 @@ responsible for parsing one distinct top-level XML element defined in the schema
the parser, we' have access to the XML element (and thus to its subelements, too) so that
we can parse our custom XML content, as you can see in the following example:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
.Java
----
package org.springframework.samples.xml;
@@ -884,7 +884,7 @@ the basic grunt work of creating a single `BeanDefinition`.
<2> We supply the `AbstractSingleBeanDefinitionParser` superclass with the type that our
single `BeanDefinition` represents.
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
.Kotlin
----
package org.springframework.samples.xml
@@ -1056,7 +1056,7 @@ setter method for the `components` property. This makes it hard (or rather impos
to configure a bean definition for the `Component` class by using setter injection.
The following listing shows the `Component` class:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
.Java
----
package com.foo;
@@ -1087,7 +1087,7 @@ The following listing shows the `Component` class:
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
.Kotlin
----
package com.foo
@@ -1114,7 +1114,7 @@ The typical solution to this issue is to create a custom `FactoryBean` that expo
setter property for the `components` property. The following listing shows such a custom
`FactoryBean`:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
.Java
----
package com.foo;
@@ -1154,7 +1154,7 @@ setter property for the `components` property. The following listing shows such
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
.Kotlin
----
package com.foo
@@ -1226,7 +1226,7 @@ listing shows:
Again following <<core.appendix.xsd-custom-introduction, the process described earlier>>,
we then create a custom `NamespaceHandler`:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
.Java
----
package com.foo;
@@ -1240,7 +1240,7 @@ we then create a custom `NamespaceHandler`:
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
.Kotlin
----
package com.foo
@@ -1259,7 +1259,7 @@ Next up is the custom `BeanDefinitionParser`. Remember that we are creating
a `BeanDefinition` that describes a `ComponentFactoryBean`. The following
listing shows our custom `BeanDefinitionParser` implementation:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
.Java
----
package com.foo;
@@ -1308,7 +1308,7 @@ listing shows our custom `BeanDefinitionParser` implementation:
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
.Kotlin
----
package com.foo
@@ -1403,14 +1403,14 @@ the named JCache for us. We can also modify the existing `BeanDefinition` for th
`'checkingAccountService'` so that it has a dependency on this new
JCache-initializing `BeanDefinition`. The following listing shows our `JCacheInitializer`:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
.Java
----
package com.foo;
public class JCacheInitializer {
private String name;
private final String name;
public JCacheInitializer(String name) {
this.name = name;
@@ -1421,7 +1421,7 @@ JCache-initializing `BeanDefinition`. The following listing shows our `JCacheIni
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
.Kotlin
----
package com.foo
@@ -1453,7 +1453,7 @@ the XSD schema that describes the custom attribute, as follows:
Next, we need to create the associated `NamespaceHandler`, as follows:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
.Java
----
package com.foo;
@@ -1469,7 +1469,7 @@ Next, we need to create the associated `NamespaceHandler`, as follows:
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
.Kotlin
----
package com.foo
@@ -1490,7 +1490,7 @@ Next, we need to create the parser. Note that, in this case, because we are goin
an XML attribute, we write a `BeanDefinitionDecorator` rather than a `BeanDefinitionParser`.
The following listing shows our `BeanDefinitionDecorator` implementation:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
.Java
----
package com.foo;
@@ -1544,7 +1544,7 @@ The following listing shows our `BeanDefinitionDecorator` implementation:
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
.Kotlin
----
package com.foo
@@ -949,7 +949,7 @@ order in which the constructor arguments are defined in a bean definition is the
in which those arguments are supplied to the appropriate constructor when the bean is
being instantiated. Consider the following class:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
.Java
----
package x.y;
@@ -961,7 +961,7 @@ being instantiated. Consider the following class:
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
.Kotlin
----
package x.y
@@ -993,7 +993,7 @@ case with the preceding example). When a simple type is used, such as
`<value>true</value>`, Spring cannot determine the type of the value, and so cannot match
by type without help. Consider the following class:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
.Java
----
package examples;
@@ -1012,7 +1012,7 @@ by type without help. Consider the following class:
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
.Kotlin
----
package examples
@@ -1077,7 +1077,7 @@ https://download.oracle.com/javase/8/docs/api/java/beans/ConstructorProperties.h
JDK annotation to explicitly name your constructor arguments. The sample class would
then have to look as follows:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
.Java
----
package examples;
@@ -1093,7 +1093,7 @@ then have to look as follows:
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
.Kotlin
----
package examples
@@ -2276,10 +2276,9 @@ and by <<beans-factory-client,making a `getBean("B")` call to the container>> as
typically new) bean B instance every time bean A needs it. The following example
shows this approach:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages",fold="none"]
.Java
----
// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;
// Spring-API imports
@@ -2287,6 +2286,10 @@ shows this approach:
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/**
* A class that uses a stateful Command-style class to perform
* some processing.
*/
public class CommandManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
@@ -2310,16 +2313,17 @@ shows this approach:
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages",fold="none"]
.Kotlin
----
// a class that uses a stateful Command-style class to perform some processing
package fiona.apple
// Spring-API imports
import org.springframework.context.ApplicationContext
import org.springframework.context.ApplicationContextAware
// A class that uses a stateful Command-style class to perform
// some processing.
class CommandManager : ApplicationContextAware {
private lateinit var applicationContext: ApplicationContext
@@ -2382,7 +2386,7 @@ Spring container dynamically overrides the implementation of the `createCommand(
method. The `CommandManager` class does not have any Spring dependencies, as
the reworked example shows:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages",fold="none"]
.Java
----
package fiona.apple;
@@ -2403,7 +2407,7 @@ the reworked example shows:
protected abstract Command createCommand();
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages",fold="none"]
.Kotlin
----
package fiona.apple
@@ -4214,7 +4218,7 @@ it is created by the container and prints the resulting string to the system con
The following listing shows the custom `BeanPostProcessor` implementation class definition:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
.Java
----
package scripting;
@@ -4234,9 +4238,11 @@ The following listing shows the custom `BeanPostProcessor` implementation class
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
.Kotlin
----
package scripting
import org.springframework.beans.factory.config.BeanPostProcessor
class InstantiationTracingBeanPostProcessor : BeanPostProcessor {
@@ -1978,7 +1978,7 @@ The definition of `TemplateParserContext` follows:
This section lists the classes used in the examples throughout this chapter.
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
.Inventor.Java
----
package org.spring.samples.spel.inventor;
@@ -2051,18 +2051,20 @@ This section lists the classes used in the examples throughout this chapter.
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
.Inventor.kt
----
class Inventor(
var name: String,
var nationality: String,
var inventions: Array<String>? = null,
var birthdate: Date = GregorianCalendar().time,
var placeOfBirth: PlaceOfBirth? = null)
package org.spring.samples.spel.inventor
class Inventor(
var name: String,
var nationality: String,
var inventions: Array<String>? = null,
var birthdate: Date = GregorianCalendar().time,
var placeOfBirth: PlaceOfBirth? = null)
----
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
.PlaceOfBirth.java
----
package org.spring.samples.spel.inventor;
@@ -2098,13 +2100,15 @@ class Inventor(
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
.PlaceOfBirth.kt
----
package org.spring.samples.spel.inventor
class PlaceOfBirth(var city: String, var country: String? = null) {
----
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
.Society.java
----
package org.spring.samples.spel.inventor;
@@ -2147,7 +2151,7 @@ class Inventor(
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
.Society.kt
----
package org.spring.samples.spel.inventor
@@ -640,7 +640,7 @@ support for additional `PropertyEditor` instances to an `ApplicationContext`.
Consider the following example, which defines a user class called `ExoticType` and
another class called `DependsOnExoticType`, which needs `ExoticType` set as a property:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
.Java
----
package example;
@@ -663,7 +663,7 @@ another class called `DependsOnExoticType`, which needs `ExoticType` set as a pr
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
.Kotlin
----
package example
@@ -689,12 +689,14 @@ string, which a `PropertyEditor` converts into an actual
The `PropertyEditor` implementation could look similar to the following:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
.Java
----
// converts string representation to ExoticType object
package example;
import java.beans.PropertyEditorSupport;
// converts string representation to ExoticType object
public class ExoticTypeEditor extends PropertyEditorSupport {
public void setAsText(String text) {
@@ -702,14 +704,14 @@ The `PropertyEditor` implementation could look similar to the following:
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
.Kotlin
----
// converts string representation to ExoticType object
package example
import java.beans.PropertyEditorSupport
// converts string representation to ExoticType object
class ExoticTypeEditor : PropertyEditorSupport() {
override fun setAsText(text: String) {
@@ -752,7 +754,7 @@ instances for each bean creation attempt.
The following example shows how to create your own `PropertyEditorRegistrar` implementation:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
.Java
----
package com.foo.editors.spring;
@@ -768,7 +770,7 @@ The following example shows how to create your own `PropertyEditorRegistrar` imp
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
.Kotlin
----
package com.foo.editors.spring
@@ -876,7 +878,7 @@ where type conversion is needed.
The SPI to implement type conversion logic is simple and strongly typed, as the following
interface definition shows:
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
----
package org.springframework.core.convert.converter;
@@ -901,7 +903,7 @@ Several converter implementations are provided in the `core.convert.support` pac
a convenience. These include converters from strings to numbers and other common types.
The following listing shows the `StringToInteger` class, which is a typical `Converter` implementation:
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
----
package org.springframework.core.convert.support;
@@ -922,7 +924,7 @@ When you need to centralize the conversion logic for an entire class hierarchy
(for example, when converting from `String` to `Enum` objects), you can implement
`ConverterFactory`, as the following example shows:
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
----
package org.springframework.core.convert.converter;
@@ -938,7 +940,7 @@ where T is a subclass of R.
Consider the `StringToEnumConverterFactory` as an example:
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
----
package org.springframework.core.convert.support;
@@ -975,7 +977,7 @@ context that you can use when you implement your conversion logic. Such context
type conversion be driven by a field annotation or by generic information declared on a
field signature. The following listing shows the interface definition of `GenericConverter`:
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
----
package org.springframework.core.convert.converter;
@@ -1039,7 +1041,7 @@ might match only if the target entity type declares a static finder method (for
`ConversionService` defines a unified API for executing type conversion logic at
runtime. Converters are often run behind the following facade interface:
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
----
package org.springframework.core.convert;
@@ -1223,7 +1225,7 @@ provides a unified type conversion API for both SPIs.
The `Formatter` SPI to implement field formatting logic is simple and strongly typed. The
following listing shows the `Formatter` interface definition:
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
----
package org.springframework.format;
@@ -1268,7 +1270,7 @@ a `java.text.DateFormat`.
The following `DateFormatter` is an example `Formatter` implementation:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
.Java
----
package org.springframework.format.datetime;
@@ -1302,7 +1304,7 @@ The following `DateFormatter` is an example `Formatter` implementation:
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
.Kotlin
----
class DateFormatter(private val pattern: String) : Formatter<Date> {
@@ -1334,7 +1336,7 @@ Field formatting can be configured by field type or annotation. To bind
an annotation to a `Formatter`, implement `AnnotationFormatterFactory`. The following
listing shows the definition of the `AnnotationFormatterFactory` interface:
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
----
package org.springframework.format;
@@ -1350,15 +1352,14 @@ listing shows the definition of the `AnnotationFormatterFactory` interface:
To create an implementation:
. Parameterize A to be the field `annotationType` with which you wish to associate
. Parameterize `A` to be the field `annotationType` with which you wish to associate
formatting logic -- for example `org.springframework.format.annotation.DateTimeFormat`.
. Have `getFieldTypes()` return the types of fields on which the annotation can be used.
. Have `getPrinter()` return a `Printer` to print the value of an annotated field.
. Have `getParser()` return a `Parser` to parse a `clientValue` for an annotated field.
The following example `AnnotationFormatterFactory` implementation binds the `@NumberFormat`
annotation to a formatter to let a number style or pattern be
specified:
annotation to a formatter to let a number style or pattern be specified:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
@@ -1366,10 +1367,12 @@ specified:
public final class NumberFormatAnnotationFormatterFactory
implements AnnotationFormatterFactory<NumberFormat> {
private static final Set<Class<?>> FIELD_TYPES = Set.of(Short.class,
Integer.class, Long.class, Float.class, Double.class,
BigDecimal.class, BigInteger.class);
public Set<Class<?>> getFieldTypes() {
return new HashSet<Class<?>>(asList(new Class<?>[] {
Short.class, Integer.class, Long.class, Float.class,
Double.class, BigDecimal.class, BigInteger.class }));
return FIELD_TYPES;
}
public Printer<Number> getPrinter(NumberFormat annotation, Class<?> fieldType) {
@@ -1383,16 +1386,13 @@ specified:
private Formatter<Number> configureFormatterFrom(NumberFormat annotation, Class<?> fieldType) {
if (!annotation.pattern().isEmpty()) {
return new NumberStyleFormatter(annotation.pattern());
} else {
Style style = annotation.style();
if (style == Style.PERCENT) {
return new PercentStyleFormatter();
} else if (style == Style.CURRENCY) {
return new CurrencyStyleFormatter();
} else {
return new NumberStyleFormatter();
}
}
// else
return switch(annotation.style()) {
case Style.PERCENT -> new PercentStyleFormatter();
case Style.CURRENCY -> new CurrencyStyleFormatter();
default -> new NumberStyleFormatter();
};
}
}
----
@@ -1428,7 +1428,7 @@ specified:
}
----
To trigger formatting, you can annotate fields with @NumberFormat, as the following
To trigger formatting, you can annotate fields with `@NumberFormat`, as the following
example shows:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
@@ -1490,7 +1490,7 @@ for use with Spring's `DataBinder` and the Spring Expression Language (SpEL).
The following listing shows the `FormatterRegistry` SPI:
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
----
package org.springframework.format;
@@ -1526,7 +1526,7 @@ these rules once, and they are applied whenever formatting is needed.
`FormatterRegistrar` is an SPI for registering formatters and converters through the
FormatterRegistry. The following listing shows its interface definition:
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
----
package org.springframework.format;
@@ -1578,20 +1578,22 @@ For example, the following Java configuration registers a global `yyyyMMdd` form
public FormattingConversionService conversionService() {
// Use the DefaultFormattingConversionService but do not register defaults
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false);
DefaultFormattingConversionService conversionService =
new DefaultFormattingConversionService(false);
// Ensure @NumberFormat is still supported
conversionService.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());
conversionService.addFormatterForFieldAnnotation(
new NumberFormatAnnotationFormatterFactory());
// Register JSR-310 date conversion with a specific global format
DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
registrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"));
registrar.registerFormatters(conversionService);
DateTimeFormatterRegistrar dateTimeRegistrar = new DateTimeFormatterRegistrar();
dateTimeRegistrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"));
dateTimeRegistrar.registerFormatters(conversionService);
// Register date conversion with a specific global format
DateFormatterRegistrar registrar = new DateFormatterRegistrar();
registrar.setFormatter(new DateFormatter("yyyyMMdd"));
registrar.registerFormatters(conversionService);
DateFormatterRegistrar dateRegistrar = new DateFormatterRegistrar();
dateRegistrar.setFormatter(new DateFormatter("yyyyMMdd"));
dateRegistrar.registerFormatters(conversionService);
return conversionService;
}
@@ -1612,14 +1614,14 @@ For example, the following Java configuration registers a global `yyyyMMdd` form
addFormatterForFieldAnnotation(NumberFormatAnnotationFormatterFactory())
// Register JSR-310 date conversion with a specific global format
val registrar = DateTimeFormatterRegistrar()
registrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"))
registrar.registerFormatters(this)
val dateTimeRegistrar = DateTimeFormatterRegistrar()
dateTimeRegistrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"))
dateTimeRegistrar.registerFormatters(this)
// Register date conversion with a specific global format
val registrar = DateFormatterRegistrar()
registrar.setFormatter(DateFormatter("yyyyMMdd"))
registrar.registerFormatters(this)
val dateRegistrar = DateFormatterRegistrar()
dateRegistrar.setFormatter(DateFormatter("yyyyMMdd"))
dateRegistrar.registerFormatters(this)
}
}
}
@@ -622,7 +622,7 @@ transactions being created and then rolled back in response to the
`UnsupportedOperationException` instance. The following listing shows the `FooService`
interface:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
.Java
----
// the service interface that we want to make transactional
@@ -641,7 +641,7 @@ interface:
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
.Kotlin
----
// the service interface that we want to make transactional
@@ -662,7 +662,7 @@ interface:
The following example shows an implementation of the preceding interface:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
.Java
----
package x.y.service;
@@ -690,7 +690,7 @@ The following example shows an implementation of the preceding interface:
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
.Kotlin
----
package x.y.service
@@ -893,7 +893,7 @@ return type is reactive.
The following listing shows a modified version of the previously used `FooService`, but
this time the code uses reactive types:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
.Java
----
// the reactive service interface that we want to make transactional
@@ -912,7 +912,7 @@ this time the code uses reactive types:
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
.Kotlin
----
// the reactive service interface that we want to make transactional
@@ -933,7 +933,7 @@ this time the code uses reactive types:
The following example shows an implementation of the preceding interface:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
.Java
----
package x.y.service;
@@ -961,7 +961,7 @@ The following example shows an implementation of the preceding interface:
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
.Kotlin
----
package x.y.service
@@ -2028,7 +2028,7 @@ configuration and AOP in general.
The following code shows the simple profiling aspect discussed earlier:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
.Java
----
package x.y;
@@ -2065,9 +2065,15 @@ The following code shows the simple profiling aspect discussed earlier:
}
}
----
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages"]
.Kotlin
----
package x.y
import org.aspectj.lang.ProceedingJoinPoint
import org.springframework.util.StopWatch
import org.springframework.core.Ordered
class SimpleProfiler : Ordered {
private var order: Int = 0
@@ -582,7 +582,7 @@ a similar contract to the JMS `MessageListener` interface but also gives the mes
method access to the JMS `Session` from which the `Message` was received.
The following listing shows the definition of the `SessionAwareMessageListener` interface:
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
----
package org.springframework.jms.listener;
@@ -31,7 +31,7 @@ The core class in Spring's JMX framework is the `MBeanExporter`. This class is
responsible for taking your Spring beans and registering them with a JMX `MBeanServer`.
For example, consider the following class:
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages",chomp="-packages"]
----
package org.springframework.jmx;
@@ -358,7 +358,7 @@ an operation or an attribute.
The following example shows the annotated version of the `JmxTestBean` class that we
used in <<jmx-exporting-mbeanserver>>:
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
----
package org.springframework.jmx;
@@ -1067,7 +1067,7 @@ example, consider the scenario where one would like to be informed (through a
`Notification`) each and every time an attribute of a target MBean changes. The following
example writes notifications to the console:
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
----
package com.example;
@@ -1317,7 +1317,7 @@ published, and invoke the `sendNotification(Notification)` on the
In the following example, exported instances of the `JmxTestBean` publish a
`NotificationEvent` every time the `add(int, int)` operation is invoked:
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
----
package org.springframework.jmx;
@@ -816,8 +816,7 @@ its properties from the job data mapped to properties of the job instance. So, i
the `ExampleJob` contains a bean property named `timeout`, and the `JobDetail`
has it applied automatically:
[source,java,indent=0]
[subs="verbatim"]
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
----
package example;
@@ -827,7 +826,7 @@ has it applied automatically:
/**
* Setter called after the ExampleJob is instantiated
* with the value from the JobDetailFactoryBean (5)
* with the value from the JobDetailFactoryBean.
*/
public void setTimeout(int timeout) {
this.timeout = timeout;
@@ -33,7 +33,7 @@ implement. Note that this interface is defined in plain Java. Dependent objects
are injected with a reference to the `Messenger` do not know that the underlying
implementation is a Groovy script. The following listing shows the `Messenger` interface:
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
----
package org.springframework.scripting;
@@ -45,7 +45,7 @@ implementation is a Groovy script. The following listing shows the `Messenger` i
The following example defines a class that has a dependency on the `Messenger` interface:
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
----
package org.springframework.scripting;
@@ -65,15 +65,14 @@ The following example defines a class that has a dependency on the `Messenger` i
The following example implements the `Messenger` interface in Groovy:
[source,groovy,indent=0,subs="verbatim,quotes"]
[source,groovy,indent=0,subs="verbatim,quotes",chomp="-packages",fold="none"]
----
// from the file 'Messenger.groovy'
package org.springframework.scripting.groovy;
package org.springframework.scripting.groovy
// import the Messenger interface (written in Java) that is to be implemented
// Import the Messenger interface (written in Java) that is to be implemented
import org.springframework.scripting.Messenger
// define the implementation in Groovy
// Define the implementation in Groovy in file 'Messenger.groovy'
class GroovyMessenger implements Messenger {
String message
@@ -276,7 +275,7 @@ surrounded by quotation marks. The following listing shows the changes that you
(the developer) should make to the `Messenger.groovy` source file when the
execution of the program is paused:
[source,groovy,indent=0,subs="verbatim,quotes"]
[source,groovy,indent=0,subs="verbatim,quotes",chomp="-packages"]
----
package org.springframework.scripting
@@ -331,13 +330,13 @@ feature works:
<lang:groovy id="messenger">
<lang:inline-script>
package org.springframework.scripting.groovy;
package org.springframework.scripting.groovy
import org.springframework.scripting.Messenger
import org.springframework.scripting.Messenger
class GroovyMessenger implements Messenger {
String message
}
class GroovyMessenger implements Messenger {
String message
}
</lang:inline-script>
<lang:property name="message" value="I Can Do The Frug" />
@@ -363,13 +362,13 @@ constructors and properties 100% clear, the following mixture of code and config
does not work:
.An approach that cannot work
[source,groovy,indent=0,subs="verbatim,quotes"]
[source,groovy,indent=0,subs="verbatim,quotes",chomp="-packages"]
----
// from the file 'Messenger.groovy'
package org.springframework.scripting.groovy;
package org.springframework.scripting.groovy
import org.springframework.scripting.Messenger
// from the file 'Messenger.groovy'
class GroovyMessenger implements Messenger {
GroovyMessenger() {}
@@ -420,7 +419,7 @@ If you have read this chapter straight from the top, you have already
<<dynamic-language-a-first-example, seen an example>> of a Groovy-dynamic-language-backed
bean. Now consider another example (again using an example from the Spring test suite):
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
----
package org.springframework.scripting;
@@ -432,11 +431,11 @@ bean. Now consider another example (again using an example from the Spring test
The following example implements the `Calculator` interface in Groovy:
[source,groovy,indent=0,subs="verbatim,quotes"]
[source,groovy,indent=0,subs="verbatim,quotes",chomp="-packages"]
----
// from the file 'calculator.groovy'
package org.springframework.scripting.groovy
// from the file 'calculator.groovy'
class GroovyCalculator implements Calculator {
int add(int x, int y) {
@@ -457,7 +456,7 @@ The following bean definition uses the calculator defined in Groovy:
Finally, the following small application exercises the preceding configuration:
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
----
package org.springframework.scripting;
@@ -596,7 +595,7 @@ Now we can show a fully working example of using a BeanShell-based bean that imp
the `Messenger` interface that was defined earlier in this chapter. We again show the
definition of the `Messenger` interface:
[source,java,indent=0,subs="verbatim,quotes"]
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
----
package org.springframework.scripting;
@@ -676,9 +675,8 @@ beans, you have to enable the "`refreshable beans`" functionality. See
The following example shows an `org.springframework.web.servlet.mvc.Controller` implemented
by using the Groovy dynamic language:
[source,groovy,indent=0,subs="verbatim,quotes"]
[source,groovy,indent=0,subs="verbatim,quotes",chomp="-packages"]
----
// from the file '/WEB-INF/groovy/FortuneController.groovy'
package org.springframework.showcase.fortune.web
import org.springframework.showcase.fortune.service.FortuneService
@@ -689,6 +687,7 @@ by using the Groovy dynamic language:
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
// from the file '/WEB-INF/groovy/FortuneController.groovy'
class FortuneController implements Controller {
@Property FortuneService fortuneService
+1 -1
View File
@@ -11,7 +11,7 @@ dependencies {
api(platform("io.micrometer:micrometer-bom:1.10.4"))
api(platform("io.netty:netty-bom:4.1.89.Final"))
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
api(platform("io.projectreactor:reactor-bom:2022.0.3"))
api(platform("io.projectreactor:reactor-bom:2022.0.4"))
api(platform("io.rsocket:rsocket-bom:1.1.3"))
api(platform("org.apache.groovy:groovy-bom:4.0.8"))
api(platform("org.apache.logging.log4j:log4j-bom:2.19.0"))
+1 -1
View File
@@ -1,4 +1,4 @@
version=6.0.5-SNAPSHOT
version=6.0.6
org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m
@@ -38,6 +38,14 @@ import org.springframework.util.StringUtils;
* for an advice method from the pointcut expression, returning, and throwing clauses.
* If an unambiguous interpretation is not available, it returns {@code null}.
*
* <h3>Algorithm Summary</h3>
* <p>If an unambiguous binding can be deduced, then it is.
* If the advice requirements cannot possibly be satisfied, then {@code null}
* is returned. By setting the {@link #setRaiseExceptions(boolean) raiseExceptions}
* property to {@code true}, descriptive exceptions will be thrown instead of
* returning {@code null} in the case that the parameter names cannot be discovered.
*
* <h3>Algorithm Details</h3>
* <p>This class interprets arguments in the following way:
* <ol>
* <li>If the first parameter of the method is of type {@link JoinPoint}
@@ -65,15 +73,15 @@ import org.springframework.util.StringUtils;
* zero we proceed to the next stage. If {@code a} &gt; 1 then an
* {@code AmbiguousBindingException} is raised. If {@code a} == 1,
* and there are no unbound arguments of type {@code Annotation+},
* then an {@code IllegalArgumentException} is raised. if there is
* then an {@code IllegalArgumentException} is raised. If there is
* exactly one such argument, then the corresponding parameter name is
* assigned the value from the pointcut expression.</li>
* <li>If a returningName has been set, and there are no unbound arguments
* <li>If a {@code returningName} has been set, and there are no unbound arguments
* then an {@code IllegalArgumentException} is raised. If there is
* more than one unbound argument then an
* {@code AmbiguousBindingException} is raised. If there is exactly
* one unbound argument then the corresponding parameter name is assigned
* the value &lt;returningName&gt;.</li>
* the value of the {@code returningName}.</li>
* <li>If there remain unbound arguments, then the pointcut expression is
* examined once more for {@code this}, {@code target}, and
* {@code args} pointcut expressions used in the binding form (binding
@@ -99,20 +107,12 @@ import org.springframework.util.StringUtils;
* <p>The behavior on raising an {@code IllegalArgumentException} or
* {@code AmbiguousBindingException} is configurable to allow this discoverer
* to be used as part of a chain-of-responsibility. By default the condition will
* be logged and the {@code getParameterNames(..)} method will simply return
* be logged and the {@link #getParameterNames(Method)} method will simply return
* {@code null}. If the {@link #setRaiseExceptions(boolean) raiseExceptions}
* property is set to {@code true}, the conditions will be thrown as
* {@code IllegalArgumentException} and {@code AmbiguousBindingException},
* respectively.
*
* <p>Was that perfectly clear? ;)
*
* <p>Short version: If an unambiguous binding can be deduced, then it is.
* If the advice requirements cannot possibly be satisfied, then {@code null}
* is returned. By setting the {@link #setRaiseExceptions(boolean) raiseExceptions}
* property to {@code true}, descriptive exceptions will be thrown instead of
* returning {@code null} in the case that the parameter names cannot be discovered.
*
* @author Adrian Colyer
* @author Juergen Hoeller
* @since 2.0
@@ -197,7 +197,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
/**
* If {@code afterReturning} advice binds the return value, the
* returning variable name must be specified.
* {@code returning} variable name must be specified.
* @param returningName the name of the returning variable
*/
public void setReturningName(@Nullable String returningName) {
@@ -206,18 +206,17 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
/**
* If {@code afterThrowing} advice binds the thrown value, the
* throwing variable name must be specified.
* {@code throwing} variable name must be specified.
* @param throwingName the name of the throwing variable
*/
public void setThrowingName(@Nullable String throwingName) {
this.throwingName = throwingName;
}
/**
* Deduce the parameter names for an advice method.
* <p>See the {@link AspectJAdviceParameterNameDiscoverer class level javadoc}
* for this class for details of the algorithm used.
* <p>See the {@link AspectJAdviceParameterNameDiscoverer class-level javadoc}
* for this class for details on the algorithm used.
* @param method the target {@link Method}
* @return the parameter names
*/
@@ -309,7 +308,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
}
/**
* If the first parameter is of type JoinPoint or ProceedingJoinPoint,bind "thisJoinPoint" as
* If the first parameter is of type JoinPoint or ProceedingJoinPoint, bind "thisJoinPoint" as
* parameter name and return true, else return false.
*/
private boolean maybeBindThisJoinPoint() {
@@ -348,14 +347,14 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
// Second candidate we've found - ambiguous binding
throw new AmbiguousBindingException("Binding of throwing parameter '" +
this.throwingName + "' is ambiguous: could be bound to argument " +
throwableIndex + " or argument " + i);
throwableIndex + " or " + i);
}
}
}
if (throwableIndex == -1) {
throw new IllegalStateException("Binding of throwing parameter '" + this.throwingName
+ "' could not be completed as no available arguments are a subtype of Throwable");
throw new IllegalStateException("Binding of throwing parameter '" + this.throwingName +
"' could not be completed as no available arguments are a subtype of Throwable");
}
else {
bindParameterName(throwableIndex, this.throwingName);
@@ -374,7 +373,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
if (this.returningName != null) {
if (this.numberOfRemainingUnboundArguments > 1) {
throw new AmbiguousBindingException("Binding of returning parameter '" + this.returningName +
"' is ambiguous, there are " + this.numberOfRemainingUnboundArguments + " candidates.");
"' is ambiguous: there are " + this.numberOfRemainingUnboundArguments + " candidates.");
}
// We're all set... find the unbound parameter, and bind it.
@@ -387,7 +386,6 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
}
}
/**
* Parse the string pointcut expression looking for:
* &#64;this, &#64;target, &#64;args, &#64;within, &#64;withincode, &#64;annotation.
@@ -431,7 +429,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
int numAnnotationSlots = countNumberOfUnboundAnnotationArguments();
if (numAnnotationSlots > 1) {
throw new AmbiguousBindingException("Found " + varNames.size() +
" potential annotation variable(s), and " +
" potential annotation variable(s) and " +
numAnnotationSlots + " potential argument slots");
}
else if (numAnnotationSlots == 1) {
@@ -452,7 +450,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
}
}
/*
/**
* If the token starts meets Java identifier conventions, it's in.
*/
@Nullable
@@ -488,7 +486,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
private void maybeBindThisOrTargetOrArgsFromPointcutExpression() {
if (this.numberOfRemainingUnboundArguments > 1) {
throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments
+ " unbound args at this(),target(),args() binding stage, with no way to determine between them");
+ " unbound args at this()/target()/args() binding stage, with no way to determine between them");
}
List<String> varNames = new ArrayList<>();
@@ -520,10 +518,9 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
}
}
if (varNames.size() > 1) {
throw new AmbiguousBindingException("Found " + varNames.size() +
" candidate this(), target() or args() variables but only one unbound argument slot");
" candidate this(), target(), or args() variables but only one unbound argument slot");
}
else if (varNames.size() == 1) {
for (int j = 0; j < this.parameterNameBindings.length; j++) {
@@ -596,7 +593,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
// else varNames.size must be 0 and we have nothing to bind.
}
/*
/**
* We've found the start of a binding pointcut at the given index into the
* token array. Now we need to extract the pointcut body and return it.
*/
@@ -649,8 +646,8 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
private void maybeBindPrimitiveArgsFromPointcutExpression() {
int numUnboundPrimitives = countNumberOfUnboundPrimitiveArguments();
if (numUnboundPrimitives > 1) {
throw new AmbiguousBindingException("Found '" + numUnboundPrimitives +
"' unbound primitive arguments with no way to distinguish between them.");
throw new AmbiguousBindingException("Found " + numUnboundPrimitives +
" unbound primitive arguments with no way to distinguish between them.");
}
if (numUnboundPrimitives == 1) {
// Look for arg variable and bind it if we find exactly one...
@@ -696,7 +693,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
return false;
}
/*
/**
* Return {@code true} if the given argument type is a subclass
* of the given supertype.
*/
@@ -724,7 +721,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
return count;
}
/*
/**
* Find the argument index with the given type, and bind the given
* {@code varName} in that position.
*/
@@ -741,22 +738,10 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
/**
* Simple struct to hold the extracted text from a pointcut body, together
* Simple record to hold the extracted text from a pointcut body, together
* with the number of tokens consumed in extracting it.
*/
private static class PointcutBody {
private final int numTokensConsumed;
@Nullable
private final String text;
public PointcutBody(int tokens, @Nullable String text) {
this.numTokensConsumed = tokens;
this.text = text;
}
}
private record PointcutBody(int numTokensConsumed, @Nullable String text) {}
/**
* Thrown in response to an ambiguous binding being detected when
@@ -121,21 +121,21 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
*/
@SuppressWarnings("unchecked")
@Nullable
protected static AspectJAnnotation<?> findAspectJAnnotationOnMethod(Method method) {
for (Class<?> clazz : ASPECTJ_ANNOTATION_CLASSES) {
AspectJAnnotation<?> foundAnnotation = findAnnotation(method, (Class<Annotation>) clazz);
if (foundAnnotation != null) {
return foundAnnotation;
protected static AspectJAnnotation findAspectJAnnotationOnMethod(Method method) {
for (Class<?> annotationType : ASPECTJ_ANNOTATION_CLASSES) {
AspectJAnnotation annotation = findAnnotation(method, (Class<Annotation>) annotationType);
if (annotation != null) {
return annotation;
}
}
return null;
}
@Nullable
private static <A extends Annotation> AspectJAnnotation<A> findAnnotation(Method method, Class<A> toLookFor) {
A result = AnnotationUtils.findAnnotation(method, toLookFor);
if (result != null) {
return new AspectJAnnotation<>(result);
private static AspectJAnnotation findAnnotation(Method method, Class<? extends Annotation> annotationType) {
Annotation annotation = AnnotationUtils.findAnnotation(method, annotationType);
if (annotation != null) {
return new AspectJAnnotation(annotation);
}
else {
return null;
@@ -156,9 +156,8 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
/**
* Class modeling an AspectJ annotation, exposing its type enumeration and
* pointcut String.
* @param <A> the annotation type
*/
protected static class AspectJAnnotation<A extends Annotation> {
protected static class AspectJAnnotation {
private static final String[] EXPRESSION_ATTRIBUTES = {"pointcut", "value"};
@@ -171,7 +170,7 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
AfterThrowing.class, AspectJAnnotationType.AtAfterThrowing //
);
private final A annotation;
private final Annotation annotation;
private final AspectJAnnotationType annotationType;
@@ -179,11 +178,11 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
private final String argumentNames;
public AspectJAnnotation(A annotation) {
public AspectJAnnotation(Annotation annotation) {
this.annotation = annotation;
this.annotationType = determineAnnotationType(annotation);
try {
this.pointcutExpression = resolveExpression(annotation);
this.pointcutExpression = resolvePointcutExpression(annotation);
Object argNames = AnnotationUtils.getValue(annotation, "argNames");
this.argumentNames = (argNames instanceof String names ? names : "");
}
@@ -192,7 +191,7 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
}
}
private AspectJAnnotationType determineAnnotationType(A annotation) {
private AspectJAnnotationType determineAnnotationType(Annotation annotation) {
AspectJAnnotationType type = annotationTypeMap.get(annotation.annotationType());
if (type != null) {
return type;
@@ -200,21 +199,21 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
throw new IllegalStateException("Unknown annotation type: " + annotation);
}
private String resolveExpression(A annotation) {
private String resolvePointcutExpression(Annotation annotation) {
for (String attributeName : EXPRESSION_ATTRIBUTES) {
Object val = AnnotationUtils.getValue(annotation, attributeName);
if (val instanceof String str && !str.isEmpty()) {
return str;
}
}
throw new IllegalStateException("Failed to resolve expression in: " + annotation);
throw new IllegalStateException("Failed to resolve pointcut expression in: " + annotation);
}
public AspectJAnnotationType getAnnotationType() {
return this.annotationType;
}
public A getAnnotation() {
public Annotation getAnnotation() {
return this.annotation;
}
@@ -239,19 +238,22 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
*/
private static class AspectJAnnotationParameterNameDiscoverer implements ParameterNameDiscoverer {
private static final String[] EMPTY_ARRAY = new String[0];
@Override
@Nullable
public String[] getParameterNames(Method method) {
if (method.getParameterCount() == 0) {
return new String[0];
return EMPTY_ARRAY;
}
AspectJAnnotation<?> annotation = findAspectJAnnotationOnMethod(method);
AspectJAnnotation annotation = findAspectJAnnotationOnMethod(method);
if (annotation == null) {
return null;
}
StringTokenizer nameTokens = new StringTokenizer(annotation.getArgumentNames(), ",");
if (nameTokens.countTokens() > 0) {
String[] names = new String[nameTokens.countTokens()];
int numTokens = nameTokens.countTokens();
if (numTokens > 0) {
String[] names = new String[numTokens];
for (int i = 0; i < names.length; i++) {
names[i] = nameTokens.nextToken();
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,7 +35,8 @@ import org.springframework.lang.Nullable;
/**
* Internal implementation of AspectJPointcutAdvisor.
* Note that there will be one instance of this advisor for each target method.
*
* <p>Note that there will be one instance of this advisor for each target method.
*
* @author Rod Johnson
* @author Juergen Hoeller
@@ -212,7 +213,7 @@ final class InstantiationModelAwarePointcutAdvisorImpl
* creation of the advice.
*/
private void determineAdviceType() {
AspectJAnnotation<?> aspectJAnnotation =
AspectJAnnotation aspectJAnnotation =
AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(this.aspectJAdviceMethod);
if (aspectJAnnotation == null) {
this.isBeforeAdvice = false;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,10 +23,6 @@ import org.springframework.lang.Nullable;
* Subinterface of {@link org.springframework.aop.aspectj.AspectInstanceFactory}
* that returns {@link AspectMetadata} associated with AspectJ-annotated classes.
*
* <p>Ideally, AspectInstanceFactory would include this method itself, but because
* AspectMetadata uses Java-5-only {@link org.aspectj.lang.reflect.AjType},
* we need to split out this subinterface.
*
* @author Rod Johnson
* @since 2.0
* @see AspectMetadata
@@ -35,13 +31,13 @@ import org.springframework.lang.Nullable;
public interface MetadataAwareAspectInstanceFactory extends AspectInstanceFactory {
/**
* Return the AspectJ AspectMetadata for this factory's aspect.
* Get the AspectJ AspectMetadata for this factory's aspect.
* @return the aspect metadata
*/
AspectMetadata getAspectMetadata();
/**
* Return the best possible creation mutex for this factory.
* Get the best possible creation mutex for this factory.
* @return the mutex object (may be {@code null} for no mutex to use)
* @since 4.3
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -87,7 +87,7 @@ public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFacto
new InstanceComparator<>(
Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class),
(Converter<Method, Annotation>) method -> {
AspectJAnnotation<?> ann = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(method);
AspectJAnnotation ann = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(method);
return (ann != null ? ann.getAnnotation() : null);
});
Comparator<Method> methodNameComparator = new ConvertingComparator<>(Method::getName);
@@ -216,7 +216,7 @@ public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFacto
@Nullable
private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {
AspectJAnnotation<?> aspectJAnnotation =
AspectJAnnotation aspectJAnnotation =
AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
if (aspectJAnnotation == null) {
return null;
@@ -240,7 +240,7 @@ public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFacto
Class<?> candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
validate(candidateAspectClass);
AspectJAnnotation<?> aspectJAnnotation =
AspectJAnnotation aspectJAnnotation =
AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
if (aspectJAnnotation == null) {
return null;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -92,8 +92,7 @@ public class BeanFactoryAdvisorRetrievalHelper {
}
catch (BeanCreationException ex) {
Throwable rootCause = ex.getMostSpecificCause();
if (rootCause instanceof BeanCurrentlyInCreationException) {
BeanCreationException bce = (BeanCreationException) rootCause;
if (rootCause instanceof BeanCurrentlyInCreationException bce) {
String bceBeanName = bce.getBeanName();
if (bceBeanName != null && this.beanFactory.isCurrentlyInCreation(bceBeanName)) {
if (logger.isTraceEnabled()) {
@@ -0,0 +1,10 @@
/**
* Various {@link org.springframework.aop.framework.autoproxy.TargetSourceCreator}
* implementations for use with Spring's AOP auto-proxying support.
*/
@NonNullApi
@NonNullFields
package org.springframework.aop.framework.autoproxy.target;
import org.springframework.lang.NonNullApi;
import org.springframework.lang.NonNullFields;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@
package org.springframework.aop.aspectj;
import java.lang.reflect.Method;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
@@ -43,17 +42,17 @@ class AspectJAdviceParameterNameDiscovererTests {
@Test
void noArgs() {
assertParameterNames(getMethod("noArgs"), "execution(* *(..))", new String[0]);
assertParameterNames(getMethod("noArgs"), "execution(* *(..))");
}
@Test
void joinPointOnly() {
assertParameterNames(getMethod("tjp"), "execution(* *(..))", new String[] {"thisJoinPoint"});
assertParameterNames(getMethod("tjp"), "execution(* *(..))", "thisJoinPoint");
}
@Test
void joinPointStaticPartOnly() {
assertParameterNames(getMethod("tjpsp"), "execution(* *(..))", new String[] {"thisJoinPointStaticPart"});
assertParameterNames(getMethod("tjpsp"), "execution(* *(..))", "thisJoinPointStaticPart");
}
@Test
@@ -64,18 +63,18 @@ class AspectJAdviceParameterNameDiscovererTests {
@Test
void oneThrowable() {
assertParameterNames(getMethod("oneThrowable"), "foo()", null, "ex", new String[] {"ex"});
assertParameterNamesExtended(getMethod("oneThrowable"), "foo()", null, "ex", "ex");
}
@Test
void oneJPAndOneThrowable() {
assertParameterNames(getMethod("jpAndOneThrowable"), "foo()", null, "ex", new String[] {"thisJoinPoint", "ex"});
assertParameterNamesExtended(getMethod("jpAndOneThrowable"), "foo()", null, "ex", "thisJoinPoint", "ex");
}
@Test
void oneJPAndTwoThrowables() {
assertException(getMethod("jpAndTwoThrowables"), "foo()", null, "ex", AmbiguousBindingException.class,
"Binding of throwing parameter 'ex' is ambiguous: could be bound to argument 1 or argument 2");
"Binding of throwing parameter 'ex' is ambiguous: could be bound to argument 1 or 2");
}
@Test
@@ -86,13 +85,13 @@ class AspectJAdviceParameterNameDiscovererTests {
@Test
void returning() {
assertParameterNames(getMethod("oneObject"), "foo()", "obj", null, new String[] {"obj"});
assertParameterNamesExtended(getMethod("oneObject"), "foo()", "obj", null, "obj");
}
@Test
void ambiguousReturning() {
assertException(getMethod("twoObjects"), "foo()", "obj", null, AmbiguousBindingException.class,
"Binding of returning parameter 'obj' is ambiguous, there are 2 candidates.");
"Binding of returning parameter 'obj' is ambiguous: there are 2 candidates.");
}
@Test
@@ -103,22 +102,22 @@ class AspectJAdviceParameterNameDiscovererTests {
@Test
void thisBindingOneCandidate() {
assertParameterNames(getMethod("oneObject"), "this(x)", new String[] {"x"});
assertParameterNames(getMethod("oneObject"), "this(x)", "x");
}
@Test
void thisBindingWithAlternateTokenizations() {
assertParameterNames(getMethod("oneObject"), "this( x )", new String[] {"x"});
assertParameterNames(getMethod("oneObject"), "this( x)", new String[] {"x"});
assertParameterNames(getMethod("oneObject"), "this (x )", new String[] {"x"});
assertParameterNames(getMethod("oneObject"), "this(x )", new String[] {"x"});
assertParameterNames(getMethod("oneObject"), "foo() && this(x)", new String[] {"x"});
assertParameterNames(getMethod("oneObject"), "this( x )", "x");
assertParameterNames(getMethod("oneObject"), "this( x)", "x");
assertParameterNames(getMethod("oneObject"), "this (x )", "x");
assertParameterNames(getMethod("oneObject"), "this(x )", "x");
assertParameterNames(getMethod("oneObject"), "foo() && this(x)", "x");
}
@Test
void thisBindingTwoCandidates() {
assertException(getMethod("oneObject"), "this(x) || this(y)", AmbiguousBindingException.class,
"Found 2 candidate this(), target() or args() variables but only one unbound argument slot");
"Found 2 candidate this(), target(), or args() variables but only one unbound argument slot");
}
@Test
@@ -131,22 +130,22 @@ class AspectJAdviceParameterNameDiscovererTests {
@Test
void targetBindingOneCandidate() {
assertParameterNames(getMethod("oneObject"), "target(x)", new String[] {"x"});
assertParameterNames(getMethod("oneObject"), "target(x)", "x");
}
@Test
void targetBindingWithAlternateTokenizations() {
assertParameterNames(getMethod("oneObject"), "target( x )", new String[] {"x"});
assertParameterNames(getMethod("oneObject"), "target( x)", new String[] {"x"});
assertParameterNames(getMethod("oneObject"), "target (x )", new String[] {"x"});
assertParameterNames(getMethod("oneObject"), "target(x )", new String[] {"x"});
assertParameterNames(getMethod("oneObject"), "foo() && target(x)", new String[] {"x"});
assertParameterNames(getMethod("oneObject"), "target( x )", "x");
assertParameterNames(getMethod("oneObject"), "target( x)", "x");
assertParameterNames(getMethod("oneObject"), "target (x )", "x");
assertParameterNames(getMethod("oneObject"), "target(x )", "x");
assertParameterNames(getMethod("oneObject"), "foo() && target(x)", "x");
}
@Test
void targetBindingTwoCandidates() {
assertException(getMethod("oneObject"), "target(x) || target(y)", AmbiguousBindingException.class,
"Found 2 candidate this(), target() or args() variables but only one unbound argument slot");
"Found 2 candidate this(), target(), or args() variables but only one unbound argument slot");
}
@Test
@@ -159,24 +158,24 @@ class AspectJAdviceParameterNameDiscovererTests {
@Test
void argsBindingOneObject() {
assertParameterNames(getMethod("oneObject"), "args(x)", new String[] {"x"});
assertParameterNames(getMethod("oneObject"), "args(x)", "x");
}
@Test
void argsBindingOneObjectTwoCandidates() {
assertException(getMethod("oneObject"), "args(x,y)", AmbiguousBindingException.class,
"Found 2 candidate this(), target() or args() variables but only one unbound argument slot");
"Found 2 candidate this(), target(), or args() variables but only one unbound argument slot");
}
@Test
void ambiguousArgsBinding() {
assertException(getMethod("twoObjects"), "args(x,y)", AmbiguousBindingException.class,
"Still 2 unbound args at this(),target(),args() binding stage, with no way to determine between them");
"Still 2 unbound args at this()/target()/args() binding stage, with no way to determine between them");
}
@Test
void argsOnePrimitive() {
assertParameterNames(getMethod("onePrimitive"), "args(count)", new String[] {"count"});
assertParameterNames(getMethod("onePrimitive"), "args(count)", "count");
}
@Test
@@ -188,37 +187,37 @@ class AspectJAdviceParameterNameDiscovererTests {
@Test
void thisAndPrimitive() {
assertParameterNames(getMethod("oneObjectOnePrimitive"), "args(count) && this(obj)",
new String[] {"obj", "count"});
"obj", "count");
}
@Test
void targetAndPrimitive() {
assertParameterNames(getMethod("oneObjectOnePrimitive"), "args(count) && target(obj)",
new String[] {"obj", "count"});
"obj", "count");
}
@Test
void throwingAndPrimitive() {
assertParameterNames(getMethod("oneThrowableOnePrimitive"), "args(count)", null, "ex",
new String[] {"ex", "count"});
assertParameterNamesExtended(getMethod("oneThrowableOnePrimitive"), "args(count)", null, "ex",
"ex", "count");
}
@Test
void allTogetherNow() {
assertParameterNames(getMethod("theBigOne"), "this(foo) && args(x)", null, "ex",
new String[] {"thisJoinPoint", "ex", "x", "foo"});
assertParameterNamesExtended(getMethod("theBigOne"), "this(foo) && args(x)", null, "ex",
"thisJoinPoint", "ex", "x", "foo");
}
@Test
void referenceBinding() {
assertParameterNames(getMethod("onePrimitive"),"somepc(foo)", new String[] {"foo"});
assertParameterNames(getMethod("onePrimitive"),"somepc(foo)", "foo");
}
@Test
void referenceBindingWithAlternateTokenizations() {
assertParameterNames(getMethod("onePrimitive"),"call(bar *) && somepc(foo)", new String[] {"foo"});
assertParameterNames(getMethod("onePrimitive"),"somepc ( foo )", new String[] {"foo"});
assertParameterNames(getMethod("onePrimitive"),"somepc( foo)", new String[] {"foo"});
assertParameterNames(getMethod("onePrimitive"),"call(bar *) && somepc(foo)", "foo");
assertParameterNames(getMethod("onePrimitive"),"somepc ( foo )", "foo");
assertParameterNames(getMethod("onePrimitive"),"somepc( foo)", "foo");
}
}
@@ -230,38 +229,38 @@ class AspectJAdviceParameterNameDiscovererTests {
@Test
void atThis() {
assertParameterNames(getMethod("oneAnnotation"),"@this(a)", new String[] {"a"});
assertParameterNames(getMethod("oneAnnotation"),"@this(a)", "a");
}
@Test
void atTarget() {
assertParameterNames(getMethod("oneAnnotation"),"@target(a)", new String[] {"a"});
assertParameterNames(getMethod("oneAnnotation"),"@target(a)", "a");
}
@Test
void atArgs() {
assertParameterNames(getMethod("oneAnnotation"),"@args(a)", new String[] {"a"});
assertParameterNames(getMethod("oneAnnotation"),"@args(a)", "a");
}
@Test
void atWithin() {
assertParameterNames(getMethod("oneAnnotation"),"@within(a)", new String[] {"a"});
assertParameterNames(getMethod("oneAnnotation"),"@within(a)", "a");
}
@Test
void atWithincode() {
assertParameterNames(getMethod("oneAnnotation"),"@withincode(a)", new String[] {"a"});
assertParameterNames(getMethod("oneAnnotation"),"@withincode(a)", "a");
}
@Test
void atAnnotation() {
assertParameterNames(getMethod("oneAnnotation"),"@annotation(a)", new String[] {"a"});
assertParameterNames(getMethod("oneAnnotation"),"@annotation(a)", "a");
}
@Test
void ambiguousAnnotationTwoVars() {
assertException(getMethod("twoAnnotations"),"@annotation(a) && @this(x)", AmbiguousBindingException.class,
"Found 2 potential annotation variable(s), and 2 potential argument slots");
"Found 2 potential annotation variable(s) and 2 potential argument slots");
}
@Test
@@ -272,15 +271,14 @@ class AspectJAdviceParameterNameDiscovererTests {
@Test
void annotationMedley() {
assertParameterNames(getMethod("annotationMedley"),"@annotation(a) && args(count) && this(foo)",
null, "ex", new String[] {"ex", "foo", "count", "a"});
assertParameterNamesExtended(getMethod("annotationMedley"),"@annotation(a) && args(count) && this(foo)",
null, "ex", "ex", "foo", "count", "a");
}
@Test
void annotationBinding() {
assertParameterNames(getMethod("pjpAndAnAnnotation"),
"execution(* *(..)) && @annotation(ann)",
new String[] {"thisJoinPoint","ann"});
"execution(* *(..)) && @annotation(ann)", "thisJoinPoint", "ann");
}
}
@@ -296,33 +294,23 @@ class AspectJAdviceParameterNameDiscovererTests {
throw new AssertionError("Bad test specification, no method '" + name + "' found in test class");
}
private void assertParameterNames(Method method, String pointcut, String[] parameterNames) {
assertParameterNames(method, pointcut, null, null, parameterNames);
private void assertParameterNames(Method method, String pointcut, String... parameterNames) {
assertParameterNamesExtended(method, pointcut, null, null, parameterNames);
}
private void assertParameterNames(
Method method, String pointcut, String returning, String throwing, String[] parameterNames) {
private void assertParameterNamesExtended(
Method method, String pointcut, String returning, String throwing, String... parameterNames) {
assertThat(parameterNames.length).as("bad test specification, must have same number of parameter names as method arguments").isEqualTo(method.getParameterCount());
assertThat(parameterNames)
.as("bad test specification, must have same number of parameter names as method arguments")
.hasSize(method.getParameterCount());
AspectJAdviceParameterNameDiscoverer discoverer = new AspectJAdviceParameterNameDiscoverer(pointcut);
discoverer.setRaiseExceptions(true);
discoverer.setReturningName(returning);
discoverer.setThrowingName(throwing);
String[] discoveredNames = discoverer.getParameterNames(method);
String formattedExpectedNames = Arrays.toString(parameterNames);
String formattedActualNames = Arrays.toString(discoveredNames);
assertThat(discoveredNames.length).as("Expecting " + parameterNames.length + " parameter names in return set '" +
formattedExpectedNames + "', but found " + discoveredNames.length +
" '" + formattedActualNames + "'").isEqualTo(parameterNames.length);
for (int i = 0; i < discoveredNames.length; i++) {
assertThat(discoveredNames[i]).as("Parameter names must never be null").isNotNull();
assertThat(discoveredNames[i]).as("Expecting parameter " + i + " to be named '" +
parameterNames[i] + "' but was '" + discoveredNames[i] + "'").isEqualTo(parameterNames[i]);
}
assertThat(discoverer.getParameterNames(method)).isEqualTo(parameterNames);
}
private void assertException(Method method, String pointcut, Class<? extends Throwable> exceptionType, String message) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -38,56 +38,55 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
* @author Adrian Colyer
* @author Juergen Hoeller
* @author Chris Beams
* @author Sam Brannen
*/
public class ArgumentBindingTests {
class ArgumentBindingTests {
@Test
public void testBindingInPointcutUsedByAdvice() {
TestBean tb = new TestBean();
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(tb);
void bindingInPointcutUsedByAdvice() {
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new TestBean());
proxyFactory.addAspect(NamedPointcutWithArgs.class);
ITestBean proxiedTestBean = proxyFactory.getProxy();
assertThatIllegalArgumentException().isThrownBy(() ->
proxiedTestBean.setName("Supercalifragalisticexpialidocious"));
assertThatIllegalArgumentException()
.isThrownBy(() -> proxiedTestBean.setName("enigma"))
.withMessage("enigma");
}
@Test
public void testAnnotationArgumentNameBinding() {
TransactionalBean tb = new TransactionalBean();
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(tb);
void annotationArgumentNameBinding() {
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new TransactionalBean());
proxyFactory.addAspect(PointcutWithAnnotationArgument.class);
ITransactionalBean proxiedTestBean = proxyFactory.getProxy();
assertThatIllegalStateException().isThrownBy(
proxiedTestBean::doInTransaction);
assertThatIllegalStateException()
.isThrownBy(proxiedTestBean::doInTransaction)
.withMessage("Invoked with @Transactional");
}
@Test
public void testParameterNameDiscoverWithReferencePointcut() throws Exception {
void parameterNameDiscoverWithReferencePointcut() throws Exception {
AspectJAdviceParameterNameDiscoverer discoverer =
new AspectJAdviceParameterNameDiscoverer("somepc(formal) && set(* *)");
discoverer.setRaiseExceptions(true);
Method methodUsedForParameterTypeDiscovery =
getClass().getMethod("methodWithOneParam", String.class);
String[] pnames = discoverer.getParameterNames(methodUsedForParameterTypeDiscovery);
assertThat(pnames.length).as("one parameter name").isEqualTo(1);
assertThat(pnames[0]).isEqualTo("formal");
Method method = getClass().getDeclaredMethod("methodWithOneParam", String.class);
assertThat(discoverer.getParameterNames(method)).containsExactly("formal");
}
public void methodWithOneParam(String aParam) {
@SuppressWarnings("unused")
private void methodWithOneParam(String aParam) {
}
public interface ITransactionalBean {
interface ITransactionalBean {
@Transactional
void doInTransaction();
}
public static class TransactionalBean implements ITransactionalBean {
static class TransactionalBean implements ITransactionalBean {
@Override
@Transactional
@@ -95,38 +94,35 @@ public class ArgumentBindingTests {
}
}
}
/**
* Mimics Spring's @Transactional annotation without actually introducing the dependency.
*/
@Retention(RetentionPolicy.RUNTIME)
@interface Transactional {
}
/**
* Represents Spring's Transactional annotation without actually introducing the dependency
*/
@Retention(RetentionPolicy.RUNTIME)
@interface Transactional {
}
@Aspect
static class PointcutWithAnnotationArgument {
@Around(value = "execution(* org.springframework..*.*(..)) && @annotation(transactional)")
public Object around(ProceedingJoinPoint pjp, Transactional transactional) throws Throwable {
throw new IllegalStateException("Invoked with @Transactional");
}
@Aspect
class PointcutWithAnnotationArgument {
}
@Aspect
static class NamedPointcutWithArgs {
@Pointcut("execution(* *(..)) && args(s,..)")
public void pointcutWithArgs(String s) {}
@Around("pointcutWithArgs(aString)")
public Object doAround(ProceedingJoinPoint pjp, String aString) throws Throwable {
throw new IllegalArgumentException(aString);
}
@Around(value = "execution(* org.springframework..*.*(..)) && @annotation(transaction)")
public Object around(ProceedingJoinPoint pjp, Transactional transaction) throws Throwable {
System.out.println("Invoked with transaction " + transaction);
throw new IllegalStateException();
}
}
@Aspect
class NamedPointcutWithArgs {
@Pointcut("execution(* *(..)) && args(s,..)")
public void pointcutWithArgs(String s) {}
@Around("pointcutWithArgs(aString)")
public Object doAround(ProceedingJoinPoint pjp, String aString) throws Throwable {
System.out.println("got '" + aString + "' at '" + pjp + "'");
throw new IllegalArgumentException(aString);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -246,7 +246,8 @@ public abstract class BeanUtils {
// A single public constructor
return (Constructor<T>) ctors[0];
}
else if (ctors.length == 0){
else if (ctors.length == 0) {
// No public constructors -> check non-public
ctors = clazz.getDeclaredConstructors();
if (ctors.length == 1) {
// A single non-public constructor, e.g. from a non-public record type
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -130,10 +130,10 @@ public interface AutowireCapableBeanFactory extends BeanFactory {
* {@link BeanPostProcessor BeanPostProcessors}.
* <p>Note: This is intended for creating a fresh instance, populating annotated
* fields and methods as well as applying all standard bean initialization callbacks.
* Constructor resolution is done via {@link #AUTOWIRE_CONSTRUCTOR}, also influenced
* by {@link SmartInstantiationAwareBeanPostProcessor#determineCandidateConstructors}.
* It does <i>not</i> imply traditional by-name or by-type autowiring of properties;
* use {@link #createBean(Class, int, boolean)} for those purposes.
* Constructor resolution is based on Kotlin primary / single public / single non-public,
* with a fallback to the default constructor in ambiguous scenarios, also influenced
* by {@link SmartInstantiationAwareBeanPostProcessor#determineCandidateConstructors}
* (e.g. for annotation-driven constructor selection).
* @param beanClass the class of the bean to create
* @return the new bean instance
* @throws BeansException if instantiation or wiring failed
@@ -315,8 +315,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
@SuppressWarnings("unchecked")
public <T> T createBean(Class<T> beanClass) throws BeansException {
// Use non-singleton bean definition, to avoid registering bean as dependent bean.
RootBeanDefinition bd = new RootBeanDefinition(beanClass);
bd.setAutowireMode(AUTOWIRE_CONSTRUCTOR);
RootBeanDefinition bd = new CreateFromClassBeanDefinition(beanClass);
bd.setScope(SCOPE_PROTOTYPE);
bd.allowCaching = ClassUtils.isCacheSafe(beanClass, getBeanClassLoader());
return (T) createBean(beanClass.getName(), bd, null);
@@ -1364,6 +1363,17 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
}
}
if (bw.getWrappedClass().isRecord()) {
if (mbd.hasPropertyValues()) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Cannot apply property values to a record");
}
else {
// Skip property population phase for records since they are immutable.
return;
}
}
// Give any InstantiationAwareBeanPostProcessors the opportunity to modify the
// state of the bean before properties are set. This can be used, for example,
// to support styles of field injection.
@@ -1898,6 +1908,36 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
}
/**
* {@link RootBeanDefinition} subclass for {@code #createBean} calls with
* flexible selection of a Kotlin primary / single public / single non-public
* constructor candidate in addition to the default constructor.
* @see BeanUtils#getResolvableConstructor(Class)
*/
@SuppressWarnings("serial")
private static class CreateFromClassBeanDefinition extends RootBeanDefinition {
public CreateFromClassBeanDefinition(Class<?> beanClass) {
super(beanClass);
}
public CreateFromClassBeanDefinition(CreateFromClassBeanDefinition original) {
super(original);
}
@Override
@Nullable
public Constructor<?>[] getPreferredConstructors() {
return ConstructorResolver.determinePreferredConstructors(getBeanClass());
}
@Override
public RootBeanDefinition cloneBeanDefinition() {
return new CreateFromClassBeanDefinition(this);
}
}
/**
* Special DependencyDescriptor variant for Spring's good old autowire="byType" mode.
* Always optional; never considering the parameter name for choosing a primary candidate.
@@ -1930,7 +1970,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
}
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
public void doWith(Method method) throws IllegalArgumentException {
if (isFactoryBeanMethod(method)) {
ResolvableType returnType = ResolvableType.forMethodReturnType(method);
ResolvableType candidate = returnType.as(FactoryBean.class).getGeneric();
@@ -1222,6 +1222,54 @@ class ConstructorResolver {
return old;
}
/**
* See {@link BeanUtils#getResolvableConstructor(Class)} for alignment.
* This variant adds a lenient fallback to the default constructor if available, similar to
* {@link org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#determineCandidateConstructors}.
*/
@Nullable
static Constructor<?>[] determinePreferredConstructors(Class<?> clazz) {
Constructor<?> primaryCtor = BeanUtils.findPrimaryConstructor(clazz);
Constructor<?> defaultCtor;
try {
defaultCtor = clazz.getDeclaredConstructor();
}
catch (NoSuchMethodException ex) {
defaultCtor = null;
}
if (primaryCtor != null) {
if (defaultCtor != null && !primaryCtor.equals(defaultCtor)) {
return new Constructor<?>[] {primaryCtor, defaultCtor};
}
else {
return new Constructor<?>[] {primaryCtor};
}
}
Constructor<?>[] ctors = clazz.getConstructors();
if (ctors.length == 1) {
// A single public constructor, potentially in combination with a non-public default constructor
if (defaultCtor != null && !ctors[0].equals(defaultCtor)) {
return new Constructor<?>[] {ctors[0], defaultCtor};
}
else {
return ctors;
}
}
else if (ctors.length == 0) {
// No public constructors -> check non-public
ctors = clazz.getDeclaredConstructors();
if (ctors.length == 1) {
// A single non-public constructor, e.g. from a non-public record type
return ctors;
}
}
return null;
}
/**
* Private inner class for holding argument combinations.
@@ -2126,6 +2126,23 @@ class DefaultListableBeanFactoryTests {
assertThat(tb.wasDestroyed()).isTrue();
}
@Test
void createBeanWithNonDefaultConstructor() {
lbf.registerBeanDefinition("otherTestBean", new RootBeanDefinition(TestBean.class));
TestBeanRecipient tb = lbf.createBean(TestBeanRecipient.class);
assertThat(lbf.containsSingleton("otherTestBean")).isTrue();
assertThat(tb.testBean).isEqualTo(lbf.getBean("otherTestBean"));
lbf.destroyBean(tb);
}
@Test
void createBeanWithPreferredDefaultConstructor() {
lbf.registerBeanDefinition("otherTestBean", new RootBeanDefinition(TestBean.class));
TestBean tb = lbf.createBean(TestBean.class);
assertThat(lbf.containsSingleton("otherTestBean")).isFalse();
lbf.destroyBean(tb);
}
@Test
void configureBean() {
MutablePropertyValues pvs = new MutablePropertyValues();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@ import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.lang.Nullable;
@@ -86,8 +87,9 @@ public class SpringBeanJobFactory extends AdaptableJobFactory
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
Object job = (this.applicationContext != null ?
// to be replaced with createBean(Class) in 6.1
this.applicationContext.getAutowireCapableBeanFactory().createBean(
bundle.getJobDetail().getJobClass()) :
bundle.getJobDetail().getJobClass(), AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false) :
super.createJobInstance(bundle));
if (isEligibleForPropertyPopulation(job)) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -38,7 +38,7 @@ import java.lang.annotation.Target;
*
* <p>If Lazy is present on a {@link Configuration @Configuration} class, this
* indicates that all {@code @Bean} methods within that {@code @Configuration}
* should be lazily initialized. If {@code @Lazy} is present and false on a {@code @Bean}
* should be lazily initialized. If {@code @Lazy} is present and {@code false} on a {@code @Bean}
* method within a {@code @Lazy}-annotated {@code @Configuration} class, this indicates
* overriding the 'default lazy' behavior and that the bean should be eagerly initialized.
*
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -132,7 +132,7 @@ public class SimpleApplicationEventMulticaster extends AbstractApplicationEventM
}
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -558,7 +558,7 @@ public class GenericApplicationContext extends AbstractApplicationContext implem
/**
* {@link RootBeanDefinition} marker subclass for {@code #registerBean} based
* {@link RootBeanDefinition} subclass for {@code #registerBean} based
* registrations with flexible autowiring for public constructors.
*/
@SuppressWarnings("serial")
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -169,8 +169,8 @@ public abstract class AbstractBindingResult extends AbstractErrors implements Bi
public List<FieldError> getFieldErrors() {
List<FieldError> result = new ArrayList<>();
for (ObjectError objectError : this.errors) {
if (objectError instanceof FieldError) {
result.add((FieldError) objectError);
if (objectError instanceof FieldError fieldError) {
result.add(fieldError);
}
}
return Collections.unmodifiableList(result);
@@ -180,8 +180,8 @@ public abstract class AbstractBindingResult extends AbstractErrors implements Bi
@Nullable
public FieldError getFieldError() {
for (ObjectError objectError : this.errors) {
if (objectError instanceof FieldError) {
return (FieldError) objectError;
if (objectError instanceof FieldError fieldError) {
return fieldError;
}
}
return null;
@@ -192,8 +192,8 @@ public abstract class AbstractBindingResult extends AbstractErrors implements Bi
List<FieldError> result = new ArrayList<>();
String fixedField = fixedField(field);
for (ObjectError objectError : this.errors) {
if (objectError instanceof FieldError && isMatchingFieldError(fixedField, (FieldError) objectError)) {
result.add((FieldError) objectError);
if (objectError instanceof FieldError fieldError && isMatchingFieldError(fixedField, fieldError)) {
result.add(fieldError);
}
}
return Collections.unmodifiableList(result);
@@ -204,10 +204,8 @@ public abstract class AbstractBindingResult extends AbstractErrors implements Bi
public FieldError getFieldError(String field) {
String fixedField = fixedField(field);
for (ObjectError objectError : this.errors) {
if (objectError instanceof FieldError fieldError) {
if (isMatchingFieldError(fixedField, fieldError)) {
return fieldError;
}
if (objectError instanceof FieldError fieldError && isMatchingFieldError(fixedField, fieldError)) {
return fieldError;
}
}
return null;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -42,10 +42,15 @@ public abstract class BindingResultUtils {
Assert.notNull(model, "Model map must not be null");
Assert.notNull(name, "Name must not be null");
Object attr = model.get(BindingResult.MODEL_KEY_PREFIX + name);
if (attr != null && !(attr instanceof BindingResult)) {
if (attr == null) {
return null;
}
if (attr instanceof BindingResult bindingResult) {
return bindingResult;
}
else {
throw new IllegalStateException("BindingResult attribute is not of type BindingResult: " + attr);
}
return (BindingResult) attr;
}
/**
@@ -760,8 +760,8 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
* @see #doBind(org.springframework.beans.MutablePropertyValues)
*/
public void bind(PropertyValues pvs) {
MutablePropertyValues mpvs = (pvs instanceof MutablePropertyValues ?
(MutablePropertyValues) pvs : new MutablePropertyValues(pvs));
MutablePropertyValues mpvs = (pvs instanceof MutablePropertyValues mutablePropertyValues ?
mutablePropertyValues : new MutablePropertyValues(pvs));
doBind(mpvs);
}
@@ -849,8 +849,8 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
PropertyValue pv = propertyValues.get(field);
boolean empty = (pv == null || pv.getValue() == null);
if (!empty) {
if (pv.getValue() instanceof String) {
empty = !StringUtils.hasText((String) pv.getValue());
if (pv.getValue() instanceof String text) {
empty = !StringUtils.hasText(text);
}
else if (pv.getValue() instanceof String[] values) {
empty = (values.length == 0 || !StringUtils.hasText(values[0]));
@@ -925,8 +925,8 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
BindingResult bindingResult = getBindingResult();
// Call each validator with the same binding result
for (Validator validator : getValidators()) {
if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {
((SmartValidator) validator).validate(target, bindingResult, validationHints);
if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator smartValidator) {
smartValidator.validate(target, bindingResult, validationHints);
}
else if (validator != null) {
validator.validate(target, bindingResult);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -105,8 +105,8 @@ public class ObjectError extends DefaultMessageSourceResolvable {
if (sourceType.isInstance(this.source)) {
return sourceType.cast(this.source);
}
else if (this.source instanceof Throwable) {
Throwable cause = ((Throwable) this.source).getCause();
else if (this.source instanceof Throwable throwable) {
Throwable cause = throwable.getCause();
if (sourceType.isInstance(cause)) {
return sourceType.cast(cause);
}
@@ -126,7 +126,7 @@ public class ObjectError extends DefaultMessageSourceResolvable {
*/
public boolean contains(Class<?> sourceType) {
return (sourceType.isInstance(this.source) ||
(this.source instanceof Throwable && sourceType.isInstance(((Throwable) this.source).getCause())));
(this.source instanceof Throwable throwable && sourceType.isInstance(throwable.getCause())));
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -82,8 +82,8 @@ public abstract class ValidationUtils {
"Validator [" + validator.getClass() + "] does not support [" + target.getClass() + "]");
}
if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {
((SmartValidator) validator).validate(target, errors, validationHints);
if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator smartValidator) {
smartValidator.validate(target, errors, validationHints);
}
else {
validator.validate(target, errors);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -64,7 +64,7 @@ public abstract class ValidationAnnotationUtils {
if (hints == null) {
return EMPTY_OBJECT_ARRAY;
}
return (hints instanceof Object[] ? (Object[]) hints : new Object[]{hints});
return (hints instanceof Object[] objectHints ? objectHints : new Object[] {hints});
}
}
@@ -30,6 +30,8 @@ import jakarta.validation.metadata.MethodDescriptor;
import jakarta.validation.metadata.MethodType;
import jakarta.validation.metadata.ParameterDescriptor;
import jakarta.validation.metadata.PropertyDescriptor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.MemberCategory;
@@ -37,6 +39,7 @@ import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
import org.springframework.beans.factory.aot.BeanRegistrationCode;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.core.KotlinDetector;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
@@ -52,6 +55,8 @@ class BeanValidationBeanRegistrationAotProcessor implements BeanRegistrationAotP
private static final boolean isBeanValidationPresent = ClassUtils.isPresent(
"jakarta.validation.Validation", BeanValidationBeanRegistrationAotProcessor.class.getClassLoader());
private static final Log logger = LogFactory.getLog(BeanValidationBeanRegistrationAotProcessor.class);
@Nullable
@Override
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
@@ -67,7 +72,22 @@ class BeanValidationBeanRegistrationAotProcessor implements BeanRegistrationAotP
@Nullable
public static BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
BeanDescriptor descriptor = validator.getConstraintsForClass(registeredBean.getBeanClass());
BeanDescriptor descriptor;
try {
descriptor = validator.getConstraintsForClass(registeredBean.getBeanClass());
}
catch (RuntimeException ex) {
if (KotlinDetector.isKotlinType(registeredBean.getBeanClass()) && ex instanceof ArrayIndexOutOfBoundsException) {
// See https://hibernate.atlassian.net/browse/HV-1796 and https://youtrack.jetbrains.com/issue/KT-40857
logger.warn("Skipping validation constraint hint inference for bean " + registeredBean.getBeanName() +
" due to an ArrayIndexOutOfBoundsException at validator level");
}
else {
logger.error("Skipping validation constraint hint inference for bean " +
registeredBean.getBeanName(), ex);
}
return null;
}
Set<ConstraintDescriptor<?>> constraintDescriptors = new HashSet<>();
for (MethodDescriptor methodDescriptor : descriptor.getConstrainedMethods(MethodType.NON_GETTER, MethodType.GETTER)) {
for (ParameterDescriptor parameterDescriptor : methodDescriptor.getParameterDescriptors()) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -77,10 +77,10 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Chris Beams
* @author Sam Brannen
*/
public class AspectJAutoProxyCreatorTests {
class AspectJAutoProxyCreatorTests {
@Test
public void testAspectsAreApplied() {
void aspectsAreApplied() {
ClassPathXmlApplicationContext bf = newContext("aspects.xml");
ITestBean tb = (ITestBean) bf.getBean("adrian");
@@ -91,7 +91,7 @@ public class AspectJAutoProxyCreatorTests {
}
@Test
public void testMultipleAspectsWithParameterApplied() {
void multipleAspectsWithParameterApplied() {
ClassPathXmlApplicationContext bf = newContext("aspects.xml");
ITestBean tb = (ITestBean) bf.getBean("adrian");
@@ -100,7 +100,7 @@ public class AspectJAutoProxyCreatorTests {
}
@Test
public void testAspectsAreAppliedInDefinedOrder() {
void aspectsAreAppliedInDefinedOrder() {
ClassPathXmlApplicationContext bf = newContext("aspectsWithOrdering.xml");
ITestBean tb = (ITestBean) bf.getBean("adrian");
@@ -108,7 +108,7 @@ public class AspectJAutoProxyCreatorTests {
}
@Test
public void testAspectsAndAdvisorAreApplied() {
void aspectsAndAdvisorAreApplied() {
ClassPathXmlApplicationContext ac = newContext("aspectsPlusAdvisor.xml");
ITestBean shouldBeWeaved = (ITestBean) ac.getBean("adrian");
@@ -116,7 +116,7 @@ public class AspectJAutoProxyCreatorTests {
}
@Test
public void testAspectsAndAdvisorAreAppliedEvenIfComingFromParentFactory() {
void aspectsAndAdvisorAreAppliedEvenIfComingFromParentFactory() {
ClassPathXmlApplicationContext ac = newContext("aspectsPlusAdvisor.xml");
GenericApplicationContext childAc = new GenericApplicationContext(ac);
@@ -153,7 +153,7 @@ public class AspectJAutoProxyCreatorTests {
}
@Test
public void testPerThisAspect() {
void perThisAspect() {
ClassPathXmlApplicationContext bf = newContext("perthis.xml");
ITestBean adrian1 = (ITestBean) bf.getBean("adrian");
@@ -173,7 +173,7 @@ public class AspectJAutoProxyCreatorTests {
}
@Test
public void testPerTargetAspect() throws SecurityException, NoSuchMethodException {
void perTargetAspect() throws SecurityException, NoSuchMethodException {
ClassPathXmlApplicationContext bf = newContext("pertarget.xml");
ITestBean adrian1 = (ITestBean) bf.getBean("adrian");
@@ -209,7 +209,7 @@ public class AspectJAutoProxyCreatorTests {
}
@Test
public void testTwoAdviceAspect() {
void twoAdviceAspect() {
ClassPathXmlApplicationContext bf = newContext("twoAdviceAspect.xml");
ITestBean adrian1 = (ITestBean) bf.getBean("adrian");
@@ -217,7 +217,7 @@ public class AspectJAutoProxyCreatorTests {
}
@Test
public void testTwoAdviceAspectSingleton() {
void twoAdviceAspectSingleton() {
ClassPathXmlApplicationContext bf = newContext("twoAdviceAspectSingleton.xml");
ITestBean adrian1 = (ITestBean) bf.getBean("adrian");
@@ -228,7 +228,7 @@ public class AspectJAutoProxyCreatorTests {
}
@Test
public void testTwoAdviceAspectPrototype() {
void twoAdviceAspectPrototype() {
ClassPathXmlApplicationContext bf = newContext("twoAdviceAspectPrototype.xml");
ITestBean adrian1 = (ITestBean) bf.getBean("adrian");
@@ -244,13 +244,13 @@ public class AspectJAutoProxyCreatorTests {
assertThat(adrian.age()).isEqualTo(start);
int newAge = 32;
adrian.setAge(newAge);
assertThat(adrian.age()).isEqualTo((start + increment));
assertThat(adrian.age()).isEqualTo(start + increment);
adrian.setAge(0);
assertThat(adrian.age()).isEqualTo((start + increment * 2));
assertThat(adrian.age()).isEqualTo(start + increment * 2);
}
@Test
public void testAdviceUsingJoinPoint() {
void adviceUsingJoinPoint() {
ClassPathXmlApplicationContext bf = newContext("usesJoinPointAspect.xml");
ITestBean adrian1 = (ITestBean) bf.getBean("adrian");
@@ -262,7 +262,7 @@ public class AspectJAutoProxyCreatorTests {
}
@Test
public void testIncludeMechanism() {
void includeMechanism() {
ClassPathXmlApplicationContext bf = newContext("usesInclude.xml");
ITestBean adrian = (ITestBean) bf.getBean("adrian");
@@ -271,7 +271,7 @@ public class AspectJAutoProxyCreatorTests {
}
@Test
public void testForceProxyTargetClass() {
void forceProxyTargetClass() {
ClassPathXmlApplicationContext bf = newContext("aspectsWithCGLIB.xml");
ProxyConfig pc = (ProxyConfig) bf.getBean(AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME);
@@ -280,7 +280,7 @@ public class AspectJAutoProxyCreatorTests {
}
@Test
public void testWithAbstractFactoryBeanAreApplied() {
void withAbstractFactoryBeanAreApplied() {
ClassPathXmlApplicationContext bf = newContext("aspectsWithAbstractBean.xml");
ITestBean adrian = (ITestBean) bf.getBean("adrian");
@@ -289,7 +289,7 @@ public class AspectJAutoProxyCreatorTests {
}
@Test
public void testRetryAspect() {
void retryAspect() {
ClassPathXmlApplicationContext bf = newContext("retryAspect.xml");
UnreliableBean bean = (UnreliableBean) bf.getBean("unreliableBean");
@@ -302,7 +302,7 @@ public class AspectJAutoProxyCreatorTests {
}
@Test
public void testWithBeanNameAutoProxyCreator() {
void withBeanNameAutoProxyCreator() {
ClassPathXmlApplicationContext bf = newContext("withBeanNameAutoProxyCreator.xml");
ITestBean tb = (ITestBean) bf.getBean("adrian");
@@ -571,11 +571,11 @@ class RetryAspect {
@SuppressWarnings("serial")
class RetryableException extends NestedRuntimeException {
public RetryableException(String msg) {
RetryableException(String msg) {
super(msg);
}
public RetryableException(String msg, Throwable cause) {
RetryableException(String msg, Throwable cause) {
super(msg, cause);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -518,6 +518,9 @@ class AnnotationConfigApplicationContextTests {
.withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS))
.accepts(runtimeHints);
assertThat(RuntimeHintsPredicates.reflection().onType(CglibConfiguration.class)
.withMemberCategories(MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_DECLARED_METHODS))
.accepts(runtimeHints);
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -233,6 +233,17 @@ class AutowiredConfigurationTests {
context.close();
}
@Test
void testValueInjectionWithRecord() {
System.setProperty("recordBeanName", "enigma");
try (GenericApplicationContext context = new AnnotationConfigApplicationContext(RecordBean.class)) {
assertThat(context.getBean(RecordBean.class).name()).isEqualTo("enigma");
}
finally {
System.clearProperty("recordBeanName");
}
}
private int contentLength() throws IOException {
return (int) new ClassPathResource("do_not_delete_me.txt").contentLength();
}
@@ -506,4 +517,8 @@ class AutowiredConfigurationTests {
}
}
record RecordBean(@Value("${recordBeanName}") String name) {
}
}
@@ -105,7 +105,7 @@ class BeanValidationBeanRegistrationAotProcessorTests {
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(Exists.List.class)
private @interface Exists {
@interface Exists {
String message() default "Does not exist";
@@ -121,7 +121,7 @@ class BeanValidationBeanRegistrationAotProcessorTests {
}
}
private static class ExistsValidator implements ConstraintValidator<Exists, String> {
static class ExistsValidator implements ConstraintValidator<Exists, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
@@ -129,7 +129,7 @@ class BeanValidationBeanRegistrationAotProcessorTests {
}
}
private static class MethodParameterLevelConstraint {
static class MethodParameterLevelConstraint {
@SuppressWarnings("unused")
public String hello(@Exists String name) {
@@ -139,7 +139,7 @@ class BeanValidationBeanRegistrationAotProcessorTests {
}
@SuppressWarnings("unused")
private static class ConstructorParameterLevelConstraint {
static class ConstructorParameterLevelConstraint {
private final String name;
@@ -154,7 +154,7 @@ class BeanValidationBeanRegistrationAotProcessorTests {
}
@SuppressWarnings("unused")
private static class PropertyLevelConstraint {
static class PropertyLevelConstraint {
@Exists
private String name;
@@ -0,0 +1,30 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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 test.aspect;
import org.aspectj.lang.annotation.Pointcut;
/**
* @author Sam Brannen
* @since 6.0.6
*/
public class CommonPointcuts {
@Pointcut("execution(* getAge())")
public void getAgeExecution() {}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2005 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,7 +20,7 @@ import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
@Aspect("perthis(execution(* getAge()))")
@Aspect("perthis(test.aspect.CommonPointcuts.getAgeExecution())")
public class PerThisAspect {
private int invocations = 0;
@@ -29,9 +29,9 @@ public class PerThisAspect {
return this.invocations;
}
@Around("execution(* getAge())")
public int changeAge(ProceedingJoinPoint pjp) throws Throwable {
return invocations++;
@Around("test.aspect.CommonPointcuts.getAgeExecution()")
public int changeAge(ProceedingJoinPoint pjp) {
return this.invocations++;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -0,0 +1,80 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.validation.beanvalidation
import org.assertj.core.api.Assertions
import org.junit.jupiter.api.Test
import org.mockito.Mockito
import org.springframework.aot.generate.GenerationContext
import org.springframework.aot.hint.MemberCategory
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates
import org.springframework.aot.test.generate.TestGenerationContext
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution
import org.springframework.beans.factory.support.DefaultListableBeanFactory
import org.springframework.beans.factory.support.RegisteredBean
import org.springframework.beans.factory.support.RootBeanDefinition
import org.springframework.validation.beanvalidation.BeanValidationBeanRegistrationAotProcessorTests.*
/**
* Kotlin tests for {@link BeanValidationBeanRegistrationAotProcessor}.
*
* @author Sebastien Deleuze
*/
class KotlinBeanValidationBeanRegistrationAotProcessorTests {
private val processor = BeanValidationBeanRegistrationAotProcessor()
private val generationContext: GenerationContext = TestGenerationContext()
@Test
fun shouldProcessMethodParameterLevelConstraint() {
process(MethodParameterLevelConstraint::class.java)
Assertions.assertThat(
RuntimeHintsPredicates.reflection().onType(ExistsValidator::class.java)
.withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)
).accepts(generationContext.runtimeHints)
}
@Test
fun shouldSkipMethodParameterLevelConstraintWihExtension() {
process(MethodParameterLevelConstraintWithExtension::class.java)
Assertions.assertThat(generationContext.runtimeHints.reflection().typeHints()).isEmpty()
}
private fun process(beanClass: Class<*>) {
val contribution = createContribution(beanClass)
contribution?.applyTo(generationContext, Mockito.mock())
}
private fun createContribution(beanClass: Class<*>): BeanRegistrationAotContribution? {
val beanFactory = DefaultListableBeanFactory()
beanFactory.registerBeanDefinition(beanClass.name, RootBeanDefinition(beanClass))
return processor.processAheadOfTime(RegisteredBean.of(beanFactory, beanClass.name))
}
internal class MethodParameterLevelConstraintWithExtension {
@Suppress("unused")
fun hello(name: @Exists String): String {
return name.toHello()
}
private fun String.toHello() =
"Hello $this"
}
}
+1
View File
@@ -86,6 +86,7 @@ dependencies {
testImplementation("com.squareup.okhttp3:mockwebserver")
testImplementation("org.jetbrains.kotlinx:kotlinx-serialization-json")
testImplementation("com.fasterxml.jackson.core:jackson-databind")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
testFixturesImplementation("com.google.code.findbugs:jsr305")
testFixturesImplementation("org.junit.platform:junit-platform-launcher")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api")
@@ -74,7 +74,7 @@ public class BindingReflectionHintsRegistrar {
}
private boolean shouldSkipMembers(Class<?> type) {
return (type.getCanonicalName() != null && type.getCanonicalName().startsWith("java.")) || type.isArray();
return type.getCanonicalName().startsWith("java.") || type.isArray();
}
private void registerReflectionHints(ReflectionHints hints, Set<Type> seen, Type type) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@ import java.util.stream.Stream;
import org.springframework.aot.hint.TypeHint.Builder;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
@@ -36,6 +37,7 @@ import org.springframework.util.ClassUtils;
* @author Stephane Nicoll
* @author Phillip Webb
* @author Andy Wilkinson
* @author Sebastien Deleuze
* @since 6.0
*/
public class ReflectionHints {
@@ -106,7 +108,11 @@ public class ReflectionHints {
* @see #registerType(Class, MemberCategory...)
*/
public ReflectionHints registerType(Class<?> type, Consumer<TypeHint.Builder> typeHint) {
return registerType(TypeReference.of(type), typeHint);
Assert.notNull(type, "'type' must not be null");
if (type.getCanonicalName() != null) {
registerType(TypeReference.of(type), typeHint);
}
return this;
}
/**
@@ -117,7 +123,11 @@ public class ReflectionHints {
* @return {@code this}, to facilitate method chaining
*/
public ReflectionHints registerType(Class<?> type, MemberCategory... memberCategories) {
return registerType(TypeReference.of(type), memberCategories);
Assert.notNull(type, "'type' must not be null");
if (type.getCanonicalName() != null) {
registerType(TypeReference.of(type), memberCategories);
}
return this;
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,11 +17,13 @@
package org.springframework.aot.hint;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* A {@link TypeReference} based on a {@link Class}.
*
* @author Stephane Nicoll
* @author Sebastien Deleuze
* @since 6.0
*/
final class ReflectionTypeReference extends AbstractTypeReference {
@@ -41,6 +43,8 @@ final class ReflectionTypeReference extends AbstractTypeReference {
}
static ReflectionTypeReference of(Class<?> type) {
Assert.notNull(type, "'type' must not be null");
Assert.notNull(type.getCanonicalName(), "'type.getCanonicalName()' must not be null");
return new ReflectionTypeReference(type);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@ import org.springframework.lang.Nullable;
* a {@link Class} yet.
*
* @author Stephane Nicoll
* @author Sebastien Deleuze
* @since 6.0
*/
public interface TypeReference {
@@ -68,6 +69,7 @@ public interface TypeReference {
* Create an instance based on the specified type.
* @param type the type to wrap
* @return a type reference for the specified type
* @throws IllegalArgumentException if the specified type {@linkplain Class#getCanonicalName() canonical name} is {@code null}
*/
static TypeReference of(Class<?> type) {
return ReflectionTypeReference.of(type);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,6 +34,7 @@ import org.springframework.util.ClassUtils;
* on the JDK and CGLIB proxy facilities and their core conventions.
*
* @author Juergen Hoeller
* @author Sebastien Deleuze
* @since 6.0.3
* @see org.springframework.aot.hint.ProxyHints
* @see org.springframework.aot.hint.ReflectionHints
@@ -46,7 +47,8 @@ public abstract class ClassHintUtils {
MemberCategory.DECLARED_FIELDS);
private static final Consumer<TypeHint.Builder> asProxiedUserClass = hint ->
hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS);
hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS,
MemberCategory.INVOKE_DECLARED_METHODS);
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -41,6 +41,8 @@ import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.util.Assert;
/**
* Utilities for working with Kotlin Coroutines.
*
@@ -68,13 +70,14 @@ public abstract class CoroutinesUtils {
}
/**
* Invoke a suspending function and converts it to {@link Mono} or
* {@link Flux}. Uses an {@linkplain Dispatchers#getUnconfined() unconfined}
* dispatcher.
* Invoke a suspending function and converts it to {@link Mono} or {@link Flux}.
* Uses an {@linkplain Dispatchers#getUnconfined() unconfined} dispatcher.
* @param method the suspending function to invoke
* @param target the target to invoke {@code method} on
* @param args the function arguments
* @param args the function arguments. If the {@code Continuation} argument is specified as the last argument
* (typically {@code null}), it is ignored.
* @return the method invocation result as reactive stream
* @throws IllegalArgumentException if {@code method} is not a suspending function
*/
public static Publisher<?> invokeSuspendingFunction(Method method, Object target,
Object... args) {
@@ -87,20 +90,22 @@ public abstract class CoroutinesUtils {
* @param context the coroutine context to use
* @param method the suspending function to invoke
* @param target the target to invoke {@code method} on
* @param args the function arguments
* @param args the function arguments. If the {@code Continuation} argument is specified as the last argument
* (typically {@code null}), it is ignored.
* @return the method invocation result as reactive stream
* @throws IllegalArgumentException if {@code method} is not a suspending function
* @since 6.0
*/
@SuppressWarnings("deprecation")
public static Publisher<?> invokeSuspendingFunction(CoroutineContext context, Method method, Object target,
Object... args) {
Assert.isTrue(KotlinDetector.isSuspendingFunction(method), "'method' must be a suspending function");
KFunction<?> function = Objects.requireNonNull(ReflectJvmMapping.getKotlinFunction(method));
if (method.isAccessible() && !KCallablesJvm.isAccessible(function)) {
KCallablesJvm.setAccessible(function, true);
}
Mono<Object> mono = MonoKt.mono(context, (scope, continuation) ->
KCallables.callSuspend(function, getSuspendedFunctionArgs(target, args), continuation))
KCallables.callSuspend(function, getSuspendedFunctionArgs(method, target, args), continuation))
.filter(result -> !Objects.equals(result, Unit.INSTANCE))
.onErrorMap(InvocationTargetException.class, InvocationTargetException::getTargetException);
@@ -120,10 +125,11 @@ public abstract class CoroutinesUtils {
return mono;
}
private static Object[] getSuspendedFunctionArgs(Object target, Object... args) {
Object[] functionArgs = new Object[args.length];
private static Object[] getSuspendedFunctionArgs(Method method, Object target, Object... args) {
int length = (args.length == method.getParameterCount() - 1 ? args.length + 1 : args.length);
Object[] functionArgs = new Object[length];
functionArgs[0] = target;
System.arraycopy(args, 0, functionArgs, 1, args.length - 1);
System.arraycopy(args, 0, functionArgs, 1, length - 1);
return functionArgs;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -762,7 +762,7 @@ public class ResolvableType implements Serializable {
/**
* Convenience method that will {@link #getGeneric(int...) get} and
* {@link #resolve() resolve} a specific generic parameters.
* {@link #resolve() resolve} a specific generic parameter.
* @param indexes the indexes that refer to the generic parameter
* (may be omitted to return the first generic)
* @return a resolved {@link Class} or {@code null}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -56,8 +56,7 @@ public abstract class AbstractFileResolvingResource extends AbstractResource {
// Try a URL connection content-length header
URLConnection con = url.openConnection();
customizeConnection(con);
HttpURLConnection httpCon =
(con instanceof HttpURLConnection ? (HttpURLConnection) con : null);
HttpURLConnection httpCon = (con instanceof HttpURLConnection huc ? huc : null);
if (httpCon != null) {
httpCon.setRequestMethod("HEAD");
int code = httpCon.getResponseCode();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -226,9 +226,9 @@ public abstract class AbstractResource implements Resource {
* @see #getDescription()
*/
@Override
public boolean equals(@Nullable Object other) {
return (this == other || (other instanceof Resource &&
((Resource) other).getDescription().equals(getDescription())));
public boolean equals(@Nullable Object obj) {
return (this == obj || (obj instanceof Resource that &&
getDescription().equals(that.getDescription())));
}
/**
@@ -129,9 +129,9 @@ public class ByteArrayResource extends AbstractResource {
* @see java.util.Arrays#equals(byte[], byte[])
*/
@Override
public boolean equals(@Nullable Object other) {
return (this == other || (other instanceof ByteArrayResource &&
Arrays.equals(((ByteArrayResource) other).byteArray, this.byteArray)));
public boolean equals(@Nullable Object obj) {
return (this == obj || (obj instanceof ByteArrayResource that &&
Arrays.equals(this.byteArray, that.byteArray)));
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -272,9 +272,9 @@ public class ClassPathResource extends AbstractFileResolvingResource {
if (this == obj) {
return true;
}
return ((obj instanceof ClassPathResource other) &&
this.absolutePath.equals(other.absolutePath) &&
ObjectUtils.nullSafeEquals(getClassLoader(), other.getClassLoader()));
return ((obj instanceof ClassPathResource that) &&
this.absolutePath.equals(that.absolutePath) &&
ObjectUtils.nullSafeEquals(getClassLoader(), that.getClassLoader()));
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -72,9 +72,9 @@ public class DescriptiveResource extends AbstractResource {
* This implementation compares the underlying description String.
*/
@Override
public boolean equals(@Nullable Object other) {
return (this == other || (other instanceof DescriptiveResource &&
((DescriptiveResource) other).description.equals(this.description)));
public boolean equals(@Nullable Object obj) {
return (this == obj || (obj instanceof DescriptiveResource that &&
this.description.equals(that.description)));
}
/**
@@ -396,8 +396,7 @@ public class FileSystemResource extends AbstractResource implements WritableReso
*/
@Override
public boolean equals(@Nullable Object obj) {
return (this == obj || (obj instanceof FileSystemResource other &&
this.path.equals(other.path)));
return (this == obj || (obj instanceof FileSystemResource that && this.path.equals(that.path)));
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -115,9 +115,9 @@ public class InputStreamResource extends AbstractResource {
* This implementation compares the underlying InputStream.
*/
@Override
public boolean equals(@Nullable Object other) {
return (this == other || (other instanceof InputStreamResource &&
((InputStreamResource) other).inputStream.equals(this.inputStream)));
public boolean equals(@Nullable Object obj) {
return (this == obj || (obj instanceof InputStreamResource that &&
this.inputStream.equals(that.inputStream)));
}
/**
@@ -298,9 +298,8 @@ public class PathResource extends AbstractResource implements WritableResource {
* This implementation compares the underlying Path references.
*/
@Override
public boolean equals(@Nullable Object other) {
return (this == other || (other instanceof PathResource &&
this.path.equals(((PathResource) other).path)));
public boolean equals(@Nullable Object obj) {
return (this == obj || (obj instanceof PathResource that && this.path.equals(that.path)));
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -343,9 +343,9 @@ public class UrlResource extends AbstractFileResolvingResource {
* This implementation compares the underlying URL references.
*/
@Override
public boolean equals(@Nullable Object other) {
return (this == other || (other instanceof UrlResource resource &&
getCleanedUrl().equals(resource.getCleanedUrl())));
public boolean equals(@Nullable Object obj) {
return (this == obj || (obj instanceof UrlResource that &&
getCleanedUrl().equals(that.getCleanedUrl())));
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -130,9 +130,9 @@ public class VfsResource extends AbstractResource {
}
@Override
public boolean equals(@Nullable Object other) {
return (this == other || (other instanceof VfsResource &&
this.resource.equals(((VfsResource) other).resource)));
public boolean equals(@Nullable Object obj) {
return (this == obj || (obj instanceof VfsResource that &&
this.resource.equals(that.resource)));
}
@Override
@@ -405,8 +405,19 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
* @see #doFindPathMatchingFileResources
*/
protected Resource convertClassLoaderURL(URL url) {
return (ResourceUtils.URL_PROTOCOL_FILE.equals(url.getProtocol()) ?
new FileSystemResource(url.getPath()) : new UrlResource(url));
if (ResourceUtils.URL_PROTOCOL_FILE.equals(url.getProtocol())) {
try {
// URI decoding for special characters such as spaces.
return new FileSystemResource(ResourceUtils.toURI(url).getSchemeSpecificPart());
}
catch (URISyntaxException ex) {
// Fallback for URLs that are not valid URIs (should hardly ever happen).
return new FileSystemResource(url.getFile());
}
}
else {
return new UrlResource(url);
}
}
/**
@@ -748,8 +759,8 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
rootDirUri = rootDirResource.getURI();
}
catch (Exception ex) {
if (logger.isInfoEnabled()) {
logger.info("Failed to resolve %s as URI: %s".formatted(rootDirResource, ex));
if (logger.isWarnEnabled()) {
logger.warn("Failed to resolve directory [%s] as URI: %s".formatted(rootDirResource, ex));
}
return Collections.emptySet();
}
@@ -797,21 +808,11 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
Set<Resource> result = new LinkedHashSet<>();
try (Stream<Path> files = Files.walk(rootPath)) {
files.filter(isMatchingFile).sorted().forEach(file -> {
try {
result.add(new FileSystemResource(file));
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to convert file %s to an org.springframework.core.io.Resource: %s"
.formatted(file, ex));
}
}
});
files.filter(isMatchingFile).sorted().map(FileSystemResource::new).forEach(result::add);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to complete search in directory [%s] for files matching pattern [%s]: %s"
if (logger.isWarnEnabled()) {
logger.warn("Failed to search in directory [%s] for files matching pattern [%s]: %s"
.formatted(rootPath.toAbsolutePath(), subPattern, ex));
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,12 +22,13 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* A simple log message type for use with Commons Logging, allowing
* for convenient lazy resolution of a given {@link Supplier} instance
* (typically bound to a Java 8 lambda expression) or a printf-style
* format string ({@link String#format}) in its {@link #toString()}.
* A simple log message type for use with Commons Logging, allowing for convenient
* lazy resolution of a given {@link Supplier} instance (typically bound to a lambda
* expression) or a printf-style format string ({@link String#format}) in its
* {@link #toString()}.
*
* @author Juergen Hoeller
* @author Sebastien Deleuze
* @since 5.2
* @see #of(Supplier)
* @see #format(String, Object)
@@ -77,7 +78,7 @@ public abstract class LogMessage implements CharSequence {
/**
* Build a lazily resolving message from the given supplier.
* @param supplier the supplier (typically bound to a Java 8 lambda expression)
* @param supplier the supplier (typically bound to a lambda expression)
* @see #toString()
*/
public static LogMessage of(Supplier<? extends CharSequence> supplier) {
@@ -87,56 +88,61 @@ public abstract class LogMessage implements CharSequence {
/**
* Build a lazily formatted message from the given format string and argument.
* @param format the format string (following {@link String#format} rules)
* @param arg1 the argument
* @param arg1 the argument (can be {@code null})
* @see String#format(String, Object...)
*/
public static LogMessage format(String format, Object arg1) {
public static LogMessage format(String format, @Nullable Object arg1) {
return new FormatMessage1(format, arg1);
}
/**
* Build a lazily formatted message from the given format string and arguments.
* @param format the format string (following {@link String#format} rules)
* @param arg1 the first argument
* @param arg2 the second argument
* @param arg1 the first argument (can be {@code null})
* @param arg2 the second argument (can be {@code null})
* @see String#format(String, Object...)
*/
public static LogMessage format(String format, Object arg1, Object arg2) {
public static LogMessage format(String format, @Nullable Object arg1, @Nullable Object arg2) {
return new FormatMessage2(format, arg1, arg2);
}
/**
* Build a lazily formatted message from the given format string and arguments.
* @param format the format string (following {@link String#format} rules)
* @param arg1 the first argument
* @param arg2 the second argument
* @param arg3 the third argument
* @param arg1 the first argument (can be {@code null})
* @param arg2 the second argument (can be {@code null})
* @param arg3 the third argument (can be {@code null})
* @see String#format(String, Object...)
*/
public static LogMessage format(String format, Object arg1, Object arg2, Object arg3) {
public static LogMessage format(String format, @Nullable Object arg1, @Nullable Object arg2, @Nullable Object arg3) {
return new FormatMessage3(format, arg1, arg2, arg3);
}
/**
* Build a lazily formatted message from the given format string and arguments.
* @param format the format string (following {@link String#format} rules)
* @param arg1 the first argument
* @param arg2 the second argument
* @param arg3 the third argument
* @param arg4 the fourth argument
* @param arg1 the first argument (can be {@code null})
* @param arg2 the second argument (can be {@code null})
* @param arg3 the third argument (can be {@code null})
* @param arg4 the fourth argument (can be {@code null})
* @see String#format(String, Object...)
*/
public static LogMessage format(String format, Object arg1, Object arg2, Object arg3, Object arg4) {
public static LogMessage format(String format, @Nullable Object arg1, @Nullable Object arg2, @Nullable Object arg3,
@Nullable Object arg4) {
return new FormatMessage4(format, arg1, arg2, arg3, arg4);
}
/**
* Build a lazily formatted message from the given format string and varargs.
* <p>This varargs {@code format()} variant may be costly. You should therefore
* use the individual argument variants whenever possible;
* {@link #format(String, Object)}, {@link #format(String, Object, Object)}, etc.
* @param format the format string (following {@link String#format} rules)
* @param args the varargs array (costly, prefer individual arguments)
* @param args the varargs array (can be {@code null} and can contain {@code null}
* elements)
* @see String#format(String, Object...)
*/
public static LogMessage format(String format, Object... args) {
public static LogMessage format(String format, @Nullable Object... args) {
return new FormatMessageX(format, args);
}
@@ -170,9 +176,10 @@ public abstract class LogMessage implements CharSequence {
private static final class FormatMessage1 extends FormatMessage {
@Nullable
private final Object arg1;
FormatMessage1(String format, Object arg1) {
FormatMessage1(String format, @Nullable Object arg1) {
super(format);
this.arg1 = arg1;
}
@@ -186,11 +193,13 @@ public abstract class LogMessage implements CharSequence {
private static final class FormatMessage2 extends FormatMessage {
@Nullable
private final Object arg1;
@Nullable
private final Object arg2;
FormatMessage2(String format, Object arg1, Object arg2) {
FormatMessage2(String format, @Nullable Object arg1, @Nullable Object arg2) {
super(format);
this.arg1 = arg1;
this.arg2 = arg2;
@@ -205,13 +214,16 @@ public abstract class LogMessage implements CharSequence {
private static final class FormatMessage3 extends FormatMessage {
@Nullable
private final Object arg1;
@Nullable
private final Object arg2;
@Nullable
private final Object arg3;
FormatMessage3(String format, Object arg1, Object arg2, Object arg3) {
FormatMessage3(String format, @Nullable Object arg1, @Nullable Object arg2, @Nullable Object arg3) {
super(format);
this.arg1 = arg1;
this.arg2 = arg2;
@@ -227,15 +239,20 @@ public abstract class LogMessage implements CharSequence {
private static final class FormatMessage4 extends FormatMessage {
@Nullable
private final Object arg1;
@Nullable
private final Object arg2;
@Nullable
private final Object arg3;
@Nullable
private final Object arg4;
FormatMessage4(String format, Object arg1, Object arg2, Object arg3, Object arg4) {
FormatMessage4(String format, @Nullable Object arg1, @Nullable Object arg2, @Nullable Object arg3,
@Nullable Object arg4) {
super(format);
this.arg1 = arg1;
this.arg2 = arg2;
@@ -252,9 +269,10 @@ public abstract class LogMessage implements CharSequence {
private static final class FormatMessageX extends FormatMessage {
@Nullable
private final Object[] args;
FormatMessageX(String format, Object... args) {
FormatMessageX(String format, @Nullable Object... args) {
super(format);
this.args = args;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -219,6 +219,7 @@ public abstract class ResourceUtils {
"because it does not reside in the file system: " + resourceUrl);
}
try {
// URI decoding for special characters such as spaces.
return new File(toURI(resourceUrl).getSchemeSpecificPart());
}
catch (URISyntaxException ex) {
@@ -25,4 +25,4 @@ import kotlin.reflect.KClass
* @since 6.0.5
*/
fun JdkProxyHint.Builder.proxiedInterfaces(vararg proxiedInterfaces: KClass<*>) =
proxiedInterfaces(*proxiedInterfaces.map { it::class.java }.toTypedArray())
proxiedInterfaces(*proxiedInterfaces.map { it.java }.toTypedArray())
@@ -25,4 +25,4 @@ import kotlin.reflect.KClass
* @since 6.0.5
*/
fun ProxyHints.registerJdkProxy(vararg proxiedInterfaces: KClass<*>) =
registerJdkProxy(*proxiedInterfaces.map { it::class.java }.toTypedArray())
registerJdkProxy(*proxiedInterfaces.map { it.java }.toTypedArray())
@@ -34,6 +34,7 @@ import static org.mockito.Mockito.verifyNoInteractions;
* Tests for {@link ReflectionHints}.
*
* @author Stephane Nicoll
* @author Sebastien Deleuze
*/
class ReflectionHintsTests {
@@ -132,6 +133,16 @@ class ReflectionHintsTests {
assertThat(fieldHint.getName()).isEqualTo("field"));
}
@Test
void registerTypeIgnoresLambda() {
Runnable lambda = () -> { };
Consumer<TypeHint.Builder> hintBuilder = mock();
this.reflectionHints.registerType(lambda.getClass());
this.reflectionHints.registerType(lambda.getClass(), hintBuilder);
assertThat(this.reflectionHints.typeHints()).isEmpty();
verifyNoInteractions(hintBuilder);
}
private void assertTestTypeFieldHint(Consumer<FieldHint> fieldHint) {
assertThat(this.reflectionHints.typeHints()).singleElement().satisfies(typeHint -> {
assertThat(typeHint.getType().getCanonicalName()).isEqualTo(TestType.class.getCanonicalName());
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,11 +18,13 @@ package org.springframework.aot.hint;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.junit.jupiter.params.provider.Arguments.arguments;
/**
@@ -30,9 +32,21 @@ import static org.junit.jupiter.params.provider.Arguments.arguments;
*
* @author Stephane Nicoll
* @author Moritz Halbritter
* @author Sebastien Deleuze
*/
class ReflectionTypeReferenceTests {
@Test
void typeReferenceWithNullClass() {
assertThatIllegalArgumentException().isThrownBy(() -> ReflectionTypeReference.of(null));
}
@Test
void typeReferenceWithLambda() {
Runnable lambda = () -> { };
assertThatIllegalArgumentException().isThrownBy(() -> ReflectionTypeReference.of(lambda.getClass()));
}
@ParameterizedTest
@MethodSource("reflectionTargetNames")
void typeReferenceFromClassHasSuitableReflectionTargetName(Class<?> clazz, String binaryName) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -201,6 +201,14 @@ public class ReflectionHintsWriterTests {
""", hints);
}
@Test
void ignoreLambda() throws JSONException {
Runnable anonymousRunnable = () -> { };
ReflectionHints hints = new ReflectionHints();
hints.registerType(anonymousRunnable.getClass());
assertEquals("[]", hints);
}
private void assertEquals(String expectedString, ReflectionHints hints) throws JSONException {
StringWriter out = new StringWriter();
BasicJsonWriter writer = new BasicJsonWriter(out, "\t");
@@ -40,7 +40,6 @@ import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Named;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
@@ -49,9 +48,12 @@ import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.util.FileCopyUtils;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.junit.jupiter.api.Named.named;
import static org.junit.jupiter.params.provider.Arguments.arguments;
/**
* Unit tests for various {@link Resource} implementations.
@@ -67,11 +69,11 @@ class ResourceTests {
@MethodSource("resource")
void resourceIsValid(Resource resource) throws Exception {
assertThat(resource.getFilename()).isEqualTo("Resource.class");
assertThat(resource.getURL().getFile().endsWith("Resource.class")).isTrue();
assertThat(resource.getURL().getFile()).endsWith("Resource.class");
assertThat(resource.exists()).isTrue();
assertThat(resource.isReadable()).isTrue();
assertThat(resource.contentLength() > 0).isTrue();
assertThat(resource.lastModified() > 0).isTrue();
assertThat(resource.contentLength()).isGreaterThan(0);
assertThat(resource.lastModified()).isGreaterThan(0);
assertThat(resource.getContentAsByteArray()).containsExactly(Files.readAllBytes(Path.of(resource.getURI())));
}
@@ -83,8 +85,8 @@ class ResourceTests {
assertThat(relative1.getURL().getFile().endsWith("ClassPathResource.class")).isTrue();
assertThat(relative1.exists()).isTrue();
assertThat(relative1.isReadable()).isTrue();
assertThat(relative1.contentLength() > 0).isTrue();
assertThat(relative1.lastModified() > 0).isTrue();
assertThat(relative1.contentLength()).isGreaterThan(0);
assertThat(relative1.lastModified()).isGreaterThan(0);
}
@ParameterizedTest(name = "{index}: {0}")
@@ -92,11 +94,11 @@ class ResourceTests {
void resourceCreateRelativeWithFolder(Resource resource) throws Exception {
Resource relative2 = resource.createRelative("support/ResourcePatternResolver.class");
assertThat(relative2.getFilename()).isEqualTo("ResourcePatternResolver.class");
assertThat(relative2.getURL().getFile().endsWith("ResourcePatternResolver.class")).isTrue();
assertThat(relative2.getURL().getFile()).endsWith("ResourcePatternResolver.class");
assertThat(relative2.exists()).isTrue();
assertThat(relative2.isReadable()).isTrue();
assertThat(relative2.contentLength() > 0).isTrue();
assertThat(relative2.lastModified() > 0).isTrue();
assertThat(relative2.contentLength()).isGreaterThan(0);
assertThat(relative2.lastModified()).isGreaterThan(0);
}
@ParameterizedTest(name = "{index}: {0}")
@@ -104,11 +106,11 @@ class ResourceTests {
void resourceCreateRelativeWithDotPath(Resource resource) throws Exception {
Resource relative3 = resource.createRelative("../SpringVersion.class");
assertThat(relative3.getFilename()).isEqualTo("SpringVersion.class");
assertThat(relative3.getURL().getFile().endsWith("SpringVersion.class")).isTrue();
assertThat(relative3.getURL().getFile()).endsWith("SpringVersion.class");
assertThat(relative3.exists()).isTrue();
assertThat(relative3.isReadable()).isTrue();
assertThat(relative3.contentLength() > 0).isTrue();
assertThat(relative3.lastModified() > 0).isTrue();
assertThat(relative3.contentLength()).isGreaterThan(0);
assertThat(relative3.lastModified()).isGreaterThan(0);
}
@ParameterizedTest(name = "{index}: {0}")
@@ -122,21 +124,20 @@ class ResourceTests {
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(relative4::getInputStream);
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(relative4::readableChannel);
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(relative4::getContentAsByteArray);
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(
() -> relative4.getContentAsString(StandardCharsets.UTF_8));
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(() -> relative4.getContentAsString(UTF_8));
}
private static Stream<Arguments> resource() throws URISyntaxException {
URL resourceClass = ResourceTests.class.getResource("Resource.class");
Path resourceClassFilePath = Paths.get(resourceClass.toURI());
return Stream.of(
Arguments.of(Named.of("ClassPathResource", new ClassPathResource("org/springframework/core/io/Resource.class"))),
Arguments.of(Named.of("ClassPathResource with ClassLoader", new ClassPathResource("org/springframework/core/io/Resource.class", ResourceTests.class.getClassLoader()))),
Arguments.of(Named.of("ClassPathResource with Class", new ClassPathResource("Resource.class", ResourceTests.class))),
Arguments.of(Named.of("FileSystemResource", new FileSystemResource(resourceClass.getFile()))),
Arguments.of(Named.of("FileSystemResource with File", new FileSystemResource(new File(resourceClass.getFile())))),
Arguments.of(Named.of("FileSystemResource with File path", new FileSystemResource(resourceClassFilePath))),
Arguments.of(Named.of("UrlResource", new UrlResource(resourceClass)))
arguments(named("ClassPathResource", new ClassPathResource("org/springframework/core/io/Resource.class"))),
arguments(named("ClassPathResource with ClassLoader", new ClassPathResource("org/springframework/core/io/Resource.class", ResourceTests.class.getClassLoader()))),
arguments(named("ClassPathResource with Class", new ClassPathResource("Resource.class", ResourceTests.class))),
arguments(named("FileSystemResource", new FileSystemResource(resourceClass.getFile()))),
arguments(named("FileSystemResource with File", new FileSystemResource(new File(resourceClass.getFile())))),
arguments(named("FileSystemResource with File path", new FileSystemResource(resourceClassFilePath))),
arguments(named("UrlResource", new UrlResource(resourceClass)))
);
}
@@ -258,7 +259,7 @@ class ResourceTests {
ByteBuffer buffer = ByteBuffer.allocate((int) resource.contentLength());
channel.read(buffer);
buffer.rewind();
assertThat(buffer.limit() > 0).isTrue();
assertThat(buffer.limit()).isGreaterThan(0);
}
}
@@ -621,6 +621,7 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests {
buffer.write(new byte[]{'a', 'b', 'c'});
buffer.read(); // skip a
@SuppressWarnings("deprecation")
ByteBuffer result = buffer.toByteBuffer();
assertThat(result.capacity()).isEqualTo(2);
assertThat(result.remaining()).isEqualTo(2);
@@ -639,6 +640,7 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests {
DataBuffer buffer = createDataBuffer(3);
buffer.write(new byte[]{'a', 'b', 'c'});
@SuppressWarnings("deprecation")
ByteBuffer result = buffer.toByteBuffer(1, 2);
assertThat(result.capacity()).isEqualTo(2);
assertThat(result.remaining()).isEqualTo(2);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
@@ -29,6 +30,7 @@ import java.util.stream.Collectors;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.util.StringUtils;
@@ -59,7 +61,7 @@ class PathMatchingResourcePatternResolverTests {
private static final String[] CLASSES_IN_REACTOR_UTIL_ANNOTATION = { "NonNull.class", "NonNullApi.class", "Nullable.class" };
private final PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
private PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
@Nested
@@ -88,8 +90,16 @@ class PathMatchingResourcePatternResolverTests {
assertFilenames(pattern, expectedFilenames);
}
@Test
void encodedHashtagInPath() throws IOException {
Path rootDir = Paths.get("src/test/resources/custom%23root").toAbsolutePath();
URL root = new URL("file:" + rootDir + "/");
resolver = new PathMatchingResourcePatternResolver(new DefaultResourceLoader(new URLClassLoader(new URL[] {root})));
assertExactFilenames("classpath*:scanned/*.txt", "resource#test1.txt", "resource#test2.txt");
}
@Nested
class WithHashtagsInTheirFileNames {
class WithHashtagsInTheirFilenames {
@Test
void usingClasspathStarProtocol() {
@@ -34,7 +34,7 @@ class JdkProxyHintExtensionsTests {
fun `proxiedInterfaces builder extension`() {
every { builder.proxiedInterfaces(*anyVararg<Class<*>>()) } returns builder
builder.proxiedInterfaces(String::class, Int::class)
verify { builder.proxiedInterfaces(*anyVararg<Class<*>>()) }
verify { builder.proxiedInterfaces(String::class.java, Int::class.java) }
}
}
@@ -34,7 +34,7 @@ class ProxyHintsExtensionsTests {
fun `registerJdkProxy extension`() {
every { proxyHints.registerJdkProxy(*anyVararg<Class<*>>()) } returns proxyHints
proxyHints.registerJdkProxy(String::class, Int::class)
verify { proxyHints.registerJdkProxy(*anyVararg<Class<*>>()) }
verify { proxyHints.registerJdkProxy(String::class.java, Int::class.java) }
}
}
@@ -0,0 +1,144 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.core
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.reactor.awaitSingle
import org.assertj.core.api.Assertions
import org.junit.jupiter.api.Test
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import reactor.test.StepVerifier
import kotlin.coroutines.Continuation
import kotlin.coroutines.coroutineContext
class KotlinCoroutinesUtilsTests {
@Test
fun deferredToMono() {
runBlocking {
val deferred: Deferred<String> = async(Dispatchers.IO) {
delay(10)
"foo"
}
val mono = CoroutinesUtils.deferredToMono(deferred)
StepVerifier.create(mono)
.expectNext("foo")
.expectComplete()
.verify()
}
}
@Test
fun monoToDeferred() {
runBlocking {
val mono = Mono.just("foo")
val deferred = CoroutinesUtils.monoToDeferred(mono)
Assertions.assertThat(deferred.await()).isEqualTo("foo")
}
}
@Test
fun invokeSuspendingFunctionWithNullContinuationParameter() {
val method = KotlinCoroutinesUtilsTests::class.java.getDeclaredMethod("suspendingFunction", String::class.java, Continuation::class.java)
val publisher = CoroutinesUtils.invokeSuspendingFunction(method, this, "foo", null)
Assertions.assertThat(publisher).isInstanceOf(Mono::class.java)
StepVerifier.create(publisher)
.expectNext("foo")
.expectComplete()
.verify()
}
@Test
fun invokeSuspendingFunctionWithoutContinuationParameter() {
val method = KotlinCoroutinesUtilsTests::class.java.getDeclaredMethod("suspendingFunction", String::class.java, Continuation::class.java)
val publisher = CoroutinesUtils.invokeSuspendingFunction(method, this, "foo")
Assertions.assertThat(publisher).isInstanceOf(Mono::class.java)
StepVerifier.create(publisher)
.expectNext("foo")
.expectComplete()
.verify()
}
@Test
fun invokeNonSuspendingFunction() {
val method = KotlinCoroutinesUtilsTests::class.java.getDeclaredMethod("nonSuspendingFunction", String::class.java)
Assertions.assertThatIllegalArgumentException().isThrownBy { CoroutinesUtils.invokeSuspendingFunction(method, this, "foo") }
}
@Test
fun invokeSuspendingFunctionWithFlow() {
val method = KotlinCoroutinesUtilsTests::class.java.getDeclaredMethod("suspendingFunctionWithFlow", Continuation::class.java)
val publisher = CoroutinesUtils.invokeSuspendingFunction(method, this)
Assertions.assertThat(publisher).isInstanceOf(Flux::class.java)
StepVerifier.create(publisher)
.expectNext("foo")
.expectNext("bar")
.expectComplete()
.verify()
}
@Test
fun invokeSuspendingFunctionWithNullContinuationParameterAndContext() {
val method = KotlinCoroutinesUtilsTests::class.java.getDeclaredMethod("suspendingFunctionWithContext", String::class.java, Continuation::class.java)
val context = CoroutineName("name")
val mono = CoroutinesUtils.invokeSuspendingFunction(context, method, this, "foo", null) as Mono
runBlocking {
Assertions.assertThat(mono.awaitSingle()).isEqualTo("foo")
}
}
@Test
fun invokeSuspendingFunctionWithoutContinuationParameterAndContext() {
val method = KotlinCoroutinesUtilsTests::class.java.getDeclaredMethod("suspendingFunctionWithContext", String::class.java, Continuation::class.java)
val context = CoroutineName("name")
val mono = CoroutinesUtils.invokeSuspendingFunction(context, method, this, "foo") as Mono
runBlocking {
Assertions.assertThat(mono.awaitSingle()).isEqualTo("foo")
}
}
@Test
fun invokeNonSuspendingFunctionWithContext() {
val method = KotlinCoroutinesUtilsTests::class.java.getDeclaredMethod("nonSuspendingFunction", String::class.java)
val context = CoroutineName("name")
Assertions.assertThatIllegalArgumentException().isThrownBy { CoroutinesUtils.invokeSuspendingFunction(context, method, this, "foo") }
}
suspend fun suspendingFunction(value: String): String {
delay(10)
return value
}
suspend fun suspendingFunctionWithFlow(): Flow<String> {
delay(10)
return flowOf("foo", "bar")
}
fun nonSuspendingFunction(value: String): String {
return value
}
suspend fun suspendingFunctionWithContext(value: String): String {
delay(10)
Assertions.assertThat(coroutineContext[CoroutineName]?.name).isEqualTo("name")
return value
}
}
@@ -0,0 +1 @@
test 1
@@ -0,0 +1 @@
test 2
@@ -1,9 +1,26 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.expression.spel
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.expression.ExpressionParser
import org.springframework.expression.spel.standard.SpelExpressionParser
import kotlin.coroutines.Continuation
class KotlinSpelReproTests {
@@ -18,11 +35,40 @@ class KotlinSpelReproTests {
assertThat(expr.getValue(context)).isEqualTo("test")
}
@Test
fun `gh-26867 SpEL can process Kotlin regular function parameter`() {
val expr = parser.parseExpression("#key.startsWith('hello')")
context.registerFunction("get", Config::class.java.getMethod("get", String::class.java))
context.setVariable("key", "hello world")
assertThat(expr.getValue(context, Boolean::class.java)).isTrue()
context.setVariable("key", "")
assertThat(expr.getValue(context, Boolean::class.java)).isFalse()
}
@Test
fun `gh-26867 SpEL can process Kotlin suspending function parameter`() {
val expr = parser.parseExpression("#key.startsWith('hello')")
context.registerFunction("suspendingGet", Config::class.java.getMethod("suspendingGet", String::class.java, Continuation::class.java))
context.setVariable("key", "hello world")
assertThat(expr .getValue(context, Boolean::class.java)).isTrue()
context.setVariable("key", "")
assertThat(expr.getValue(context, Boolean::class.java)).isFalse()
}
@Suppress("UNUSED_PARAMETER")
class Config {
fun kotlinSupplier(): () -> String {
return { "test" }
}
fun get(key: String): Any {
throw NotImplementedError()
}
suspend fun suspendingGet(key: String) {
throw NotImplementedError()
}
}
}
@@ -346,8 +346,26 @@ public class SingleConnectionFactory implements ConnectionFactory, QueueConnecti
if (this.connection != null) {
closeConnection(this.connection);
}
this.connection = doCreateConnection();
prepareConnection(this.connection);
// Create new (method local) connection, which is later assigned to instance connection
// - prevention to hold instance connection without exception listener, in case when
// some subsequent methods (after creation of connection) throws JMSException
Connection con = doCreateConnection();
try {
prepareConnection(con);
this.connection = con;
}
catch (JMSException ex) {
// Attempt to close new (not used) connection to release possible resources
try {
con.close();
}
catch(Throwable th) {
if (logger.isDebugEnabled()) {
logger.debug("Could not close newly obtained JMS Connection that failed to prepare", th);
}
}
throw ex;
}
if (this.startedCount > 0) {
this.connection.start();
}
@@ -16,6 +16,9 @@
package org.springframework.jms.connection;
import java.lang.reflect.Field;
import java.util.concurrent.atomic.AtomicInteger;
import jakarta.jms.Connection;
import jakarta.jms.ConnectionFactory;
import jakarta.jms.ExceptionListener;
@@ -29,7 +32,10 @@ import jakarta.jms.TopicConnectionFactory;
import jakarta.jms.TopicSession;
import org.junit.jupiter.api.Test;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@@ -341,6 +347,76 @@ public class SingleConnectionFactoryTests {
assertThat(listener.getCount()).isEqualTo(1);
}
@Test
public void testWithConnectionFactoryAndExceptionListenerAndReconnectOnExceptionWithJMSException() throws Exception {
// Throws JMSException on setExceptionListener() method, but only at the first time
class FailingTestConnection extends TestConnection {
private int setExceptionListenerInvocationCounter;
@Override
public void setExceptionListener(ExceptionListener exceptionListener) throws JMSException {
setExceptionListenerInvocationCounter++;
// Throw JMSException on first invocation
if (setExceptionListenerInvocationCounter == 1) {
throw new JMSException("Test JMSException (setExceptionListener())");
}
super.setExceptionListener(exceptionListener);
}
}
// Prepare base JMS ConnectionFactory
// - createConnection(1st) -> TestConnection,
// - createConnection(2nd and next) -> FailingTestConnection
TestConnection testCon = new TestConnection();
FailingTestConnection failingCon = new FailingTestConnection();
AtomicInteger createConnectionMethodCounter = new AtomicInteger();
ConnectionFactory cf = mock(ConnectionFactory.class);
given(cf.createConnection()).willAnswer(invocation -> {
int methodInvocationCounter = createConnectionMethodCounter.incrementAndGet();
return methodInvocationCounter == 1 ? testCon : failingCon;
});
// Prepare SingleConnectionFactory (setReconnectOnException())
// - internal connection exception listener should be registered
SingleConnectionFactory scf = new SingleConnectionFactory(cf);
scf.setReconnectOnException(true);
Field conField = ReflectionUtils.findField(SingleConnectionFactory.class, "connection");
conField.setAccessible(true);
// Get connection (1st)
Connection con1 = scf.getConnection();
assertThat(createConnectionMethodCounter.get()).isEqualTo(1);
assertThat(con1).isNotNull();
assertThat(con1.getExceptionListener()).isNotNull();
assertThat(con1).isSameAs(testCon);
// Get connection again, the same should be returned (shared connection till some problem)
Connection con2 = scf.getConnection();
assertThat(createConnectionMethodCounter.get()).isEqualTo(1);
assertThat(con2.getExceptionListener()).isNotNull();
assertThat(con2).isSameAs(con1);
// Invoke reset connection to simulate problem with connection
// - SCF exception listener should be invoked -> connection should be set to null
// - next attempt to invoke getConnection() must create new connection
scf.resetConnection();
assertThat(conField.get(scf)).isNull();
// Attempt to get connection again
// - JMSException should be returned from FailingTestConnection
// - connection should be still null (no new connection without exception listener like before fix)
assertThatExceptionOfType(JMSException.class).isThrownBy(() -> scf.getConnection());
assertThat(createConnectionMethodCounter.get()).isEqualTo(2);
assertThat(conField.get(scf)).isNull();
// Attempt to get connection again -> FailingTestConnection should be returned
// - no JMSException is thrown, exception listener should be present
Connection con3 = scf.getConnection();
assertThat(createConnectionMethodCounter.get()).isEqualTo(3);
assertThat(con3).isNotNull();
assertThat(con3).isSameAs(failingCon);
assertThat(con3.getExceptionListener()).isNotNull();
}
@Test
public void testWithConnectionFactoryAndLocalExceptionListenerWithCleanup() throws JMSException {
ConnectionFactory cf = mock();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,6 +22,8 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.aot.hint.annotation.Reflective;
/**
* Annotation for handling exceptions thrown from message-handling methods within a
* specific handler class.
@@ -32,6 +34,7 @@ import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Reflective(MessageMappingReflectiveProcessor.class)
public @interface MessageExceptionHandler {
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -33,20 +33,24 @@ import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.MessageHeaderAccessor;
/**
* {@link ReflectiveProcessor} implementation for {@link MessageMapping}
* annotated types. In addition to registering reflection hints for invoking
* {@link ReflectiveProcessor} implementation for types annotated
* with {@link MessageMapping @MessageMapping},
* {@link org.springframework.messaging.simp.annotation.SubscribeMapping @SubscribeMapping}
* and {@link MessageExceptionHandler @MessageExceptionHandler}.
* In addition to registering reflection hints for invoking
* the annotated method, this implementation handles:
*
* <ul>
* <li>Return types</li>
* <li>Parameters identified as potential payloads</li>
* <li>{@link Message} parameters</li>
* <li>Exception classes specified via {@link MessageExceptionHandler @MessageExceptionHandler}</li>
* </ul>
*
* @author Sebastien Deleuze
* @since 6.0
*/
class MessageMappingReflectiveProcessor implements ReflectiveProcessor {
public class MessageMappingReflectiveProcessor implements ReflectiveProcessor {
private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar();
@@ -58,6 +62,9 @@ class MessageMappingReflectiveProcessor implements ReflectiveProcessor {
}
else if (element instanceof Method method) {
registerMethodHints(hints, method);
if (element.isAnnotationPresent(MessageExceptionHandler.class)) {
registerMessageExceptionHandlerHints(hints, element.getAnnotation(MessageExceptionHandler.class));
}
}
}
@@ -84,6 +91,12 @@ class MessageMappingReflectiveProcessor implements ReflectiveProcessor {
}
}
protected void registerMessageExceptionHandlerHints(ReflectionHints hints, MessageExceptionHandler annotation) {
for (Class<?> exceptionClass : annotation.value()) {
hints.registerType(exceptionClass);
}
}
protected boolean couldBePayload(MethodParameter methodParameter) {
return !methodParameter.hasParameterAnnotation(DestinationVariable.class) &&
!methodParameter.hasParameterAnnotation(Header.class) &&
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -170,13 +170,13 @@ public class PayloadMethodArgumentResolver implements HandlerMethodArgumentResol
@SuppressWarnings("unchecked")
private Flux<DataBuffer> extractContent(MethodParameter parameter, Message<?> message) {
Object payload = message.getPayload();
if (payload instanceof DataBuffer) {
return Flux.just((DataBuffer) payload);
if (payload instanceof DataBuffer dataBuffer) {
return Flux.just(dataBuffer);
}
if (payload instanceof Publisher) {
return Flux.from((Publisher<?>) payload).map(value -> {
if (value instanceof DataBuffer) {
return (DataBuffer) value;
if (payload instanceof Publisher<?> publisher) {
return Flux.from(publisher).map(value -> {
if (value instanceof DataBuffer dataBuffer) {
return dataBuffer;
}
String className = value.getClass().getName();
throw getUnexpectedPayloadError(message, parameter, "Publisher<" + className + ">");
@@ -204,11 +204,11 @@ public class PayloadMethodArgumentResolver implements HandlerMethodArgumentResol
if (headerValue == null) {
return null;
}
else if (headerValue instanceof String) {
return MimeTypeUtils.parseMimeType((String) headerValue);
else if (headerValue instanceof String stringHeader) {
return MimeTypeUtils.parseMimeType(stringHeader);
}
else if (headerValue instanceof MimeType) {
return (MimeType) headerValue;
else if (headerValue instanceof MimeType mimeTypeHeader) {
return mimeTypeHeader;
}
else {
throw new IllegalArgumentException("Unexpected MimeType value: " + headerValue);
@@ -290,12 +290,12 @@ public class PayloadMethodArgumentResolver implements HandlerMethodArgumentResol
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
Object[] validationHints = (hints instanceof Object[] objectHints ? objectHints : new Object[] {hints});
String name = Conventions.getVariableNameForParameter(parameter);
return target -> {
BeanPropertyBindingResult bindingResult = new BeanPropertyBindingResult(target, name);
if (!ObjectUtils.isEmpty(validationHints) && this.validator instanceof SmartValidator) {
((SmartValidator) this.validator).validate(target, bindingResult, validationHints);
if (!ObjectUtils.isEmpty(validationHints) && this.validator instanceof SmartValidator sv) {
sv.validate(target, bindingResult, validationHints);
}
else {
this.validator.validate(target, bindingResult);
@@ -172,8 +172,8 @@ public class PayloadMethodArgumentResolver implements HandlerMethodArgumentResol
else if (payload instanceof byte[] bytes) {
return bytes.length == 0;
}
else if (payload instanceof String s) {
return !StringUtils.hasText(s);
else if (payload instanceof String text) {
return !StringUtils.hasText(text);
}
else if (payload instanceof Optional<?> optional) {
return optional.isEmpty();
@@ -216,11 +216,11 @@ public class PayloadMethodArgumentResolver implements HandlerMethodArgumentResol
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
Object[] validationHints = (hints instanceof Object[] objectHints ? objectHints : new Object[] {hints});
BeanPropertyBindingResult bindingResult =
new BeanPropertyBindingResult(target, getParameterName(parameter));
if (!ObjectUtils.isEmpty(validationHints) && this.validator instanceof SmartValidator) {
((SmartValidator) this.validator).validate(target, bindingResult, validationHints);
if (!ObjectUtils.isEmpty(validationHints) && this.validator instanceof SmartValidator sv) {
sv.validate(target, bindingResult, validationHints);
}
else {
this.validator.validate(target, bindingResult);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,6 +22,9 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.aot.hint.annotation.Reflective;
import org.springframework.messaging.handler.annotation.MessageMappingReflectiveProcessor;
/**
* Annotation for mapping subscription messages onto specific handler methods based
* on the destination of a subscription. Supported with STOMP over WebSocket only
@@ -54,6 +57,7 @@ import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Reflective(MessageMappingReflectiveProcessor.class)
public @interface SubscribeMapping {
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,16 +16,19 @@
package org.springframework.messaging.handler.annotation;
import java.io.IOException;
import java.lang.reflect.Method;
import java.security.Principal;
import org.junit.jupiter.api.Test;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.ReflectionHints;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.TypeReference;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.simp.annotation.SubscribeMapping;
import org.springframework.messaging.support.MessageHeaderAccessor;
import static org.assertj.core.api.Assertions.assertThat;
@@ -39,13 +42,13 @@ public class MessageMappingReflectiveProcessorTests {
private final MessageMappingReflectiveProcessor processor = new MessageMappingReflectiveProcessor();
private final ReflectionHints hints = new ReflectionHints();
private final RuntimeHints hints = new RuntimeHints();
@Test
void registerReflectiveHintsForMethodWithReturnValue() throws NoSuchMethodException {
Method method = SampleController.class.getDeclaredMethod("returnValue");
processor.registerReflectionHints(hints, method);
assertThat(hints.typeHints()).satisfiesExactlyInAnyOrder(
processor.registerReflectionHints(hints.reflection(), method);
assertThat(hints.reflection().typeHints()).satisfiesExactlyInAnyOrder(
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleController.class)),
typeHint -> {
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(OutgoingMessage.class));
@@ -62,8 +65,8 @@ public class MessageMappingReflectiveProcessorTests {
@Test
void registerReflectiveHintsForMethodWithExplicitPayload() throws NoSuchMethodException {
Method method = SampleController.class.getDeclaredMethod("explicitPayload", IncomingMessage.class);
processor.registerReflectionHints(hints, method);
assertThat(hints.typeHints()).satisfiesExactlyInAnyOrder(
processor.registerReflectionHints(hints.reflection(), method);
assertThat(hints.reflection().typeHints()).satisfiesExactlyInAnyOrder(
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleController.class)),
typeHint -> {
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(IncomingMessage.class));
@@ -80,8 +83,8 @@ public class MessageMappingReflectiveProcessorTests {
@Test
void registerReflectiveHintsForMethodWithImplicitPayload() throws NoSuchMethodException {
Method method = SampleController.class.getDeclaredMethod("implicitPayload", IncomingMessage.class);
processor.registerReflectionHints(hints, method);
assertThat(hints.typeHints()).satisfiesExactlyInAnyOrder(
processor.registerReflectionHints(hints.reflection(), method);
assertThat(hints.reflection().typeHints()).satisfiesExactlyInAnyOrder(
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleController.class)),
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(IncomingMessage.class)),
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(String.class)));
@@ -90,8 +93,8 @@ public class MessageMappingReflectiveProcessorTests {
@Test
void registerReflectiveHintsForMethodWithMessage() throws NoSuchMethodException {
Method method = SampleController.class.getDeclaredMethod("message", Message.class);
processor.registerReflectionHints(hints, method);
assertThat(hints.typeHints()).satisfiesExactlyInAnyOrder(
processor.registerReflectionHints(hints.reflection(), method);
assertThat(hints.reflection().typeHints()).satisfiesExactlyInAnyOrder(
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleController.class)),
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(IncomingMessage.class)),
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(String.class)));
@@ -102,8 +105,8 @@ public class MessageMappingReflectiveProcessorTests {
Method method = SampleController.class.getDeclaredMethod("implicitPayloadWithIgnoredAnnotations",
IncomingMessage.class, Ignored.class, Ignored.class, Ignored.class, MessageHeaders.class,
MessageHeaderAccessor.class, Principal.class);
processor.registerReflectionHints(hints, method);
assertThat(hints.typeHints()).satisfiesExactlyInAnyOrder(
processor.registerReflectionHints(hints.reflection(), method);
assertThat(hints.reflection().typeHints()).satisfiesExactlyInAnyOrder(
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleController.class)),
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(IncomingMessage.class)),
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(String.class)));
@@ -111,11 +114,26 @@ public class MessageMappingReflectiveProcessorTests {
@Test
void registerReflectiveHintsForClass() {
processor.registerReflectionHints(hints, SampleAnnotatedController.class);
assertThat(hints.typeHints()).singleElement().satisfies(
processor.registerReflectionHints(hints.reflection(), SampleAnnotatedController.class);
assertThat(hints.reflection().typeHints()).singleElement().satisfies(
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleAnnotatedController.class)));
}
@Test
void registerReflectiveHintsForMethodWithSubscribeMapping() throws NoSuchMethodException {
Method method = SampleController.class.getDeclaredMethod("handleSubscribe");
processor.registerReflectionHints(hints.reflection(), method);
assertThat(RuntimeHintsPredicates.reflection().onMethod(SampleController.class, "handleSubscribe")).accepts(hints);
}
@Test
void registerReflectiveHintsForMethodWithMessageExceptionHandler() throws NoSuchMethodException {
Method method = SampleController.class.getDeclaredMethod("handleIOException");
processor.registerReflectionHints(hints.reflection(), method);
assertThat(RuntimeHintsPredicates.reflection().onMethod(SampleController.class, "handleIOException")).accepts(hints);
assertThat(RuntimeHintsPredicates.reflection().onType(IOException.class)).accepts(hints);
}
static class SampleController {
@@ -145,6 +163,16 @@ public class MessageMappingReflectiveProcessorTests {
MessageHeaderAccessor messageHeaderAccessor,
Principal principal) {
}
@SubscribeMapping("/foo")
public String handleSubscribe() {
return "bar";
}
@MessageExceptionHandler(IOException.class)
public void handleIOException() {
}
}
@MessageMapping
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -211,6 +211,7 @@ class MetadataEncoderTests {
.encode()
.block();
@SuppressWarnings("deprecation")
ByteBuf byteBuf = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT)
.wrap(buffer.toByteBuffer())
.getNativeBuffer();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -272,7 +272,7 @@ public abstract class AbstractStompBrokerRelayIntegrationTests {
public void expectBrokerAvailabilityEvent(boolean isBrokerAvailable) throws InterruptedException {
BrokerAvailabilityEvent event = this.eventQueue.poll(20000, TimeUnit.MILLISECONDS);
assertThat(event).as("Times out waiting for BrokerAvailabilityEvent[" + isBrokerAvailable + "]").isNotNull();
assertThat(event).as("Timed out waiting for BrokerAvailabilityEvent[" + isBrokerAvailable + "]").isNotNull();
assertThat(event.isBrokerAvailable()).isEqualTo(isBrokerAvailable);
}
}

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