Compare commits

...

191 Commits

Author SHA1 Message Date
Spring Builds 8c85c3166c Release v5.3.31 2023-11-16 08:00:15 +00:00
Juergen Hoeller b28a5f858c Upgrade to Reactor 2020.0.38 and Netty 4.1.101
Includes RxJava 3.1.8, SmallRye Mutiny 1.9.0, BlockHound 1.0.8, OpenPDF 1.3.33, Checkstyle 10.12.5

Closes gh-31584
2023-11-15 14:25:03 +01:00
Juergen Hoeller e660859ec3 Consistent ordering of overloaded operations
(cherry picked from commit c373f496f3)
2023-11-15 14:15:47 +01:00
Stéphane Nicoll a6ab308cf4 Fix wrong nullability requirement
Closes gh-31611
2023-11-15 12:08:19 +01:00
Sam Brannen c489234cc2 Polish contribution
See gh-31598
See gh-31603

(cherry picked from commit e71117dcdf)
2023-11-14 15:31:32 +01:00
Jason af593588d3 Avoid duplicate resources in PathMatchingResourcePatternResolver on Windows
This commit updates PathMatchingResourcePatternResolver to avoid
returning duplicate resources on MS Windows when searching using the
`classpath*:` prefix and a wildcard pattern that matches resources
which are directly present in a JAR as well as present via classpath
manifest entries.

See gh-31598
Closes gh-31603

(cherry picked from commit d5874ab99e)
2023-11-14 15:31:11 +01:00
Juergen Hoeller 75193b2e84 Upgrade to Log4J 2.21.1, Tomcat 9.0.82, Jetty 9.4.53, Undertow 2.2.28, Netty 4.1.100, Reactor 2020.0.37 2023-11-09 12:32:42 +01:00
Juergen Hoeller f97e819c9e Log4jLog re-resolves ExtendedLogger on deserialization
This is necessary for compatibility with Log4J 2.21, analogous to the existing re-resolution in Spring's SLF4J adapter.

Closes gh-31582

(cherry picked from commit 1e78cc35e5)
2023-11-09 12:12:55 +01:00
Juergen Hoeller f8e1ce3074 Check for procedure vs function constants in CallMetaDataContext
Closes gh-31550

(cherry picked from commit 9957bb6918)
2023-11-09 12:06:59 +01:00
Arjen Poutsma d3ec9395e1 Guard for empty FileItems in CommonsFileUploadSupport
This commit ensures that a FileItem is not empty before its value is
read.

Closes gh-31564
2023-11-08 15:35:17 +01:00
Juergen Hoeller de0cb53394 Polishing
(cherry picked from commit 925fa0272b)
2023-10-24 23:32:17 +02:00
Juergen Hoeller 1bf5d8b9e5 Avoid ResolvableType for simple assignability check in copyProperties
Closes gh-27246

(cherry picked from commit 09aa59f9e7)
2023-10-24 23:22:31 +02:00
Stéphane Nicoll 15a306d8dd Resolve to empty MultiValueMap when no matrix variables are provided
Closes gh-31484
2023-10-24 11:02:45 +02:00
Juergen Hoeller 680939557c Test for @Resource @Lazy fallback type match
See gh-31447
2023-10-23 17:49:04 +02:00
Juergen Hoeller 84f3ce1f91 Test for getObjectType on uninitialized ProxyFactoryBean
See gh-31473
2023-10-23 17:48:56 +02:00
Juergen Hoeller 2dc4fe0857 BeanCopier sets name prefix for public classes as well
Includes consistent formatting of Spring-patched files.

Closes gh-28699

(cherry picked from commit 7a60e2024b)
2023-10-15 16:25:40 +02:00
Juergen Hoeller cb28e2a701 Polishing 2023-10-11 16:01:12 +02:00
Juergen Hoeller 0370aa6007 Do not close transactional Connection in doReleaseConnection
Closes gh-28133
2023-10-11 15:51:58 +02:00
Juergen Hoeller 704650de6d Properly return SQLExceptionTranslator-provided exception
Closes gh-31409
2023-10-11 13:20:13 +02:00
Arjen Poutsma 8538a73f20 Updated Java 8 version in sdkmanrc 2023-10-11 10:38:59 +02:00
Juergen Hoeller c615a6667d Revise javadoc for LifecycleProcessor bean etc 2023-10-10 23:28:57 +02:00
Juergen Hoeller 45cfe70d30 Revise transaction annotation recommendations
Closes gh-23538
2023-10-10 23:11:59 +02:00
Juergen Hoeller 288e255ce9 Throw IllegalArgumentException for null SQL String
Closes gh-31391

(cherry picked from commit 8b5d993e61)
2023-10-10 23:07:46 +02:00
Stéphane Nicoll 12159b98a2 Provide best-effort toString for Lazy resolved message
Previously, MessagingMessageListenerAdapter or any adapter relying on
the default MessagingMessageConverter would log an incoming message
with a toString of the Message that does not provide any extra
information. This is due to the default implementation providing a
lazy resolution message that only attempts to extract the payload
when necessary.

This commit implements a toString method that uses the raw JMS message
if the payload is not available. If it is, the payload is used instead.

Closes gh-21265

(cherry picked from commit a37abd5e54)
2023-10-10 23:07:40 +02:00
Sam Brannen 7a8ec2a761 Polish BeanPropertyRowMapper Javadoc 2023-10-02 17:28:15 +02:00
Stéphane Nicoll ca4674df42 Fix description of default behavior in BeanPropertyRowMapper
Closes gh-31349
2023-10-02 15:08:47 +02:00
Juergen Hoeller 2ec32a4e2f Polishing 2023-09-29 15:39:11 +02:00
Juergen Hoeller 3849ed3a0c Explicit note on local bean access within @PostConstruct method
Closes gh-27876
2023-09-29 15:38:55 +02:00
Sam Brannen d62f17864c Test status quo for zero capacity behavior in ConcurrentLruCache
See gh-31317
2023-09-27 12:57:08 +02:00
Stéphane Nicoll 3c59c2a275 Fix note on CGLIB supported method visibility
CGLIB do support package-private and protected methods now so the
note in the reference doc should be changed accordingly.

Closes gh-31311
2023-09-25 15:26:42 +02:00
Juergen Hoeller 2b4c1e265c Reset findLoadedClassMethod in case of makeAccessible failing
Closes gh-31232
2023-09-14 16:46:17 +02:00
Spring Builds cc0cbc15aa Next development version (v5.3.31-SNAPSHOT) 2023-09-14 07:54:54 +00:00
Juergen Hoeller f7bf2431fb Clarify IN clause resolution with List/Iterable parameter
Closes gh-31228
2023-09-14 09:28:54 +02:00
Sébastien Deleuze 40678bb981 Refine CORS documentation for wildcard processing
This commit adds a reference documentation section dedicated
to CORS credentialed requests and related wildcard processing.

Closes gh-31168
2023-09-11 18:22:03 +02:00
Sébastien Deleuze 75faf698af Refine CORS documentation for wildcard processing
This commit refines CORS wildcard processing Javadoc to
provides more details on how wildcards are handled for
Access-Control-Allow-Methods, Access-Control-Allow-Headers
and Access-Control-Expose-Headers CORS headers.

For Access-Control-Expose-Headers, it is not possible to copy
the response headers which are not available at the point
when the CorsProcessor is invoked. Since all the major browsers
seem to support wildcard including on requests with credentials,
and since this is ultimately the user-agent responsibility to
check on client-side what is authorized or not, Spring Framework
continues to support this use case.

See gh-31168
2023-09-11 18:15:07 +02:00
Juergen Hoeller 39c225c813 AnnotationUtils.clearCache() includes all annotation caches
Closes gh-31170

(cherry picked from commit 78fce80c43)
2023-09-11 17:49:56 +02:00
Juergen Hoeller 0c3d8d7a44 Align abstract method signatures with original Commons Logging API
Closes gh-31166

(cherry picked from commit 268043e9c9)
2023-09-11 17:49:35 +02:00
Sam Brannen ddcae04ad5 Do not invoke [Map|Collection].isEmpty() in nullSafeConciseToString()
gh-30811 introduced explicit support for collections and maps in
ObjectUtils.nullSafeConciseToString() by invoking isEmpty() on a Map or
Collection to determine which concise string representation should be
used. However, this caused a regression in which an exception was
thrown if the Map or Collection was a proxy generated by
AbstractFactoryBean to support <util:set />, <util:list />, and
<util:map /> in XML configuration.

This commit addresses this set of regressions by always returning
"[...]" or "{...}" for a Collection or Map, respectively, disregarding
whether the map is empty or not.

Closes gh-31156
2023-09-08 16:21:39 +02:00
Juergen Hoeller 994bbec0c3 Polishing 2023-08-23 18:57:17 +02:00
Juergen Hoeller afb378a59f Consistently throw ParseException instead of IllegalStateException
Closes gh-31097
2023-08-23 18:57:12 +02:00
Sébastien Deleuze a4fc7d3c11 Optimize ClassUtils#getMostSpecificMethod
This commit optimizes ClassUtils#getMostSpecificMethod which is
a method frequently invoked in typical Spring applications.

It refines ClassUtils#isOverridable by considering static and
final modifiers as non overridable and optimizes its implementation.

Closes gh-31100
2023-08-23 18:16:49 +02:00
Sam Brannen 88c3a788f3 Update copyright headers 2023-08-21 15:12:54 +02:00
Sam Brannen d7ac89ecc9 Revise contribution
Beginning with Java 16, inner classes may contain static members. We
therefore need to search for @DynamicPropertySource methods in the
current class after searching enclosing classes so that a local
@DynamicPropertySource method can override properties registered in an
enclosing class.

However, since Spring Framework 5.3.x is built using Java 8, this
commit removes DynamicPropertySourceOverridesEnclosingClassTests since
it declares a static method in a @Nested (inner) test class, which
results in a compiler error on Java 8.

See https://bugs.openjdk.org/browse/JDK-8254321
See gh-31085
2023-08-21 15:06:00 +02:00
Yanming Zhou 9894174960 Allow overriding dynamic property from enclosing class in nested test class
Prior to this commit, a dynamic property registered via a
@DynamicPropertySource method in a @Nested test class was not able to
override a property registered via a @DynamicPropertySource method in
the enclosing class.

See gh-26091
Closes gh-31083
2023-08-21 14:52:58 +02:00
Juergen Hoeller 493f75e892 Optimize whitespace checks in StringUtils (as far as possible on JDK 8)
Closes gh-31067
2023-08-18 11:40:19 +02:00
Juergen Hoeller df066d8190 Fix accidental javadoc references to jakarta packages 2023-08-16 13:03:23 +02:00
Juergen Hoeller 5f7a6a0f38 Align validation metadata handling in PayloadMethodArgumentResolver
Reuses ValidationAnnotationUtils which is slightly optimized for the detection of Spring's Validated annotation now, also to the benefit of common web scenarios.

Closes gh-21852

(cherry picked from commit c7269feeaa)
2023-08-16 13:02:31 +02:00
Juergen Hoeller 4326c53222 Polishing
(cherry picked from commit 2ce75dc415)
2023-08-14 20:02:50 +02:00
Juergen Hoeller 3da7a35a91 Test factory-bean/method placeholders as well
See gh-20189

(cherry picked from commit 8b3ddeed05)
2023-08-14 19:41:48 +02:00
Juergen Hoeller b9be40ccd2 Add registerReactiveTypeOverride method to ReactiveAdapterRegistry
Closes gh-31047

(cherry picked from commit 389238f622)
2023-08-14 15:18:56 +02:00
Juergen Hoeller f7d4bd176e Explicit note on connection pool deadlock with REQUIRES_NEW
Closes gh-26250
2023-08-12 14:59:32 +02:00
Juergen Hoeller 2b48254268 Use extracted attributes instead of annotation access
See gh-31034

(cherry picked from commit d781f299c0)
2023-08-12 11:38:50 +02:00
Juergen Hoeller bb46b31925 Find TransactionalEventListener annotation on target method
Closes gh-31034

(cherry picked from commit 6fc4898a1b)
2023-08-12 11:38:41 +02:00
Juergen Hoeller 0c275107ea Cancel without interruption of currently running tasks
Leave potential interruption up to scheduler shutdown.

Closes gh-31019

(cherry picked from commit 6fc5a78252)
2023-08-10 00:08:39 +02:00
Juergen Hoeller 9931f442e4 Polishing
(cherry picked from commit 2aae0a4e0c)
2023-08-07 15:14:06 +02:00
Juergen Hoeller fc085e8663 Reinstate Introspector.flushFromCaches() call for JDK ClassInfo cache
Closes gh-27781
2023-08-07 15:07:33 +02:00
Juergen Hoeller 9d7154901f Polishing
(cherry picked from commit 6e5af9dccb)
2023-08-06 14:50:24 +02:00
Juergen Hoeller 24893d038f Polishing 2023-08-04 10:35:51 +02:00
Juergen Hoeller b9482375b7 Sort multiple @Autowired methods on same bean class via ASM
Closes gh-30359

(cherry picked from commit 7e6612a920)
2023-08-04 01:31:49 +02:00
Juergen Hoeller ad61fb75da Polishing 2023-08-02 01:24:11 +02:00
Juergen Hoeller c5aa7830bc Polishing
(cherry picked from commit abbea39855)
2023-07-27 21:58:22 +02:00
Juergen Hoeller 14c8c9168c Polishing 2023-07-26 14:02:05 +02:00
Juergen Hoeller 23eb0e3128 Polishing
(cherry picked from commit bbde68c49e)
2023-07-25 19:58:12 +02:00
Juergen Hoeller 840bd574db Polishing
(cherry picked from commit fdf1418dfb)
2023-07-24 11:40:59 +02:00
Juergen Hoeller 6dea580145 Clarify DataAccessException/ScriptException declarations for R2DBC
Closes gh-30932

(cherry picked from commit 5bcf5c6f7c)
2023-07-24 11:35:23 +02:00
Juergen Hoeller d1efc891db Upgrade to Netty 4.1.95 2023-07-22 00:43:26 +02:00
Juergen Hoeller c9849d6972 Polishing
(cherry picked from commit 3a9e0ea8a7)
2023-07-22 00:43:09 +02:00
Juergen Hoeller 790abeda1c Polishing 2023-07-21 20:40:06 +02:00
Juergen Hoeller 340b32a3cb Polishing 2023-07-19 23:31:38 +02:00
Juergen Hoeller a7b7466274 Polishing 2023-07-19 01:17:25 +02:00
Juergen Hoeller 3a8c0dbd8a Decouple exception messages for sync=true from @Cacheable
(cherry picked from commit bbcc788f60)
2023-07-18 22:12:58 +02:00
Juergen Hoeller b387d9bf10 MethodIntrospector handles overriding bridge method correctly
Closes gh-30906

(cherry picked from commit 616f728afa)
2023-07-18 13:27:15 +02:00
Juergen Hoeller 0f33f79c05 Avoid synchronization for shortcut re-resolution
See gh-30883

(cherry picked from commit 161a717639)
2023-07-16 16:26:37 +02:00
Juergen Hoeller ef65429823 Polishing
(cherry picked from commit 3a278cc66d)
2023-07-15 14:38:30 +02:00
Juergen Hoeller 0b4b313bae Cache DependencyDescriptor per autowired constructor argument
Aligned with shortcut handling in AutowiredAnnotationBeanPostProcessor.
Includes minor MethodInvoker optimization for pre-resolved targetClass.

Closes gh-30883

(cherry picked from commit 6183f06846)
2023-07-15 14:38:24 +02:00
Juergen Hoeller 6879be7508 Explicit hints for @PostConstruct methods (preventing deadlocks)
Closes gh-25074
2023-07-14 16:33:22 +02:00
Juergen Hoeller 355fa258bd Polishing 2023-07-14 14:52:15 +02:00
Spring Builds 60337738d8 Next development version (v5.3.30-SNAPSHOT) 2023-07-13 07:54:12 +00:00
Juergen Hoeller 53319381d0 Polishing
(cherry picked from commit c873a597c7)
2023-07-12 19:25:26 +02:00
Juergen Hoeller e1e7fa489b Upgrade to Reactor 2020.0.34 (and Tomcat 9.0.78)
Closes gh-30873
2023-07-12 09:41:42 +02:00
Brian Clozel e1351a5cb5 Update OS version in CI image 2023-07-12 08:28:18 +02:00
Juergen Hoeller ac94d2bd54 Polishing
(cherry picked from commit f19433f2d8)
2023-07-11 18:40:40 +02:00
Juergen Hoeller 16fd70ae35 Avoid illegal reflective access in ContextOverridingClassLoader
Closes gh-22791

(cherry picked from commit 0b02a5e073)
2023-07-11 18:36:19 +02:00
Juergen Hoeller c1bf09952b Improve diagnostics for LinkageError in case of ClassLoader mismatch
Closes gh-25940
2023-07-11 18:36:12 +02:00
Juergen Hoeller 9e7ee0cb8e Deprecate setAllowResultAccessAfterCompletion and document it as broken
Closes gh-26557
2023-07-11 18:36:02 +02:00
rstoyanchev 1cd994d3a8 Encapsulate full path initialization 2023-07-11 11:45:23 +01:00
Juergen Hoeller 3d28c024c5 Handle JDBC warnings in case of a statement exception as well
Closes gh-23106

(cherry picked from commit 3b899fe7e2)
2023-07-10 17:25:05 +02:00
Juergen Hoeller 3444892aea Polishing
(cherry picked from commit a17cf742b2)
2023-07-09 17:13:49 +02:00
Juergen Hoeller 14a7983eef Tolerate isCandidateClass call with null as annotation type
Closes gh-30842

(cherry picked from commit a102cd5f32)
2023-07-09 17:05:54 +02:00
Sam Brannen 0a4f290f0f Polish DefaultSingletonBeanRegistryTests 2023-07-09 16:11:45 +02:00
Sam Brannen 6ea4d3794a Further simplify DefaultSingletonBeanRegistry.isDependent()
See gh-30841
2023-07-09 16:11:41 +02:00
bnbakp0582 85eec5d344 Simplify DefaultSingletonBeanRegistry.isDependent()
Move `alreadySeen` handling out of for-loop.

Closes gh-30841
2023-07-09 16:11:32 +02:00
Juergen Hoeller 0961bc546a Polishing
(cherry picked from commit 0b7a24fc14)
2023-07-08 01:10:31 +02:00
Juergen Hoeller f2df10c7fe Polishing 2023-07-07 13:26:06 +02:00
Sam Brannen 03f1fabbdb Improve assertions in DefaultConversionServiceTests
Specifically, we now check the actual type of a converted collection in
various assertions to ensure that converters adhere to their contracts.
2023-07-06 13:11:35 +02:00
Sam Brannen 2ada2b77f6 Update Javadoc for ObjectUtils.nullSafeConciseToString()
See gh-30811
2023-07-06 12:29:46 +02:00
Juergen Hoeller d5380b3070 Expand tests for array to Collection/Set/List interface
See gh-28048
2023-07-05 21:12:36 +02:00
Sam Brannen a3907a64e5 Support arrays, collections, & maps in ObjectUtils.nullSafeConciseToString()
Prior to this commit, there was no explicit support for arrays,
collections, and maps in nullSafeConciseToString(). This lead to string
representations such as the following, regardless of whether the array,
collection, or map was empty.

- char[]@1623b78d
- java.util.ImmutableCollections$List12@74fe5c40
- java.util.ImmutableCollections$MapN@10e31a9a

This commit introduces explicit support for arrays, collections, and
maps in nullSafeConciseToString(), which results in the following
empty/non-empty string representations.

- array: {} / {...}
- collection: [] / [...]
- map: {} / {...}

The reason a string representation of an array uses "{}" instead of
"[]" (like in Arrays.toString(...)) is that
ObjectUtils.nullSafeToString(<array>) already follows that convention,
and the implementation of nullSafeConciseToString() aligns with that
for the sake of consistency.

Closes gh-30811
2023-07-05 17:15:15 +02:00
Juergen Hoeller dd16e012ba Clarify ReactiveTransactionManager exception declarations
Avoid misleading "throws TransactionException" declarations but preserve javadoc "@throws" notes for specific exceptions (with reactive propagation semantics).

Closes gh-30817
2023-07-05 12:15:05 +02:00
Juergen Hoeller d4cd358c76 Discuss JdbcTransactionManager vs DataSourceTransactionManager
Closes gh-30802
2023-07-05 12:14:48 +02:00
Juergen Hoeller da814e01c7 Polishing 2023-07-04 21:52:36 +02:00
Juergen Hoeller 5d4c2846d9 Polishing 2023-07-04 16:43:29 +02:00
Juergen Hoeller a3daee6ad8 Make File/Path tests pass on Windows
See gh-30806
2023-07-04 16:38:10 +02:00
Juergen Hoeller 5614e5bc18 Restore full representation of rejected value in FieldError.toString()
We would preferably use ObjectUtils.nullSafeConciseToString(rejectedValue) here but revert to the full nullSafeToString representation for strict backwards compatibility (programmatic toString calls as well as exception messages).

Closes gh-30799

(cherry picked from commit 1dc9dffc70)
2023-07-04 16:13:48 +02:00
Juergen Hoeller 6dde13f597 Refresh cached value after unexpected mismatch (e.g. null vs non-null)
In addition to the previously addressed removal of bean definitions, this is able to deal with prototype factory methods returning non-null after null or also null after non-null. Stale cached values are getting refreshed rather than bypassed.

Closes gh-30794

(cherry picked from commit 0226580773)
2023-07-04 16:13:44 +02:00
Sam Brannen c057da23ec Extend supported types in ObjectUtils.nullSafeConciseToString()
This commit extends the list of explicitly supported types in
ObjectUtils.nullSafeConciseToString() with the following.

- Optional
- File
- Path
- InetAddress
- Charset
- Currency
- TimeZone
- ZoneId
- Pattern

Closes gh-30806
2023-07-04 15:01:35 +02:00
Sam Brannen a7f07328ab Add tests for status quo in ObjectUtils.nullSafeConciseToString() 2023-07-04 14:57:57 +02:00
Sam Brannen 45f747fae1 Clean up warnings in tests 2023-07-04 14:34:32 +02:00
Vladyslav Baidak 258bd3f73c Fix typo in Javadoc for BeanDefinitionDsl.kt
Closes gh-30798
2023-07-03 15:53:09 +02:00
Juergen Hoeller 97b95d9d01 Upgrade to Tomcat 9.0.76, Netty 4.1.94, Undertow 2.2.25, Checkstyle 10.12.1 2023-06-30 13:16:57 +02:00
Juergen Hoeller 69827a2f21 Raise beforeCompletion/afterCompletion exception log level to error
Closes gh-30776

(cherry picked from commit f1567fb21a)
2023-06-30 13:16:37 +02:00
Juergen Hoeller ef699b6a9e Align ConcurrentMapCacheManager locking behavior with CaffeineCacheManager
Closes gh-30780

(cherry picked from commit 60865eae4b)
2023-06-30 10:53:21 +02:00
Juergen Hoeller e440eb8365 Consistently handle invocation exceptions in TypeProxyInvocationHandler
Closes gh-30764

(cherry picked from commit 3cb746c358)
2023-06-28 15:48:41 +02:00
Juergen Hoeller 02cbee560d Polishing
(cherry picked from commit 6526e79eea)
2023-06-26 20:06:12 +02:00
Juergen Hoeller 14da1aca2f Adapt no-arg value from interface-based InvocationHandler callback
Closes gh-30756

(cherry picked from commit b77d4d01c5)
2023-06-26 20:06:06 +02:00
Juergen Hoeller ce97342fee Consistently use mutable ArrayList for modulesToInstall vs modules
Closes gh-30751

(cherry picked from commit 062d701ae1)
2023-06-26 12:39:01 +02:00
Sam Brannen 2e51aa250e Update copyright headers 2023-06-22 14:54:43 +02:00
Juergen Hoeller ec2957afc8 Test for supportsEventType mismatch with unrelated event type
See gh-30712
2023-06-21 18:01:15 +02:00
Juergen Hoeller d3df45d8fe Avoid ResolvableType creation for interface/superclass check
See gh-30713

(cherry picked from commit 1dfe737d0e)
2023-06-21 17:46:59 +02:00
Juergen Hoeller 5375f62dc1 Cache hasUnresolvableGenerics result for repeated checks
Closes gh-30713

(cherry picked from commit 93218a06ba)
2023-06-21 13:24:42 +02:00
Juergen Hoeller c7bc40d3ba Ensure Spring LogFactory contains all public methods from Apache LogFactory
Closes gh-30668

(cherry picked from commit 20bbebb299)
2023-06-21 09:58:23 +02:00
Juergen Hoeller 1071778aa9 Fall back to type-based creation if no bean of the given name exists
Closes gh-30683

(cherry picked from commit dff7aa4d4b)
2023-06-17 11:46:48 +02:00
Juergen Hoeller 40a9ae9d14 Recognize error code 2628 as data integrity violation (MSSQL 2019)
Closes gh-30681

(cherry picked from commit c634acd9ff)
2023-06-17 11:44:11 +02:00
Sam Brannen e34a7baeb3 Remove code duplication in RootBeanDefinition 2023-06-15 16:12:31 +02:00
Brian Clozel 62eb9b391d Use Docker hub credentials for CI tasks 2023-06-15 13:20:51 +02:00
Spring Builds ea89bf2c91 Next development version (v5.3.29-SNAPSHOT) 2023-06-15 07:32:26 +00:00
Juergen Hoeller 99ae6e70bb Declare ClassLoader for DeserializingConverter constructor as nullable
Closes gh-30670

(cherry picked from commit b9221656cc)
2023-06-14 22:34:28 +02:00
Juergen Hoeller c2cc55eacc Consider UUID as simple value type with concise toString output
Closes gh-30661

(cherry picked from commit 927d27b121)
2023-06-14 10:47:54 +02:00
Juergen Hoeller 9cff2ace97 Upgrade to Reactor 2020.0.33
Closes gh-30656
2023-06-13 18:40:46 +02:00
Juergen Hoeller 3c2590d339 Document limited isolation level support for concurrent transactions
See gh-29997
2023-06-13 12:57:52 +02:00
Juergen Hoeller 4b55333b0e Document which @Scheduled attributes support SpEL expressions
Closes gh-29290

(cherry picked from commit f8c8873c99)
2023-06-12 13:08:02 +02:00
Juergen Hoeller c27acad616 Specific check for parent of MethodInvocationInfo ClassLoader
See gh-30389
2023-06-12 11:33:54 +02:00
Juergen Hoeller 70be9afdc6 Reuse method cache from original proxy factory (aligned with 6.0.x)
See gh-30616
2023-06-12 11:11:07 +02:00
Juergen Hoeller d4450a8702 Specific check for parent of spring-aop ClassLoader
Closes gh-30389

(cherry picked from commit 0a5aff1b60)
2023-06-12 11:08:37 +02:00
Juergen Hoeller 210e47d65e Polishing 2023-06-08 18:31:31 +02:00
Juergen Hoeller 06411831e8 Consistent ProxyCallbackFilter#equals/hashCode methods
Opaque check in equals instead; no consideration of optimize flag.

Closes gh-30616
2023-06-08 18:31:26 +02:00
Juergen Hoeller 8c7daa807a Polishing 2023-06-07 20:14:41 +02:00
Juergen Hoeller 46d171a8fd Restore creation of plain HashSet/HashMap for direct HashSet/HashMap type
Closes gh-30596

(cherry picked from commit cdc4497664)
2023-06-05 14:03:15 +02:00
Juergen Hoeller 259bd5250d Consistent javadoc references to JdbcTransactionManager 2023-06-05 11:12:38 +02:00
Juergen Hoeller cef046218c Upgrade to Tomcat 9.0.75, Netty 4.1.93, Undertow 2.2.24, EclipseLink 2.7.12 2023-06-04 18:13:00 +02:00
Juergen Hoeller e4bd1344e2 Set and reset shared isolation value within synchronized transaction begin
Since EclipseLink applies a custom transaction isolation value to its shared DatabasePlatform instance, we need to immediately restore the original value after the current value got picked up for JDBC Connection access inside of EclipseLink. In order to not interfere with concurrent transactions, we need to use synchronization around the transaction begin sequence in such a case.

Closes gh-29997
2023-06-04 18:12:40 +02:00
Juergen Hoeller 9decbf2158 Polishing 2023-06-03 00:01:18 +02:00
Juergen Hoeller 268b7a8931 Revise TargetSource implementations for proper nullability
Includes hashCode optimization in AbstractBeanFactoryBasedTargetSource.
Includes ThreadLocal naming fix in ThreadLocalTargetSource.

Closes gh-30576
Closes gh-30581

(cherry picked from commit c68552556f)
2023-06-02 23:48:41 +02:00
Juergen Hoeller 1240fb6b9a Consistently publish events from CompletableFuture
Closes gh-30578

(cherry picked from commit b738a20233)
2023-06-02 23:36:13 +02:00
Brian Clozel 7dae3afb4b Resolve Asciidoctor extensions from Central
This commit upgrades spring-asciidoctor-extensions-block-switch to 0.6.1
as this version is available on Maven Central.
2023-05-30 09:49:23 +02:00
Stephane Nicoll d9de36b5ee Merge branch 'gh-30555' into 5.3.x
Closes gh-30555
2023-05-30 09:18:41 +02:00
Stephane Nicoll 4e696db922 Update copyright year of changed file
See gh-30554
2023-05-30 09:18:06 +02:00
Stefano Cordio 0adad10595 Fix FileSystemUtils::deleteRecursively Javadoc
See gh-30554
2023-05-30 09:17:54 +02:00
Juergen Hoeller 540d0d9345 Avoid Autowired shortcut resolution for NullBean values
Includes getBean documentation against NullBean values.

Closes gh-30485

(cherry picked from commit 8b8d147480)
2023-05-26 11:16:09 +02:00
Brian Clozel 572bbeeba3 Use spring-doc-resources SNAPSHOT version 2023-05-25 20:04:08 +02:00
Brian Clozel 77cd44dd20 Update CI pipeline 2023-05-25 19:13:58 +02:00
rstoyanchev 65e7b4a279 Add ignore rule for cached-antora-playbook.yml
In case of checking out the 5.3.x branch after 6.0.x or main
2023-05-23 15:14:01 +01:00
Sam Brannen 7d95a24573 Make maximum SpEL expression length configurable
Closes gh-30446
2023-05-10 15:12:36 +02:00
Juergen Hoeller 1dbe0ee6db Respect TaskDecorator configuration on DefaultManagedTaskExecutor
Closes gh-30442
2023-05-08 12:18:53 +02:00
Juergen Hoeller 0211016957 Consistent support for MultiValueMap and common Map implementations
Closes gh-30440
2023-05-08 12:13:44 +02:00
Sam Brannen 08fe123930 Introduce Environment.matchesProfiles() for profile expressions
Environment.acceptsProfiles(String...) was deprecated in 5.1 in
conjunction with gh-17063 which introduced a new
acceptsProfiles(Profiles) method to replace it. The deprecated method
only supports OR semantics; whereas, the new method supports profile
expressions. Thus, the goal was to encourage people to use the more
powerful profile expressions instead of the limited OR support with
profile names.

However, there are use cases where it is difficult (if not impossible)
to provide a Profiles instance, and there are use cases where it is
simply preferable to provide profile expressions directly as strings.

To address these issues, this commit introduces a new matchesProfiles()
method in Environment that accepts a var-args list of profile
expressions.

See gh-30206
Closes gh-30226
2023-04-25 19:19:22 +02:00
Sam Brannen 219448796f Polish Environment and StandardEnvironmentTests
See gh-30206
See gh-30226
2023-04-25 19:19:03 +02:00
Sam Brannen 0956c144c9 Polish ProfilesParser internals 2023-04-25 18:56:08 +02:00
Sam Brannen 964950a8b9 Reject null and empty SpEL expressions
Prior to gh-30325, supplying a null reference for a SpEL expression was
effectively equivalent to supplying the String "null" as the
expression. Consequently, evaluation of a null reference expression
always evaluated to a null reference. However, that was accidental
rather than by design.

Due to the introduction of the checkExpressionLength(String) method in
InternalSpelExpressionParser (in conjunction with gh-30325), an attempt
to evaluate a null reference as a SpEL expression now results in a
NullPointerException.

To address both of these issues,
TemplateAwareExpressionParser.parseExpression() and
SpelExpressionParser.parseRaw() now reject null and empty SpEL
expressions.

See gh-30371
Closes gh-30373
2023-04-25 14:31:35 +02:00
Sam Brannen 5afd94f90f Polish SpelParserTests and TemplateExpressionParsingTests 2023-04-25 14:29:01 +02:00
Arjen Poutsma ec5f7644e4 Updated CI image JDK 2023-04-24 13:00:06 +02:00
Arjen Poutsma 0c8ef4c671 Updated sdkmanrc 2023-04-24 12:37:15 +02:00
Spring Builds a851b7309a Next development version (v5.3.28-SNAPSHOT) 2023-04-13 08:57:10 +00:00
Sam Brannen 6bfb94a563 Disable variable assignment in SimpleEvaluationContext
This commit introduces infrastructure to differentiate between
programmatic setting of a variable in an EvaluationContext versus the
assignment of a variable within a SpEL expression using the assignment
operator (=). In addition, this commit disables variable assignment
within expressions when using the SimpleEvaluationContext.

Closes gh-30327
2023-04-13 10:20:06 +02:00
Sam Brannen ebc8265428 Limit SpEL expression length
This commit enforces a limit of the maximum size of a single SpEL
expression.

Closes gh-30329
2023-04-13 10:15:02 +02:00
Sam Brannen 86457464d7 Limit string concatenation in SpEL expressions
This commit introduces support for limiting the maximum length of a
string resulting from the concatenation operator (+) in SpEL
expressions.

Closes gh-30331
2023-04-13 10:14:18 +02:00
Sam Brannen be129dc171 Change max regex length in SpEL expressions to 1000
This commit changes the max regex length in SpEL expressions from 1024
to 1000 in order to consistently use "round" numbers for recently
introduced limits.

See gh-30265
2023-04-13 10:14:15 +02:00
Juergen Hoeller 8bb1b3eb44 Upgrade to Netty 4.1.91 and Checkstyle 10.9.3 2023-04-12 13:53:17 +02:00
Stephane Nicoll 6abd822e77 Upgrade to Reactor 2020.0.31
Closes gh-30315
2023-04-11 17:35:25 +02:00
rstoyanchev 1c43a4c7ab Fix regression in ReactorServerHttpRequest
Instead of a backport for cef916, this change simply undoes the
optimization that led to the regression.

Closes gh-30314
2023-04-11 15:12:54 +01:00
Sam Brannen 423f2215c2 Remove flaky assertion to fix build on JDK 17
Sometime between JDK 8 and JDK 17, the behavior for List::toArray()
changed. Specifically, the type returned for List<String> changed from
String[] to Object[].

This commit therefore removes an assertion against this particular
JDK-specific behavior. The affected test method retains additional
assertions along the same lines but which are not flaky.
2023-04-10 17:34:04 +02:00
Sébastien Deleuze 0bad69d5fb Fix SSE with indenting serializer in WebMvc.fn
This commit ensures that HTTP headers like "text/event-stream"
are correctly forwarded to the converter used in
SseServerResponse for proper pretty print handling.

Close gh-30302
2023-04-07 11:56:47 +02:00
Sam Brannen 6b19642256 Increase max regex length in SpEL expressions
This commit increases the max regex length in SpEL expressions from 256
to 1024 in order to support use cases where a regex may be rather long
without necessarily increasing the complexity of the regex.

Closes gh-30298
2023-04-06 18:02:36 +02:00
Sam Brannen 19bb4e96f2 Improve Javadoc for ObjectUtils.nullSafeConciseToString() 2023-04-06 17:36:32 +02:00
Sam Brannen 7a2594acda Add tests for corner cases
See gh-30290
See gh-30286
2023-04-05 15:28:45 +02:00
Sam Brannen 91c58af7af Introduce ObjectUtils.nullSafeConciseToString()
ObjectUtils.nullSafeToString(Object) exists for generating a string
representation of various objects in a "null-safe" manner, including
support for object graphs, collections, etc.

However, there are times when we would like to generate a "concise",
null-safe string representation that does not include an entire object
graph (or potentially a collection of object graphs).

This commit introduces ObjectUtils.nullSafeConciseToString(Object) to
address this need and makes use of the new feature in FieldError and
ConversionFailedException.

Closes gh-30286
2023-04-05 15:25:59 +02:00
Sam Brannen 0a1aeafe08 Introduce StringUtils.truncate()
StringUtils.truncate() serves as central, consistent way for truncating
strings used in log messages and exception failure messages, for
immediate use in LogFormatUtils and ObjectUtils.

See gh-30286
Closes gh-30290
2023-04-05 15:15:53 +02:00
Simon Baslé 0e69bac7b0 Polish 0cfbf60: fix a test for Java 8 compatibility 2023-04-04 15:25:27 +02:00
Simon Baslé 0cfbf6036c Rename MockMVC matcher methods to prevent regression in user tests
This commit changes the name of two recently introduced methods in the
`MockRestRequestMatchers` class for header and queryParam. These have
been found to cause false negatives in user tests, due to the new
overload taking precedence in some cases.

Namely, using a `Matcher` factory method which can apply to both `List`
and `String` will cause the compiler to select the newest list overload,
by instantiating a `Matcher<Object>`.

This can cause false negatives in user tests, failing tests that used
to pass because the Matcher previously applied to the first String in
the header or queryParam value list. For instance, `equalsTo("a")`.

The new overloads are recent enough and this has enough potential to
cause an arbitrary number of user tests to fail that we break the API
to eliminate the ambiguity, by renaming the methods with a `*List`
suffix.

See gh-30220
See gh-30238
Closes gh-30235
2023-04-04 15:07:41 +02:00
Sébastien Deleuze e931fdc5c2 Make HttpComponentsHeadersAdapter#getFirst nullable
Backport of gh-30267

Closes gh-30269
2023-04-03 09:06:32 +02:00
Sébastien Deleuze 3a1681357c Fix PathVariable reference documentation code snippets
Closes gh-30258
2023-03-31 15:51:45 +02:00
Juergen Hoeller a93382dbbf Propagate HttpStreamResetException itself if cause is null
Closes gh-30245

(cherry picked from commit 8fca258207)
2023-03-30 19:32:29 +02:00
Sébastien Deleuze 79c5ef88f5 Refine generic type management in AbstractMessageWriterResultHandler
This commit updates AbstractMessageWriterResultHandler#writeBody in
order to use the declared bodyParameter instead of
ResolvableType.forInstance(body) when the former has unresolvable
generics.

Closes gh-30215
2023-03-30 18:26:15 +02:00
Juergen Hoeller 0c80e5f9e6 Use JdkDynamicAopProxy class loader instead of JDK bootstrap/platform loader
Closes gh-30115

(cherry picked from commit 7e905e3e00)
2023-03-29 14:07:50 +02:00
Juergen Hoeller 7ad01a94d6 Use MethodInvocationInfo class loader in case of JDK platform loader as well
Closes gh-30210

(cherry picked from commit 491ae1e3be)
2023-03-29 13:56:23 +02:00
Juergen Hoeller 5d6d653cbd Use MethodInvocationInfo class loader in case of core JDK interface type
Closes gh-30210

(cherry picked from commit ce2689eead)
2023-03-28 13:56:02 +02:00
Johnny Lim 9868e888a5 Update versions in Javadoc
Closes gh-30191
2023-03-25 17:17:28 +01:00
ghostg00 6f6eb3d996 Fix example in Javadoc for @EnableWebSocket
The `echoWebSocketHandler()` method is not defined in the
`WebSocketConfigurer` interface and should therefore be annotated with
`@Bean` instead of `@Override`.

Closes gh-30187
2023-03-24 17:55:24 +01:00
Giuseppe a94c50e294 Handle all exceptions for stored proc out param retrieval in SharedEntityManagerCreator
Prior to this commit, the EntityManager was not closed in
SharedEntityManagerCreator.DeferredQueryInvocationHandler's
invoke(Object, Method, Object[]) method if an invocation of
getOutputParameterValue(*) threw an exception other than
IllegalArgumentException, which could lead to a connection leak.

This commit addresses this by catching RuntimeException instead of
IllegalArgumentException.

Closes gh-30164
2023-03-22 15:44:06 +01:00
Kukri b8c1255ee0 Fix anchor in link to "Web on Reactive Stack" chapter
Closes gh-30163
2023-03-22 11:35:12 +01:00
Spring Builds 129062034b Next development version (v5.3.27-SNAPSHOT) 2023-03-20 10:05:15 +00:00
337 changed files with 6874 additions and 4087 deletions
+3 -1
View File
@@ -46,4 +46,6 @@ atlassian-ide-plugin.xml
.gradletasknamecache
# VS Code
.vscode/
.vscode/
cached-antora-playbook.yml
+1 -1
View File
@@ -1,3 +1,3 @@
# Enable auto-env through the sdkman_auto_env config
# Add key=value pairs of SDKs to use below
java=8.0.362-librca
java=8.0.382-librca
+13 -13
View File
@@ -28,18 +28,18 @@ configure(allprojects) { project ->
dependencyManagement {
imports {
mavenBom "com.fasterxml.jackson:jackson-bom:2.12.7"
mavenBom "io.netty:netty-bom:4.1.90.Final"
mavenBom "io.projectreactor:reactor-bom:2020.0.30"
mavenBom "io.netty:netty-bom:4.1.101.Final"
mavenBom "io.projectreactor:reactor-bom:2020.0.38"
mavenBom "io.r2dbc:r2dbc-bom:Arabba-SR13"
mavenBom "io.rsocket:rsocket-bom:1.1.3"
mavenBom "org.eclipse.jetty:jetty-bom:9.4.51.v20230217"
mavenBom "org.eclipse.jetty:jetty-bom:9.4.53.v20231009"
mavenBom "org.jetbrains.kotlin:kotlin-bom:1.5.32"
mavenBom "org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.5.2"
mavenBom "org.jetbrains.kotlinx:kotlinx-serialization-bom:1.2.2"
mavenBom "org.junit:junit-bom:5.8.2"
}
dependencies {
dependencySet(group: 'org.apache.logging.log4j', version: '2.20.0') {
dependencySet(group: 'org.apache.logging.log4j', version: '2.21.1') {
entry 'log4j-api'
entry 'log4j-core'
entry 'log4j-jul'
@@ -67,9 +67,9 @@ configure(allprojects) { project ->
dependency "io.reactivex:rxjava:1.3.8"
dependency "io.reactivex:rxjava-reactive-streams:1.2.1"
dependency "io.reactivex.rxjava2:rxjava:2.2.21"
dependency "io.reactivex.rxjava3:rxjava:3.1.5"
dependency "io.smallrye.reactive:mutiny:1.8.0"
dependency "io.projectreactor.tools:blockhound:1.0.6.RELEASE"
dependency "io.reactivex.rxjava3:rxjava:3.1.8"
dependency "io.smallrye.reactive:mutiny:1.9.0"
dependency "io.projectreactor.tools:blockhound:1.0.8.RELEASE"
dependency "com.caucho:hessian:4.0.63"
dependency "com.fasterxml:aalto-xml:1.3.1"
@@ -96,7 +96,7 @@ configure(allprojects) { project ->
dependency "com.h2database:h2:2.1.214"
dependency "com.github.ben-manes.caffeine:caffeine:2.9.3"
dependency "com.github.librepdf:openpdf:1.3.30"
dependency "com.github.librepdf:openpdf:1.3.33"
dependency "com.rometools:rome:1.18.0"
dependency "commons-io:commons-io:2.5"
dependency "io.vavr:vavr:0.10.4"
@@ -128,18 +128,18 @@ configure(allprojects) { project ->
dependency "org.webjars:webjars-locator-core:0.48"
dependency "org.webjars:underscorejs:1.8.3"
dependencySet(group: 'org.apache.tomcat', version: '9.0.73') {
dependencySet(group: 'org.apache.tomcat', version: '9.0.82') {
entry 'tomcat-util'
entry('tomcat-websocket') {
exclude group: "org.apache.tomcat", name: "tomcat-servlet-api"
exclude group: "org.apache.tomcat", name: "tomcat-websocket-api"
}
}
dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.73') {
dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.82') {
entry 'tomcat-embed-core'
entry 'tomcat-embed-websocket'
}
dependencySet(group: 'io.undertow', version: '2.2.23.Final') {
dependencySet(group: 'io.undertow', version: '2.2.28.Final') {
entry 'undertow-core'
entry('undertow-servlet') {
exclude group: "org.jboss.spec.javax.servlet", name: "jboss-servlet-api_4.0_spec"
@@ -238,7 +238,7 @@ configure(allprojects) { project ->
dependency "com.ibm.websphere:uow:6.0.2.17"
dependency "com.jamonapi:jamon:2.82"
dependency "joda-time:joda-time:2.10.13"
dependency "org.eclipse.persistence:org.eclipse.persistence.jpa:2.7.10"
dependency "org.eclipse.persistence:org.eclipse.persistence.jpa:2.7.12"
dependency "org.javamoney:moneta:1.3"
dependency "com.sun.activation:javax.activation:1.2.0"
@@ -340,7 +340,7 @@ configure([rootProject] + javaProjects) { project ->
}
checkstyle {
toolVersion = "10.9.1"
toolVersion = "10.12.5"
configDirectory.set(rootProject.file("src/checkstyle"))
}
+1 -1
View File
@@ -1,4 +1,4 @@
FROM ubuntu:focal-20220922
FROM ubuntu:jammy-20230624
ADD setup.sh /setup.sh
ADD get-jdk-url.sh /get-jdk-url.sh
+2 -2
View File
@@ -3,10 +3,10 @@ set -e
case "$1" in
java8)
echo "https://github.com/bell-sw/Liberica/releases/download/8u345+1/bellsoft-jdk8u345+1-linux-amd64.tar.gz"
echo "https://github.com/bell-sw/Liberica/releases/download/8u372+7/bellsoft-jdk8u372+7-linux-amd64.tar.gz"
;;
java17)
echo "https://github.com/bell-sw/Liberica/releases/download/17.0.4+8/bellsoft-jdk17.0.4+8-linux-amd64.tar.gz"
echo "https://github.com/bell-sw/Liberica/releases/download/17.0.7+7/bellsoft-jdk17.0.7+7-linux-amd64.tar.gz"
;;
*)
echo $"Unknown java version"
-47
View File
@@ -64,12 +64,6 @@ resource_types:
<<: *docker-resource-source
repository: dpb587/github-status-resource
tag: master
- name: pull-request
type: registry-image
source:
<<: *docker-resource-source
repository: teliaoss/github-pr-resource
tag: v0.23.0
- name: slack-notification
type: registry-image
source:
@@ -111,14 +105,6 @@ resources:
username: ((artifactory-username))
password: ((artifactory-password))
build_name: ((build-name))
- name: git-pull-request
type: pull-request
icon: source-pull
source:
access_token: ((github-ci-pull-request-token))
repository: ((github-repo-name))
base_branch: ((branch))
ignore_paths: ["ci/*"]
- name: repo-status-build
type: github-status-resource
icon: eye-check-outline
@@ -259,37 +245,6 @@ jobs:
<<: *slack-fail-params
- put: repo-status-jdk17-build
params: { state: "success", commit: "git-repo" }
- name: build-pull-requests
serial: true
public: true
plan:
- get: ci-image
- get: git-repo
resource: git-pull-request
trigger: true
version: every
- do:
- put: git-pull-request
params:
path: git-repo
status: pending
- task: build-pr
image: ci-image
file: git-repo/ci/tasks/build-pr.yml
privileged: true
timeout: ((task-timeout))
params:
BRANCH: ((branch))
on_success:
put: git-pull-request
params:
path: git-repo
status: success
on_failure:
put: git-pull-request
params:
path: git-repo
status: failure
- name: stage-milestone
serial: true
plan:
@@ -442,5 +397,3 @@ groups:
jobs: ["stage-milestone", "stage-rc", "stage-release", "promote-milestone", "promote-rc", "promote-release", "create-github-release"]
- name: "ci-images"
jobs: ["build-ci-images"]
- name: "pull-requests"
jobs: [ "build-pull-requests" ]
+2
View File
@@ -5,6 +5,8 @@ image_resource:
source:
repository: springio/github-changelog-generator
tag: '0.0.7'
username: ((docker-hub-username))
password: ((docker-hub-password))
inputs:
- name: git-repo
- name: artifactory-repo
+2
View File
@@ -5,6 +5,8 @@ image_resource:
source:
repository: springio/concourse-release-scripts
tag: '0.3.4'
username: ((docker-hub-username))
password: ((docker-hub-password))
inputs:
- name: git-repo
- name: artifactory-repo
+1 -1
View File
@@ -1,4 +1,4 @@
version=5.3.26-SNAPSHOT
version=5.3.31
org.gradle.jvmargs=-Xmx2048m
org.gradle.caching=true
org.gradle.parallel=true
+3 -4
View File
@@ -3,7 +3,7 @@ configurations {
}
dependencies {
asciidoctorExt("io.spring.asciidoctor:spring-asciidoctor-extensions-block-switch:0.5.0")
asciidoctorExt("io.spring.asciidoctor:spring-asciidoctor-extensions-block-switch:0.6.1")
}
repositories {
@@ -73,9 +73,8 @@ pluginManager.withPlugin("kotlin") {
}
task downloadResources(type: Download) {
def version = "0.2.5"
src "https://repo.spring.io/release/io/spring/docresources/" +
"spring-doc-resources/$version/spring-doc-resources-${version}.zip"
src "https://repo.spring.io/artifactory/snapshot/io/spring/docresources/" +
"spring-doc-resources/0.2.6-SNAPSHOT/spring-doc-resources-0.2.6-20210308.231804-2.zip"
dest project.file("$buildDir/docs/spring-doc-resources.zip")
onlyIfModified true
useETag "all"
@@ -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.
@@ -60,8 +60,8 @@ class ScheduledAndTransactionalAnnotationIntegrationTests {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(Config.class, JdkProxyTxConfig.class, RepoConfigA.class);
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(ctx::refresh)
.withCauseInstanceOf(IllegalStateException.class);
.isThrownBy(ctx::refresh)
.withCauseInstanceOf(IllegalStateException.class);
}
@Test
@@ -70,7 +70,7 @@ class ScheduledAndTransactionalAnnotationIntegrationTests {
ctx.register(Config.class, SubclassProxyTxConfig.class, RepoConfigA.class);
ctx.refresh();
Thread.sleep(100); // allow @Scheduled method to be called several times
Thread.sleep(200); // allow @Scheduled method to be called several times
MyRepository repository = ctx.getBean(MyRepository.class);
CallCountingTransactionManager txManager = ctx.getBean(CallCountingTransactionManager.class);
@@ -85,7 +85,7 @@ class ScheduledAndTransactionalAnnotationIntegrationTests {
ctx.register(Config.class, JdkProxyTxConfig.class, RepoConfigB.class);
ctx.refresh();
Thread.sleep(100); // allow @Scheduled method to be called several times
Thread.sleep(200); // allow @Scheduled method to be called several times
MyRepositoryWithScheduledMethod repository = ctx.getBean(MyRepositoryWithScheduledMethod.class);
CallCountingTransactionManager txManager = ctx.getBean(CallCountingTransactionManager.class);
@@ -100,7 +100,7 @@ class ScheduledAndTransactionalAnnotationIntegrationTests {
ctx.register(AspectConfig.class, MyRepositoryWithScheduledMethodImpl.class);
ctx.refresh();
Thread.sleep(100); // allow @Scheduled method to be called several times
Thread.sleep(200); // allow @Scheduled method to be called several times
MyRepositoryWithScheduledMethod repository = ctx.getBean(MyRepositoryWithScheduledMethod.class);
assertThat(AopUtils.isCglibProxy(repository)).isTrue();
@@ -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,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
@@ -158,7 +158,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
/** The pointcut expression associated with the advice, as a simple String. */
@Nullable
private String pointcutExpression;
private final String pointcutExpression;
private boolean raiseExceptions;
@@ -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
*/
@@ -316,13 +315,13 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
}
private void bindParameterName(int index, String name) {
private void bindParameterName(int index, @Nullable String name) {
this.parameterNameBindings[index] = name;
this.numberOfRemainingUnboundArguments--;
}
/**
* 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() {
@@ -367,8 +366,8 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
}
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);
@@ -400,7 +399,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.
@@ -465,7 +463,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
}
}
/*
/**
* If the token starts meets Java identifier conventions, it's in.
*/
@Nullable
@@ -533,7 +531,6 @@ 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");
@@ -609,7 +606,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.
*/
@@ -709,7 +706,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
return false;
}
/*
/**
* Return {@code true} if the given argument type is a subclass
* of the given supertype.
*/
@@ -737,7 +734,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.
*/
@@ -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.
@@ -248,8 +248,8 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
/**
* If a pointcut expression has been specified in XML, the user cannot
* write {@code and} as "&&" (though &amp;&amp; will work).
* We also allow {@code and} between two pointcut sub-expressions.
* write "and" as "&&" (though {@code &amp;&amp;} will work).
* <p>We also allow "and" between two pointcut sub-expressions.
* <p>This method converts back to {@code &&} for the AspectJ pointcut parser.
*/
private String replaceBooleanOperators(String pcExpr) {
@@ -527,7 +527,7 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
return false;
}
AspectJExpressionPointcut otherPc = (AspectJExpressionPointcut) other;
return ObjectUtils.nullSafeEquals(this.getExpression(), otherPc.getExpression()) &&
return ObjectUtils.nullSafeEquals(getExpression(), otherPc.getExpression()) &&
ObjectUtils.nullSafeEquals(this.pointcutDeclarationScope, otherPc.pointcutDeclarationScope) &&
ObjectUtils.nullSafeEquals(this.pointcutParameterNames, otherPc.pointcutParameterNames) &&
ObjectUtils.nullSafeEquals(this.pointcutParameterTypes, otherPc.pointcutParameterTypes);
@@ -535,7 +535,7 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
@Override
public int hashCode() {
int hashCode = ObjectUtils.nullSafeHashCode(this.getExpression());
int hashCode = ObjectUtils.nullSafeHashCode(getExpression());
hashCode = 31 * hashCode + ObjectUtils.nullSafeHashCode(this.pointcutDeclarationScope);
hashCode = 31 * hashCode + ObjectUtils.nullSafeHashCode(this.pointcutParameterNames);
hashCode = 31 * hashCode + ObjectUtils.nullSafeHashCode(this.pointcutParameterTypes);
@@ -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.
@@ -521,6 +521,7 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
copy.copyFrom(this);
copy.targetSource = EmptyTargetSource.forClass(getTargetClass(), getTargetSource().isStatic());
copy.advisorChainFactory = this.advisorChainFactory;
copy.methodCache = this.methodCache;
copy.interfaces = new ArrayList<>(this.interfaces);
copy.advisors = new ArrayList<>(this.advisors);
return copy;
@@ -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.
@@ -283,8 +283,8 @@ class CglibAopProxy implements AopProxy, Serializable {
private Callback[] getCallbacks(Class<?> rootClass) throws Exception {
// Parameters used for optimization choices...
boolean exposeProxy = this.advised.isExposeProxy();
boolean isFrozen = this.advised.isFrozen();
boolean exposeProxy = this.advised.isExposeProxy();
boolean isStatic = this.advised.getTargetSource().isStatic();
// Choose an "aop" interceptor (used for AOP calls).
@@ -424,10 +424,9 @@ class CglibAopProxy implements AopProxy, Serializable {
/**
* Method interceptor used for static targets with no advice chain. The call
* is passed directly back to the target. Used when the proxy needs to be
* exposed and it can't be determined that the method won't return
* {@code this}.
* Method interceptor used for static targets with no advice chain. The call is
* passed directly back to the target. Used when the proxy needs to be exposed
* and it can't be determined that the method won't return {@code this}.
*/
private static class StaticUnadvisedInterceptor implements MethodInterceptor, Serializable {
@@ -899,9 +898,9 @@ class CglibAopProxy implements AopProxy, Serializable {
// Proxy is not yet available, but that shouldn't matter.
List<?> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
boolean haveAdvice = !chain.isEmpty();
boolean isFrozen = this.advised.isFrozen();
boolean exposeProxy = this.advised.isExposeProxy();
boolean isStatic = this.advised.getTargetSource().isStatic();
boolean isFrozen = this.advised.isFrozen();
if (haveAdvice || !isFrozen) {
// If exposing the proxy, then AOP_PROXY must be used.
if (exposeProxy) {
@@ -970,6 +969,9 @@ class CglibAopProxy implements AopProxy, Serializable {
if (this.advised.isExposeProxy() != otherAdvised.isExposeProxy()) {
return false;
}
if (this.advised.isOpaque() != otherAdvised.isOpaque()) {
return false;
}
if (this.advised.getTargetSource().isStatic() != otherAdvised.getTargetSource().isStatic()) {
return false;
}
@@ -1016,10 +1018,6 @@ class CglibAopProxy implements AopProxy, Serializable {
Advice advice = advisor.getAdvice();
hashCode = 13 * hashCode + advice.getClass().hashCode();
}
hashCode = 13 * hashCode + (this.advised.isFrozen() ? 1 : 0);
hashCode = 13 * hashCode + (this.advised.isExposeProxy() ? 1 : 0);
hashCode = 13 * hashCode + (this.advised.isOptimize() ? 1 : 0);
hashCode = 13 * hashCode + (this.advised.isOpaque() ? 1 : 0);
return hashCode;
}
}
@@ -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.
@@ -123,7 +123,33 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
if (logger.isTraceEnabled()) {
logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
}
return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this);
return Proxy.newProxyInstance(determineClassLoader(classLoader), this.proxiedInterfaces, this);
}
/**
* Determine whether the JDK bootstrap or platform loader has been suggested ->
* use higher-level loader which can see Spring infrastructure classes instead.
*/
private ClassLoader determineClassLoader(@Nullable ClassLoader classLoader) {
if (classLoader == null) {
// JDK bootstrap loader -> use spring-aop ClassLoader instead.
return getClass().getClassLoader();
}
if (classLoader.getParent() == null) {
// Potentially the JDK platform loader on JDK 9+
ClassLoader aopClassLoader = getClass().getClassLoader();
ClassLoader aopParent = aopClassLoader.getParent();
while (aopParent != null) {
if (classLoader == aopParent) {
// Suggested ClassLoader is ancestor of spring-aop ClassLoader
// -> use spring-aop ClassLoader itself instead.
return aopClassLoader;
}
aopParent = aopParent.getParent();
}
}
// Regular case: use suggested ClassLoader as-is.
return classLoader;
}
/**
@@ -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.
@@ -34,6 +34,7 @@ import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Convenient superclass for
@@ -81,6 +82,11 @@ public abstract class AbstractBeanFactoryBasedTargetSourceCreator
return this.beanFactory;
}
private ConfigurableBeanFactory getConfigurableBeanFactory() {
Assert.state(this.beanFactory != null, "BeanFactory not set");
return this.beanFactory;
}
//---------------------------------------------------------------------
// Implementation of the TargetSourceCreator interface
@@ -104,7 +110,7 @@ public abstract class AbstractBeanFactoryBasedTargetSourceCreator
// We need to override just this bean definition, as it may reference other beans
// and we're happy to take the parent's definition for those.
// Always use prototype scope if demanded.
BeanDefinition bd = this.beanFactory.getMergedBeanDefinition(beanName);
BeanDefinition bd = getConfigurableBeanFactory().getMergedBeanDefinition(beanName);
GenericBeanDefinition bdCopy = new GenericBeanDefinition(bd);
if (isPrototypeBased()) {
bdCopy.setScope(BeanDefinition.SCOPE_PROTOTYPE);
@@ -126,7 +132,7 @@ public abstract class AbstractBeanFactoryBasedTargetSourceCreator
protected DefaultListableBeanFactory getInternalBeanFactoryForBean(String beanName) {
synchronized (this.internalBeanFactories) {
return this.internalBeanFactories.computeIfAbsent(beanName,
name -> buildInternalBeanFactory(this.beanFactory));
name -> buildInternalBeanFactory(getConfigurableBeanFactory()));
}
}
@@ -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.
@@ -24,6 +24,8 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.aop.TargetSource;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
@@ -57,15 +59,18 @@ public abstract class AbstractBeanFactoryBasedTargetSource implements TargetSour
protected final Log logger = LogFactory.getLog(getClass());
/** Name of the target bean we will create on each invocation. */
@Nullable
private String targetBeanName;
/** Class of the target. */
@Nullable
private volatile Class<?> targetClass;
/**
* BeanFactory that owns this TargetSource. We need to hold onto this
* reference so that we can create new prototype instances as necessary.
*/
@Nullable
private BeanFactory beanFactory;
@@ -86,6 +91,7 @@ public abstract class AbstractBeanFactoryBasedTargetSource implements TargetSour
* Return the name of the target bean in the factory.
*/
public String getTargetBeanName() {
Assert.state(this.targetBeanName != null, "Target bean name not set");
return this.targetBeanName;
}
@@ -115,11 +121,13 @@ public abstract class AbstractBeanFactoryBasedTargetSource implements TargetSour
* Return the owning BeanFactory.
*/
public BeanFactory getBeanFactory() {
Assert.state(this.beanFactory != null, "BeanFactory not set");
return this.beanFactory;
}
@Override
@Nullable
public Class<?> getTargetClass() {
Class<?> targetClass = this.targetClass;
if (targetClass != null) {
@@ -128,7 +136,7 @@ public abstract class AbstractBeanFactoryBasedTargetSource implements TargetSour
synchronized (this) {
// Full check within synchronization, entering the BeanFactory interaction algorithm only once...
targetClass = this.targetClass;
if (targetClass == null && this.beanFactory != null) {
if (targetClass == null && this.beanFactory != null && this.targetBeanName != null) {
// Determine type of the target bean.
targetClass = this.beanFactory.getType(this.targetBeanName);
if (targetClass == null) {
@@ -182,18 +190,16 @@ public abstract class AbstractBeanFactoryBasedTargetSource implements TargetSour
@Override
public int hashCode() {
int hashCode = getClass().hashCode();
hashCode = 13 * hashCode + ObjectUtils.nullSafeHashCode(this.beanFactory);
hashCode = 13 * hashCode + ObjectUtils.nullSafeHashCode(this.targetBeanName);
return hashCode;
return getClass().hashCode() * 13 + ObjectUtils.nullSafeHashCode(this.targetBeanName);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(getClass().getSimpleName());
sb.append(" for target bean '").append(this.targetBeanName).append('\'');
if (this.targetClass != null) {
sb.append(" of type [").append(this.targetClass.getName()).append(']');
Class<?> targetClass = this.targetClass;
if (targetClass != null) {
sb.append(" of type [").append(targetClass.getName()).append(']');
}
return sb.toString();
}
@@ -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.
@@ -46,6 +46,7 @@ public abstract class AbstractLazyCreationTargetSource implements TargetSource {
protected final Log logger = LogFactory.getLog(getClass());
/** The lazily initialized target object. */
@Nullable
private Object lazyTarget;
@@ -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.
@@ -70,6 +70,7 @@ public final class EmptyTargetSource implements TargetSource, Serializable {
// Instance implementation
//---------------------------------------------------------------------
@Nullable
private final Class<?> targetClass;
private final boolean isStatic;
@@ -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.
@@ -95,8 +95,7 @@ public class HotSwappableTargetSource implements TargetSource, Serializable {
/**
* Two HotSwappableTargetSources are equal if the current target
* objects are equal.
* Two HotSwappableTargetSources are equal if the current target objects are equal.
*/
@Override
public boolean equals(Object other) {
@@ -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,14 +82,8 @@ public class SingletonTargetSource implements TargetSource, Serializable {
*/
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof SingletonTargetSource)) {
return false;
}
SingletonTargetSource otherTargetSource = (SingletonTargetSource) other;
return this.target.equals(otherTargetSource.target);
return (this == other || (other instanceof SingletonTargetSource &&
this.target.equals(((SingletonTargetSource) other).target)));
}
/**
@@ -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.
@@ -58,7 +58,12 @@ public class ThreadLocalTargetSource extends AbstractPrototypeBasedTargetSource
* is meant to be per thread per instance of the ThreadLocalTargetSource class.
*/
private final ThreadLocal<Object> targetInThread =
new NamedThreadLocal<>("Thread-local instance of bean '" + getTargetBeanName() + "'");
new NamedThreadLocal<Object>("Thread-local instance of bean") {
@Override
public String toString() {
return super.toString() + " '" + getTargetBeanName() + "'";
}
};
/**
* Set of managed targets, enabling us to keep track of the targets we've created.
@@ -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,7 +16,10 @@
package org.springframework.aop.framework;
import java.sql.SQLException;
import java.sql.Savepoint;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.accessibility.Accessible;
@@ -380,6 +383,43 @@ public class ProxyFactoryTests {
assertThat(proxy.getName()).isEqualTo("tb");
}
@Test
public void testCharSequenceProxy() {
CharSequence target = "test";
ProxyFactory pf = new ProxyFactory(target);
ClassLoader cl = target.getClass().getClassLoader();
CharSequence proxy = (CharSequence) pf.getProxy(cl);
assertThat(proxy.toString()).isEqualTo(target);
}
@Test
public void testDateProxy() {
Date target = new Date();
ProxyFactory pf = new ProxyFactory(target);
pf.setProxyTargetClass(true);
ClassLoader cl = target.getClass().getClassLoader();
Date proxy = (Date) pf.getProxy(cl);
assertThat(proxy.getTime()).isEqualTo(target.getTime());
}
@Test
public void testJdbcSavepointProxy() throws SQLException {
Savepoint target = new Savepoint() {
@Override
public int getSavepointId() throws SQLException {
return 1;
}
@Override
public String getSavepointName() throws SQLException {
return "sp";
}
};
ProxyFactory pf = new ProxyFactory(target);
ClassLoader cl = Savepoint.class.getClassLoader();
Savepoint proxy = (Savepoint) pf.getProxy(cl);
assertThat(proxy.getSavepointName()).isEqualTo("sp");
}
@Order(2)
public static class A implements Runnable {
@@ -391,7 +431,7 @@ public class ProxyFactoryTests {
@Order(1)
public static class B implements Runnable{
public static class B implements Runnable {
@Override
public void run() {
@@ -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,9 +77,7 @@ public class ThrowsAdviceInterceptorTests {
given(mi.getMethod()).willReturn(Object.class.getMethod("hashCode"));
given(mi.getThis()).willReturn(new Object());
given(mi.proceed()).willThrow(ex);
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(() ->
ti.invoke(mi))
.isSameAs(ex);
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(() -> ti.invoke(mi)).isSameAs(ex);
assertThat(th.getCalls()).isEqualTo(1);
assertThat(th.getCalls("ioException")).isEqualTo(1);
}
@@ -92,9 +90,7 @@ public class ThrowsAdviceInterceptorTests {
ConnectException ex = new ConnectException("");
MethodInvocation mi = mock(MethodInvocation.class);
given(mi.proceed()).willThrow(ex);
assertThatExceptionOfType(ConnectException.class).isThrownBy(() ->
ti.invoke(mi))
.isSameAs(ex);
assertThatExceptionOfType(ConnectException.class).isThrownBy(() -> ti.invoke(mi)).isSameAs(ex);
assertThat(th.getCalls()).isEqualTo(1);
assertThat(th.getCalls("remoteException")).isEqualTo(1);
}
@@ -117,9 +113,7 @@ public class ThrowsAdviceInterceptorTests {
ConnectException ex = new ConnectException("");
MethodInvocation mi = mock(MethodInvocation.class);
given(mi.proceed()).willThrow(ex);
assertThatExceptionOfType(Throwable.class).isThrownBy(() ->
ti.invoke(mi))
.isSameAs(t);
assertThatExceptionOfType(Throwable.class).isThrownBy(() -> ti.invoke(mi)).isSameAs(t);
assertThat(th.getCalls()).isEqualTo(1);
assertThat(th.getCalls("remoteException")).isEqualTo(1);
}
@@ -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.
@@ -1004,18 +1004,20 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
*/
protected abstract static class PropertyHandler {
@Nullable
private final Class<?> propertyType;
private final boolean readable;
private final boolean writable;
public PropertyHandler(Class<?> propertyType, boolean readable, boolean writable) {
public PropertyHandler(@Nullable Class<?> propertyType, boolean readable, boolean writable) {
this.propertyType = propertyType;
this.readable = readable;
this.writable = writable;
}
@Nullable
public Class<?> getPropertyType() {
return this.propertyType;
}
@@ -23,6 +23,7 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.net.URI;
import java.net.URL;
import java.time.temporal.Temporal;
@@ -30,6 +31,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -787,38 +789,28 @@ public abstract class BeanUtils {
if (editable != null) {
if (!editable.isInstance(target)) {
throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
"] not assignable to Editable class [" + editable.getName() + "]");
"] not assignable to editable class [" + editable.getName() + "]");
}
actualEditable = editable;
}
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
Set<String> ignoredProps = (ignoreProperties != null ? new HashSet<>(Arrays.asList(ignoreProperties)) : null);
CachedIntrospectionResults sourceResults = (actualEditable != source.getClass() ?
CachedIntrospectionResults.forClass(source.getClass()) : null);
for (PropertyDescriptor targetPd : targetPds) {
Method writeMethod = targetPd.getWriteMethod();
if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
if (writeMethod != null && (ignoredProps == null || !ignoredProps.contains(targetPd.getName()))) {
PropertyDescriptor sourcePd = (sourceResults != null ?
sourceResults.getPropertyDescriptor(targetPd.getName()) : targetPd);
if (sourcePd != null) {
Method readMethod = sourcePd.getReadMethod();
if (readMethod != null) {
ResolvableType sourceResolvableType = ResolvableType.forMethodReturnType(readMethod);
ResolvableType targetResolvableType = ResolvableType.forMethodParameter(writeMethod, 0);
// Ignore generic types in assignable check if either ResolvableType has unresolvable generics.
boolean isAssignable =
(sourceResolvableType.hasUnresolvableGenerics() || targetResolvableType.hasUnresolvableGenerics() ?
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()) :
targetResolvableType.isAssignableFrom(sourceResolvableType));
if (isAssignable) {
if (isAssignable(writeMethod, readMethod)) {
try {
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
readMethod.setAccessible(true);
}
ReflectionUtils.makeAccessible(readMethod);
Object value = readMethod.invoke(source);
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
ReflectionUtils.makeAccessible(writeMethod);
writeMethod.invoke(target, value);
}
catch (Throwable ex) {
@@ -832,6 +824,24 @@ public abstract class BeanUtils {
}
}
private static boolean isAssignable(Method writeMethod, Method readMethod) {
Type paramType = writeMethod.getGenericParameterTypes()[0];
if (paramType instanceof Class) {
return ClassUtils.isAssignable((Class<?>) paramType, readMethod.getReturnType());
}
else if (paramType.equals(readMethod.getGenericReturnType())) {
return true;
}
else {
ResolvableType sourceType = ResolvableType.forMethodReturnType(readMethod);
ResolvableType targetType = ResolvableType.forMethodParameter(writeMethod, 0);
// Ignore generic types in assignable check if either ResolvableType has unresolvable generics.
return (sourceType.hasUnresolvableGenerics() || targetType.hasUnresolvableGenerics() ?
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()) :
targetType.isAssignableFrom(sourceType));
}
}
/**
* Inner class to avoid a hard dependency on Kotlin at runtime.
@@ -892,7 +902,6 @@ public abstract class BeanUtils {
}
return kotlinConstructor.callBy(argParameters);
}
}
}
@@ -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.
@@ -248,9 +248,22 @@ public final class CachedIntrospectionResults {
return beanInfo;
}
}
return (shouldIntrospectorIgnoreBeaninfoClasses ?
BeanInfo beanInfo = (shouldIntrospectorIgnoreBeaninfoClasses ?
Introspector.getBeanInfo(beanClass, Introspector.IGNORE_ALL_BEANINFO) :
Introspector.getBeanInfo(beanClass));
// Immediately remove class from Introspector cache to allow for proper garbage
// collection on class loader shutdown; we cache it in CachedIntrospectionResults
// in a GC-friendly manner. This is necessary (again) for the JDK ClassInfo cache.
Class<?> classToFlush = beanClass;
do {
Introspector.flushFromCaches(classToFlush);
classToFlush = classToFlush.getSuperclass();
}
while (classToFlush != null && classToFlush != Object.class);
return beanInfo;
}
@@ -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.
@@ -129,7 +129,6 @@ public class DirectFieldAccessor extends AbstractNestablePropertyAccessor {
ReflectionUtils.makeAccessible(this.field);
return this.field.get(getWrappedInstance());
}
catch (IllegalAccessException ex) {
throw new InvalidPropertyException(getWrappedClass(),
this.field.getName(), "Field is not accessible", 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.
@@ -136,7 +136,10 @@ public interface BeanFactory {
* <p>Translates aliases back to the corresponding canonical bean name.
* <p>Will ask the parent factory if the bean cannot be found in this factory instance.
* @param name the name of the bean to retrieve
* @return an instance of the bean
* @return an instance of the bean.
* Note that the return value will never be {@code null} but possibly a stub for
* {@code null} returned from a factory method, to be checked via {@code equals(null)}.
* Consider using {@link #getBeanProvider(Class)} for resolving optional dependencies.
* @throws NoSuchBeanDefinitionException if there is no bean with the specified name
* @throws BeansException if the bean could not be obtained
*/
@@ -152,7 +155,11 @@ public interface BeanFactory {
* <p>Will ask the parent factory if the bean cannot be found in this factory instance.
* @param name the name of the bean to retrieve
* @param requiredType type the bean must match; can be an interface or superclass
* @return an instance of the bean
* @return an instance of the bean.
* Note that the return value will never be {@code null}. In case of a stub for
* {@code null} from a factory method having been resolved for the requested bean, a
* {@code BeanNotOfRequiredTypeException} against the NullBean stub will be raised.
* Consider using {@link #getBeanProvider(Class)} for resolving optional dependencies.
* @throws NoSuchBeanDefinitionException if there is no such bean definition
* @throws BeanNotOfRequiredTypeException if the bean is not of the required type
* @throws BeansException if the bean could not be created
@@ -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,6 +17,7 @@
package org.springframework.beans.factory.annotation;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
@@ -62,6 +63,10 @@ import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -144,6 +149,9 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
@Nullable
private ConfigurableListableBeanFactory beanFactory;
@Nullable
private MetadataReaderFactory metadataReaderFactory;
private final Set<String> lookupMethodsChecked = Collections.newSetFromMap(new ConcurrentHashMap<>(256));
private final Map<Class<?>, Constructor<?>[]> candidateConstructorsCache = new ConcurrentHashMap<>(256);
@@ -238,6 +246,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
"AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory: " + beanFactory);
}
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
this.metadataReaderFactory = new SimpleMetadataReaderFactory(this.beanFactory.getBeanClassLoader());
}
@@ -463,12 +472,11 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
return InjectionMetadata.EMPTY;
}
List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
final List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
Class<?> targetClass = clazz;
do {
final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
final List<InjectionMetadata.InjectedElement> fieldElements = new ArrayList<>();
ReflectionUtils.doWithLocalFields(targetClass, field -> {
MergedAnnotation<?> ann = findAutowiredAnnotation(field);
if (ann != null) {
@@ -479,10 +487,11 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
return;
}
boolean required = determineRequiredStatus(ann);
currElements.add(new AutowiredFieldElement(field, required));
fieldElements.add(new AutowiredFieldElement(field, required));
}
});
final List<InjectionMetadata.InjectedElement> methodElements = new ArrayList<>();
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
@@ -504,11 +513,12 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
}
boolean required = determineRequiredStatus(ann);
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new AutowiredMethodElement(method, required, pd));
methodElements.add(new AutowiredMethodElement(method, required, pd));
}
});
elements.addAll(0, currElements);
elements.addAll(0, sortMethodElements(methodElements, targetClass));
elements.addAll(0, fieldElements);
targetClass = targetClass.getSuperclass();
}
while (targetClass != null && targetClass != Object.class);
@@ -573,6 +583,47 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
return BeanFactoryUtils.beansOfTypeIncludingAncestors(this.beanFactory, type);
}
/**
* Sort the method elements via ASM for deterministic declaration order if possible.
*/
private List<InjectionMetadata.InjectedElement> sortMethodElements(
List<InjectionMetadata.InjectedElement> methodElements, Class<?> targetClass) {
if (this.metadataReaderFactory != null && methodElements.size() > 1) {
// Try reading the class file via ASM for deterministic declaration order...
// Unfortunately, the JVM's standard reflection returns methods in arbitrary
// order, even between different runs of the same application on the same JVM.
try {
AnnotationMetadata asm =
this.metadataReaderFactory.getMetadataReader(targetClass.getName()).getAnnotationMetadata();
Set<MethodMetadata> asmMethods = asm.getAnnotatedMethods(Autowired.class.getName());
if (asmMethods.size() >= methodElements.size()) {
List<InjectionMetadata.InjectedElement> candidateMethods = new ArrayList<>(methodElements);
List<InjectionMetadata.InjectedElement> selectedMethods = new ArrayList<>(asmMethods.size());
for (MethodMetadata asmMethod : asmMethods) {
for (Iterator<InjectionMetadata.InjectedElement> it = candidateMethods.iterator(); it.hasNext();) {
InjectionMetadata.InjectedElement element = it.next();
if (element.getMember().getName().equals(asmMethod.getMethodName())) {
selectedMethods.add(element);
it.remove();
break;
}
}
}
if (selectedMethods.size() == methodElements.size()) {
// All reflection-detected methods found in ASM method set -> proceed
return selectedMethods;
}
}
}
catch (IOException ex) {
logger.debug("Failed to read class file via ASM for determining @Autowired method order", ex);
// No worries, let's continue with the reflection metadata we started with...
}
}
return methodElements;
}
/**
* Register the specified bean as dependent on the autowired beans.
*/
@@ -594,7 +645,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
* Resolve the specified cached method argument or field value.
*/
@Nullable
private Object resolvedCachedArgument(@Nullable String beanName, @Nullable Object cachedArgument) {
private Object resolveCachedArgument(@Nullable String beanName, @Nullable Object cachedArgument) {
if (cachedArgument instanceof DependencyDescriptor) {
DependencyDescriptor descriptor = (DependencyDescriptor) cachedArgument;
Assert.state(this.beanFactory != null, "No BeanFactory available");
@@ -629,10 +680,12 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
Object value;
if (this.cached) {
try {
value = resolvedCachedArgument(beanName, this.cachedFieldValue);
value = resolveCachedArgument(beanName, this.cachedFieldValue);
}
catch (NoSuchBeanDefinitionException ex) {
// Unexpected removal of target bean for cached argument -> re-resolve
catch (BeansException ex) {
// Unexpected target bean mismatch for cached argument -> re-resolve
this.cached = false;
logger.debug("Failed to resolve cached argument", ex);
value = resolveFieldValue(field, bean, beanName);
}
}
@@ -649,7 +702,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
private Object resolveFieldValue(Field field, Object bean, @Nullable String beanName) {
DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
desc.setContainingClass(bean.getClass());
Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
Set<String> autowiredBeanNames = new LinkedHashSet<>(2);
Assert.state(beanFactory != null, "No BeanFactory available");
TypeConverter typeConverter = beanFactory.getTypeConverter();
Object value;
@@ -661,21 +714,23 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
}
synchronized (this) {
if (!this.cached) {
Object cachedFieldValue = null;
if (value != null || this.required) {
cachedFieldValue = desc;
Object cachedFieldValue = desc;
registerDependentBeans(beanName, autowiredBeanNames);
if (autowiredBeanNames.size() == 1) {
if (value != null && autowiredBeanNames.size() == 1) {
String autowiredBeanName = autowiredBeanNames.iterator().next();
if (beanFactory.containsBean(autowiredBeanName) &&
beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
cachedFieldValue = new ShortcutDependencyDescriptor(
desc, autowiredBeanName, field.getType());
cachedFieldValue = new ShortcutDependencyDescriptor(desc, autowiredBeanName);
}
}
this.cachedFieldValue = cachedFieldValue;
this.cached = true;
}
else {
this.cachedFieldValue = null;
// cached flag remains false
}
this.cachedFieldValue = cachedFieldValue;
this.cached = true;
}
}
return value;
@@ -709,10 +764,12 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
Object[] arguments;
if (this.cached) {
try {
arguments = resolveCachedArguments(beanName);
arguments = resolveCachedArguments(beanName, this.cachedMethodArguments);
}
catch (NoSuchBeanDefinitionException ex) {
// Unexpected removal of target bean for cached argument -> re-resolve
catch (BeansException ex) {
// Unexpected target bean mismatch for cached argument -> re-resolve
this.cached = false;
logger.debug("Failed to resolve cached argument", ex);
arguments = resolveMethodArguments(method, bean, beanName);
}
}
@@ -731,14 +788,13 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
}
@Nullable
private Object[] resolveCachedArguments(@Nullable String beanName) {
Object[] cachedMethodArguments = this.cachedMethodArguments;
private Object[] resolveCachedArguments(@Nullable String beanName, @Nullable Object[] cachedMethodArguments) {
if (cachedMethodArguments == null) {
return null;
}
Object[] arguments = new Object[cachedMethodArguments.length];
for (int i = 0; i < arguments.length; i++) {
arguments[i] = resolvedCachedArgument(beanName, cachedMethodArguments[i]);
arguments[i] = resolveCachedArgument(beanName, cachedMethodArguments[i]);
}
return arguments;
}
@@ -748,7 +804,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
int argumentCount = method.getParameterCount();
Object[] arguments = new Object[argumentCount];
DependencyDescriptor[] descriptors = new DependencyDescriptor[argumentCount];
Set<String> autowiredBeans = new LinkedHashSet<>(argumentCount);
Set<String> autowiredBeanNames = new LinkedHashSet<>(argumentCount * 2);
Assert.state(beanFactory != null, "No BeanFactory available");
TypeConverter typeConverter = beanFactory.getTypeConverter();
for (int i = 0; i < arguments.length; i++) {
@@ -757,7 +813,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
currDesc.setContainingClass(bean.getClass());
descriptors[i] = currDesc;
try {
Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter);
Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeanNames, typeConverter);
if (arg == null && !this.required) {
arguments = null;
break;
@@ -771,26 +827,27 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
synchronized (this) {
if (!this.cached) {
if (arguments != null) {
DependencyDescriptor[] cachedMethodArguments = Arrays.copyOf(descriptors, arguments.length);
registerDependentBeans(beanName, autowiredBeans);
if (autowiredBeans.size() == argumentCount) {
Iterator<String> it = autowiredBeans.iterator();
DependencyDescriptor[] cachedMethodArguments = Arrays.copyOf(descriptors, argumentCount);
registerDependentBeans(beanName, autowiredBeanNames);
if (autowiredBeanNames.size() == argumentCount) {
Iterator<String> it = autowiredBeanNames.iterator();
Class<?>[] paramTypes = method.getParameterTypes();
for (int i = 0; i < paramTypes.length; i++) {
String autowiredBeanName = it.next();
if (beanFactory.containsBean(autowiredBeanName) &&
if (arguments[i] != null && beanFactory.containsBean(autowiredBeanName) &&
beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) {
cachedMethodArguments[i] = new ShortcutDependencyDescriptor(
descriptors[i], autowiredBeanName, paramTypes[i]);
descriptors[i], autowiredBeanName);
}
}
}
this.cachedMethodArguments = cachedMethodArguments;
this.cached = true;
}
else {
this.cachedMethodArguments = null;
// cached flag remains false
}
this.cached = true;
}
}
return arguments;
@@ -806,17 +863,14 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
private final String shortcut;
private final Class<?> requiredType;
public ShortcutDependencyDescriptor(DependencyDescriptor original, String shortcut, Class<?> requiredType) {
public ShortcutDependencyDescriptor(DependencyDescriptor original, String shortcut) {
super(original);
this.shortcut = shortcut;
this.requiredType = requiredType;
}
@Override
public Object resolveShortcut(BeanFactory beanFactory) {
return beanFactory.getBean(this.shortcut, this.requiredType);
return beanFactory.getBean(this.shortcut, getDependencyType());
}
}
@@ -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.
@@ -1504,8 +1504,8 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
converter = bw;
}
Set<String> autowiredBeanNames = new LinkedHashSet<>(4);
String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
Set<String> autowiredBeanNames = new LinkedHashSet<>(propertyNames.length * 2);
for (String propertyName : propertyNames) {
try {
PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName);
@@ -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.
@@ -134,7 +134,7 @@ class BeanDefinitionValueResolver {
return resolveInnerBean(argName, innerBeanName, bd);
}
else if (value instanceof DependencyDescriptor) {
Set<String> autowiredBeanNames = new LinkedHashSet<>(4);
Set<String> autowiredBeanNames = new LinkedHashSet<>(2);
Object result = this.beanFactory.resolveDependency(
(DependencyDescriptor) value, this.beanName, autowiredBeanNames, this.typeConverter);
for (String autowiredBeanName : autowiredBeanNames) {
@@ -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.
@@ -45,6 +45,7 @@ import org.springframework.beans.TypeConverter;
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.InjectionPoint;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
@@ -85,12 +86,6 @@ class ConstructorResolver {
private static final Object[] EMPTY_ARGS = new Object[0];
/**
* Marker for autowired arguments in a cached argument array, to be replaced
* by a {@linkplain #resolveAutowiredArgument resolved autowired argument}.
*/
private static final Object autowiredArgumentMarker = new Object();
private static final NamedThreadLocal<InjectionPoint> currentInjectionPoint =
new NamedThreadLocal<>("Current injection point");
@@ -729,7 +724,7 @@ class ConstructorResolver {
ArgumentsHolder args = new ArgumentsHolder(paramTypes.length);
Set<ConstructorArgumentValues.ValueHolder> usedValueHolders = new HashSet<>(paramTypes.length);
Set<String> autowiredBeanNames = new LinkedHashSet<>(4);
Set<String> allAutowiredBeanNames = new LinkedHashSet<>(paramTypes.length * 2);
for (int paramIndex = 0; paramIndex < paramTypes.length; paramIndex++) {
Class<?> paramType = paramTypes[paramIndex];
@@ -764,8 +759,8 @@ class ConstructorResolver {
throw new UnsatisfiedDependencyException(
mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam),
"Could not convert argument value of type [" +
ObjectUtils.nullSafeClassName(valueHolder.getValue()) +
"] to required type [" + paramType.getName() + "]: " + ex.getMessage());
ObjectUtils.nullSafeClassName(valueHolder.getValue()) +
"] to required type [" + paramType.getName() + "]: " + ex.getMessage());
}
Object sourceHolder = valueHolder.getSource();
if (sourceHolder instanceof ConstructorArgumentValues.ValueHolder) {
@@ -788,11 +783,17 @@ class ConstructorResolver {
"] - did you specify the correct bean references as arguments?");
}
try {
Object autowiredArgument = resolveAutowiredArgument(
methodParam, beanName, autowiredBeanNames, converter, fallback);
args.rawArguments[paramIndex] = autowiredArgument;
args.arguments[paramIndex] = autowiredArgument;
args.preparedArguments[paramIndex] = autowiredArgumentMarker;
ConstructorDependencyDescriptor desc = new ConstructorDependencyDescriptor(methodParam, true);
Set<String> autowiredBeanNames = new LinkedHashSet<>(2);
Object arg = resolveAutowiredArgument(
desc, paramType, beanName, autowiredBeanNames, converter, fallback);
if (arg != null) {
setShortcutIfPossible(desc, paramType, autowiredBeanNames);
}
allAutowiredBeanNames.addAll(autowiredBeanNames);
args.rawArguments[paramIndex] = arg;
args.arguments[paramIndex] = arg;
args.preparedArguments[paramIndex] = desc;
args.resolveNecessary = true;
}
catch (BeansException ex) {
@@ -802,14 +803,7 @@ class ConstructorResolver {
}
}
for (String autowiredBeanName : autowiredBeanNames) {
this.beanFactory.registerDependentBean(autowiredBeanName, beanName);
if (logger.isDebugEnabled()) {
logger.debug("Autowiring by type from bean name '" + beanName +
"' via " + (executable instanceof Constructor ? "constructor" : "factory method") +
" to bean named '" + autowiredBeanName + "'");
}
}
registerDependentBeans(executable, beanName, allAutowiredBeanNames);
return args;
}
@@ -829,31 +823,61 @@ class ConstructorResolver {
Object[] resolvedArgs = new Object[argsToResolve.length];
for (int argIndex = 0; argIndex < argsToResolve.length; argIndex++) {
Object argValue = argsToResolve[argIndex];
MethodParameter methodParam = MethodParameter.forExecutable(executable, argIndex);
if (argValue == autowiredArgumentMarker) {
argValue = resolveAutowiredArgument(methodParam, beanName, null, converter, true);
Class<?> paramType = paramTypes[argIndex];
boolean convertNecessary = false;
if (argValue instanceof ConstructorDependencyDescriptor) {
ConstructorDependencyDescriptor descriptor = (ConstructorDependencyDescriptor) argValue;
try {
argValue = resolveAutowiredArgument(descriptor, paramType, beanName,
null, converter, true);
}
catch (BeansException ex) {
// Unexpected target bean mismatch for cached argument -> re-resolve
Set<String> autowiredBeanNames = null;
if (descriptor.hasShortcut()) {
// Reset shortcut and try to re-resolve it in this thread...
descriptor.setShortcut(null);
autowiredBeanNames = new LinkedHashSet<>(2);
}
logger.debug("Failed to resolve cached argument", ex);
argValue = resolveAutowiredArgument(descriptor, paramType, beanName,
autowiredBeanNames, converter, true);
if (autowiredBeanNames != null && !descriptor.hasShortcut()) {
// We encountered as stale shortcut before, and the shortcut has
// not been re-resolved by another thread in the meantime...
if (argValue != null) {
setShortcutIfPossible(descriptor, paramType, autowiredBeanNames);
}
registerDependentBeans(executable, beanName, autowiredBeanNames);
}
}
}
else if (argValue instanceof BeanMetadataElement) {
argValue = valueResolver.resolveValueIfNecessary("constructor argument", argValue);
convertNecessary = true;
}
else if (argValue instanceof String) {
argValue = this.beanFactory.evaluateBeanDefinitionString((String) argValue, mbd);
convertNecessary = true;
}
Class<?> paramType = paramTypes[argIndex];
try {
resolvedArgs[argIndex] = converter.convertIfNecessary(argValue, paramType, methodParam);
}
catch (TypeMismatchException ex) {
throw new UnsatisfiedDependencyException(
mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam),
"Could not convert argument value of type [" + ObjectUtils.nullSafeClassName(argValue) +
"] to required type [" + paramType.getName() + "]: " + ex.getMessage());
if (convertNecessary) {
MethodParameter methodParam = MethodParameter.forExecutable(executable, argIndex);
try {
argValue = converter.convertIfNecessary(argValue, paramType, methodParam);
}
catch (TypeMismatchException ex) {
throw new UnsatisfiedDependencyException(
mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam),
"Could not convert argument value of type [" + ObjectUtils.nullSafeClassName(argValue) +
"] to required type [" + paramType.getName() + "]: " + ex.getMessage());
}
}
resolvedArgs[argIndex] = argValue;
}
return resolvedArgs;
}
protected Constructor<?> getUserDeclaredConstructor(Constructor<?> constructor) {
private Constructor<?> getUserDeclaredConstructor(Constructor<?> constructor) {
Class<?> declaringClass = constructor.getDeclaringClass();
Class<?> userClass = ClassUtils.getUserClass(declaringClass);
if (userClass != declaringClass) {
@@ -869,23 +893,22 @@ class ConstructorResolver {
}
/**
* Template method for resolving the specified argument which is supposed to be autowired.
* Resolve the specified argument which is supposed to be autowired.
*/
@Nullable
protected Object resolveAutowiredArgument(MethodParameter param, String beanName,
Object resolveAutowiredArgument(DependencyDescriptor descriptor, Class<?> paramType, String beanName,
@Nullable Set<String> autowiredBeanNames, TypeConverter typeConverter, boolean fallback) {
Class<?> paramType = param.getParameterType();
if (InjectionPoint.class.isAssignableFrom(paramType)) {
InjectionPoint injectionPoint = currentInjectionPoint.get();
if (injectionPoint == null) {
throw new IllegalStateException("No current InjectionPoint available for " + param);
throw new IllegalStateException("No current InjectionPoint available for " + descriptor);
}
return injectionPoint;
}
try {
return this.beanFactory.resolveDependency(
new DependencyDescriptor(param, true), beanName, autowiredBeanNames, typeConverter);
return this.beanFactory.resolveDependency(descriptor, beanName, autowiredBeanNames, typeConverter);
}
catch (NoUniqueBeanDefinitionException ex) {
throw ex;
@@ -908,6 +931,31 @@ class ConstructorResolver {
}
}
private void setShortcutIfPossible(
ConstructorDependencyDescriptor descriptor, Class<?> paramType, Set<String> autowiredBeanNames) {
if (autowiredBeanNames.size() == 1) {
String autowiredBeanName = autowiredBeanNames.iterator().next();
if (this.beanFactory.containsBean(autowiredBeanName) &&
this.beanFactory.isTypeMatch(autowiredBeanName, paramType)) {
descriptor.setShortcut(autowiredBeanName);
}
}
}
private void registerDependentBeans(
Executable executable, String beanName, Set<String> autowiredBeanNames) {
for (String autowiredBeanName : autowiredBeanNames) {
this.beanFactory.registerDependentBean(autowiredBeanName, beanName);
if (logger.isDebugEnabled()) {
logger.debug("Autowiring by type from bean name '" + beanName + "' via " +
(executable instanceof Constructor ? "constructor" : "factory method") +
" to bean named '" + autowiredBeanName + "'");
}
}
}
static InjectionPoint setCurrentInjectionPoint(@Nullable InjectionPoint injectionPoint) {
InjectionPoint old = currentInjectionPoint.get();
if (injectionPoint != null) {
@@ -1006,4 +1054,35 @@ class ConstructorResolver {
}
}
/**
* DependencyDescriptor marker for constructor arguments,
* for differentiating between a provided DependencyDescriptor instance
* and an internally built DependencyDescriptor for autowiring purposes.
*/
@SuppressWarnings("serial")
private static class ConstructorDependencyDescriptor extends DependencyDescriptor {
@Nullable
private volatile String shortcut;
public ConstructorDependencyDescriptor(MethodParameter methodParameter, boolean required) {
super(methodParameter, required);
}
public void setShortcut(@Nullable String shortcut) {
this.shortcut = shortcut;
}
public boolean hasShortcut() {
return (this.shortcut != null);
}
@Override
public Object resolveShortcut(BeanFactory beanFactory) {
String shortcut = this.shortcut;
return (shortcut != null ? beanFactory.getBean(shortcut, getDependencyType()) : 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.
@@ -961,7 +961,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
for (String beanName : beanNames) {
Object singletonInstance = getSingleton(beanName);
if (singletonInstance instanceof SmartInitializingSingleton) {
StartupStep smartInitialize = this.getApplicationStartup().start("spring.beans.smart-initialize")
StartupStep smartInitialize = getApplicationStartup().start("spring.beans.smart-initialize")
.tag("beanName", beanName);
SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
if (System.getSecurityManager() != null) {
@@ -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.
@@ -102,7 +102,7 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
private boolean singletonsCurrentlyInDestruction = false;
/** Disposable bean instances: bean name to disposable instance. */
private final Map<String, Object> disposableBeans = new LinkedHashMap<>();
private final Map<String, DisposableBean> disposableBeans = new LinkedHashMap<>();
/** Map between containing bean names: bean name to Set of bean names that the bean contains. */
private final Map<String, Set<String>> containedBeanMap = new ConcurrentHashMap<>(16);
@@ -340,7 +340,7 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
* (within the entire factory).
* @param beanName the name of the bean
*/
public boolean isSingletonCurrentlyInCreation(String beanName) {
public boolean isSingletonCurrentlyInCreation(@Nullable String beanName) {
return this.singletonsCurrentlyInCreation.contains(beanName);
}
@@ -447,17 +447,17 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
}
String canonicalName = canonicalName(beanName);
Set<String> dependentBeans = this.dependentBeanMap.get(canonicalName);
if (dependentBeans == null) {
if (dependentBeans == null || dependentBeans.isEmpty()) {
return false;
}
if (dependentBeans.contains(dependentBeanName)) {
return true;
}
if (alreadySeen == null) {
alreadySeen = new HashSet<>();
}
alreadySeen.add(beanName);
for (String transitiveDependency : dependentBeans) {
if (alreadySeen == null) {
alreadySeen = new HashSet<>();
}
alreadySeen.add(beanName);
if (isDependent(transitiveDependency, dependentBeanName, alreadySeen)) {
return true;
}
@@ -554,7 +554,7 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
// Destroy the corresponding DisposableBean instance.
DisposableBean disposableBean;
synchronized (this.disposableBeans) {
disposableBean = (DisposableBean) this.disposableBeans.remove(beanName);
disposableBean = this.disposableBeans.remove(beanName);
}
destroyBean(beanName, disposableBean);
}
@@ -68,8 +68,8 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
private static final String SHUTDOWN_METHOD_NAME = "shutdown";
private static final Log logger = LogFactory.getLog(DisposableBeanAdapter.class);
private static final Log logger = LogFactory.getLog(DisposableBeanAdapter.class);
private final Object bean;
@@ -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.
@@ -112,7 +112,7 @@ public class LookupOverride extends MethodOverride {
@Override
public int hashCode() {
return (29 * super.hashCode() + ObjectUtils.nullSafeHashCode(this.beanName));
return super.hashCode() * 29 + ObjectUtils.nullSafeHashCode(this.beanName);
}
@Override
@@ -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.
@@ -506,23 +506,12 @@ public class RootBeanDefinition extends AbstractBeanDefinition {
if (isExternallyManagedInitMethod(initMethod)) {
return true;
}
if (this.externallyManagedInitMethods != null) {
for (String candidate : this.externallyManagedInitMethods) {
int indexOfDot = candidate.lastIndexOf('.');
if (indexOfDot >= 0) {
String methodName = candidate.substring(indexOfDot + 1);
if (methodName.equals(initMethod)) {
return true;
}
}
}
}
return false;
return hasAnyExternallyManagedMethod(this.externallyManagedInitMethods, initMethod);
}
}
/**
* Return all externally managed initialization methods (as an immutable Set).
* Get all externally managed initialization methods (as an immutable Set).
* <p>See {@link #registerExternallyManagedInitMethod} for details
* regarding the format for the initialization methods in the returned set.
* @since 5.3.11
@@ -583,19 +572,23 @@ public class RootBeanDefinition extends AbstractBeanDefinition {
if (isExternallyManagedDestroyMethod(destroyMethod)) {
return true;
}
if (this.externallyManagedDestroyMethods != null) {
for (String candidate : this.externallyManagedDestroyMethods) {
int indexOfDot = candidate.lastIndexOf('.');
if (indexOfDot >= 0) {
String methodName = candidate.substring(indexOfDot + 1);
if (methodName.equals(destroyMethod)) {
return true;
}
return hasAnyExternallyManagedMethod(this.externallyManagedDestroyMethods, destroyMethod);
}
}
private static boolean hasAnyExternallyManagedMethod(Set<String> candidates, String methodName) {
if (candidates != null) {
for (String candidate : candidates) {
int indexOfDot = candidate.lastIndexOf('.');
if (indexOfDot > 0) {
String candidateMethodName = candidate.substring(indexOfDot + 1);
if (candidateMethodName.equals(methodName)) {
return true;
}
}
}
return false;
}
return false;
}
/**
@@ -1271,10 +1271,11 @@ class DefaultListableBeanFactoryTests {
lbf.registerBeanDefinition("rod", bd);
RootBeanDefinition bd2 = new RootBeanDefinition(TestBean.class);
lbf.registerBeanDefinition("rod2", bd2);
assertThatExceptionOfType(UnsatisfiedDependencyException.class).isThrownBy(() ->
lbf.autowire(ConstructorDependency.class, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false))
.withMessageContaining("rod")
.withMessageContaining("rod2");
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
.isThrownBy(() -> lbf.autowire(ConstructorDependency.class, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false))
.withMessageContaining("rod")
.withMessageContaining("rod2");
}
@Test
@@ -1336,11 +1337,12 @@ class DefaultListableBeanFactoryTests {
RootBeanDefinition bd2 = new RootBeanDefinition(TestBean.class);
bd2.setDependsOn("tb1");
lbf.registerBeanDefinition("tb2", bd2);
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(() ->
lbf.preInstantiateSingletons())
.withMessageContaining("Circular")
.withMessageContaining("'tb2'")
.withMessageContaining("'tb1'");
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> lbf.preInstantiateSingletons())
.withMessageContaining("Circular")
.withMessageContaining("'tb2'")
.withMessageContaining("'tb1'");
}
@Test
@@ -1354,11 +1356,12 @@ class DefaultListableBeanFactoryTests {
RootBeanDefinition bd3 = new RootBeanDefinition(TestBean.class);
bd3.setDependsOn("tb1");
lbf.registerBeanDefinition("tb3", bd3);
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(
lbf::preInstantiateSingletons)
.withMessageContaining("Circular")
.withMessageContaining("'tb3'")
.withMessageContaining("'tb1'");
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(lbf::preInstantiateSingletons)
.withMessageContaining("Circular")
.withMessageContaining("'tb3'")
.withMessageContaining("'tb1'");
}
@Test
@@ -1493,10 +1496,11 @@ class DefaultListableBeanFactoryTests {
RootBeanDefinition bd2 = new RootBeanDefinition(HighPriorityTestBean.class);
lbf.registerBeanDefinition("bd1", bd1);
lbf.registerBeanDefinition("bd2", bd2);
assertThatExceptionOfType(NoUniqueBeanDefinitionException.class).isThrownBy(() ->
lbf.getBean(TestBean.class))
.withMessageContaining("Multiple beans found with the same priority")
.withMessageContaining("5"); // conflicting priority
assertThatExceptionOfType(NoUniqueBeanDefinitionException.class)
.isThrownBy(() -> lbf.getBean(TestBean.class))
.withMessageContaining("Multiple beans found with the same priority")
.withMessageContaining("5"); // conflicting priority
}
@Test
@@ -1698,9 +1702,9 @@ class DefaultListableBeanFactoryTests {
lbf.registerBeanDefinition("bd1", bd1);
lbf.registerBeanDefinition("bd2", bd2);
assertThatExceptionOfType(NoUniqueBeanDefinitionException.class).isThrownBy(() ->
lbf.getBean(ConstructorDependency.class, 42))
.withMessageContaining("more than one 'primary'");
assertThatExceptionOfType(NoUniqueBeanDefinitionException.class)
.isThrownBy(() -> lbf.getBean(ConstructorDependency.class, 42))
.withMessageContaining("more than one 'primary'");
}
@Test
@@ -1881,10 +1885,11 @@ class DefaultListableBeanFactoryTests {
RootBeanDefinition bd2 = new RootBeanDefinition(TestBean.class);
lbf.registerBeanDefinition("test", bd);
lbf.registerBeanDefinition("spouse", bd2);
assertThatExceptionOfType(UnsatisfiedDependencyException.class).isThrownBy(() ->
lbf.autowire(DependenciesBean.class, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true))
.withMessageContaining("test")
.withMessageContaining("spouse");
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
.isThrownBy(() -> lbf.autowire(DependenciesBean.class, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true))
.withMessageContaining("test")
.withMessageContaining("spouse");
}
@Test
@@ -1946,10 +1951,11 @@ class DefaultListableBeanFactoryTests {
RootBeanDefinition bd2 = new RootBeanDefinition(HighPriorityTestBean.class);
lbf.registerBeanDefinition("test", bd);
lbf.registerBeanDefinition("spouse", bd2);
assertThatExceptionOfType(UnsatisfiedDependencyException.class).isThrownBy(() ->
lbf.autowire(DependenciesBean.class, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true))
.withCauseExactlyInstanceOf(NoUniqueBeanDefinitionException.class)
.withMessageContaining("5");
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
.isThrownBy(() -> lbf.autowire(DependenciesBean.class, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true))
.withCauseExactlyInstanceOf(NoUniqueBeanDefinitionException.class)
.withMessageContaining("5");
}
@Test
@@ -2185,19 +2191,21 @@ class DefaultListableBeanFactoryTests {
@Test
void beanDefinitionWithInterface() {
lbf.registerBeanDefinition("test", new RootBeanDefinition(ITestBean.class));
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(() ->
lbf.getBean("test"))
.withMessageContaining("interface")
.satisfies(ex -> assertThat(ex.getBeanName()).isEqualTo("test"));
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> lbf.getBean("test"))
.withMessageContaining("interface")
.satisfies(ex -> assertThat(ex.getBeanName()).isEqualTo("test"));
}
@Test
void beanDefinitionWithAbstractClass() {
lbf.registerBeanDefinition("test", new RootBeanDefinition(AbstractBeanFactory.class));
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(() ->
lbf.getBean("test"))
.withMessageContaining("abstract")
.satisfies(ex -> assertThat(ex.getBeanName()).isEqualTo("test"));
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> lbf.getBean("test"))
.withMessageContaining("abstract")
.satisfies(ex -> assertThat(ex.getBeanName()).isEqualTo("test"));
}
@Test
@@ -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.
@@ -28,36 +28,32 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Chris Beams
* @since 04.07.2006
*/
public class DefaultSingletonBeanRegistryTests {
class DefaultSingletonBeanRegistryTests {
private final DefaultSingletonBeanRegistry beanRegistry = new DefaultSingletonBeanRegistry();
@Test
public void testSingletons() {
DefaultSingletonBeanRegistry beanRegistry = new DefaultSingletonBeanRegistry();
void singletons() {
TestBean tb = new TestBean();
beanRegistry.registerSingleton("tb", tb);
assertThat(beanRegistry.getSingleton("tb")).isSameAs(tb);
TestBean tb2 = (TestBean) beanRegistry.getSingleton("tb2", () -> new TestBean());
TestBean tb2 = (TestBean) beanRegistry.getSingleton("tb2", TestBean::new);
assertThat(beanRegistry.getSingleton("tb2")).isSameAs(tb2);
assertThat(beanRegistry.getSingleton("tb")).isSameAs(tb);
assertThat(beanRegistry.getSingleton("tb2")).isSameAs(tb2);
assertThat(beanRegistry.getSingletonCount()).isEqualTo(2);
String[] names = beanRegistry.getSingletonNames();
assertThat(names.length).isEqualTo(2);
assertThat(names[0]).isEqualTo("tb");
assertThat(names[1]).isEqualTo("tb2");
assertThat(beanRegistry.getSingletonNames()).containsExactly("tb", "tb2");
beanRegistry.destroySingletons();
assertThat(beanRegistry.getSingletonCount()).isEqualTo(0);
assertThat(beanRegistry.getSingletonNames().length).isEqualTo(0);
assertThat(beanRegistry.getSingletonCount()).isZero();
assertThat(beanRegistry.getSingletonNames()).isEmpty();
}
@Test
public void testDisposableBean() {
DefaultSingletonBeanRegistry beanRegistry = new DefaultSingletonBeanRegistry();
void disposableBean() {
DerivedTestBean tb = new DerivedTestBean();
beanRegistry.registerSingleton("tb", tb);
beanRegistry.registerDisposableBean("tb", tb);
@@ -65,21 +61,16 @@ public class DefaultSingletonBeanRegistryTests {
assertThat(beanRegistry.getSingleton("tb")).isSameAs(tb);
assertThat(beanRegistry.getSingletonCount()).isEqualTo(1);
String[] names = beanRegistry.getSingletonNames();
assertThat(names.length).isEqualTo(1);
assertThat(names[0]).isEqualTo("tb");
assertThat(beanRegistry.getSingletonNames()).containsExactly("tb");
assertThat(tb.wasDestroyed()).isFalse();
beanRegistry.destroySingletons();
assertThat(beanRegistry.getSingletonCount()).isEqualTo(0);
assertThat(beanRegistry.getSingletonNames().length).isEqualTo(0);
assertThat(tb.wasDestroyed()).isTrue();
assertThat(beanRegistry.getSingletonCount()).isZero();
assertThat(beanRegistry.getSingletonNames()).isEmpty();
}
@Test
public void testDependentRegistration() {
DefaultSingletonBeanRegistry beanRegistry = new DefaultSingletonBeanRegistry();
void dependentRegistration() {
beanRegistry.registerDependentBean("a", "b");
beanRegistry.registerDependentBean("b", "c");
beanRegistry.registerDependentBean("c", "b");
@@ -4,25 +4,27 @@
<beans default-lazy-init="true">
<bean name="beta" class="org.springframework.beans.factory.FactoryBeanTests$Beta" autowire="byType">
<property name="name" value="${myName}"/>
<property name="name" value="${myName}"/>
</bean>
<bean id="alpha" class="org.springframework.beans.factory.FactoryBeanTests$Alpha" autowire="byType"/>
<bean id="gamma" class="org.springframework.beans.factory.FactoryBeanTests$Gamma"/>
<bean id="betaFactory" class="org.springframework.beans.factory.FactoryBeanTests$BetaFactoryBean" autowire="constructor">
<property name="beta" ref="beta"/>
</bean>
<bean id="betaFactory" class="org.springframework.beans.factory.FactoryBeanTests$BetaFactoryBean" autowire="constructor">
<property name="beta" ref="beta"/>
</bean>
<bean id="gammaFactory" factory-bean="betaFactory" factory-method="getGamma"/>
<bean id="gammaFactory" factory-bean="${gammaFactory}" factory-method="${gamma}"/>
<bean id="propertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="properties">
<props>
<prop key="myName">yourName</prop>
</props>
</property>
</bean>
<bean id="propertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="properties">
<props>
<prop key="myName">yourName</prop>
<prop key="gammaFactory">betaFactory</prop>
<prop key="gamma">getGamma</prop>
</props>
</property>
</bean>
</beans>
@@ -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.
@@ -477,7 +477,7 @@ public class TestBean implements BeanNameAware, BeanFactoryAware, ITestBean, IOt
@Override
public int hashCode() {
return this.age;
return TestBean.class.hashCode();
}
@Override
@@ -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.
@@ -59,8 +59,8 @@ public class CaffeineCache extends AbstractValueAdaptingCache {
* given internal {@link com.github.benmanes.caffeine.cache.Cache} to use.
* @param name the name of the cache
* @param cache the backing Caffeine Cache instance
* @param allowNullValues whether to accept and convert {@code null}
* values for this cache
* @param allowNullValues whether to accept and convert {@code null} values
* for this cache
*/
public CaffeineCache(String name, com.github.benmanes.caffeine.cache.Cache<Object, Object> cache,
boolean allowNullValues) {
@@ -86,7 +86,7 @@ public class CaffeineCache extends AbstractValueAdaptingCache {
@SuppressWarnings("unchecked")
@Override
@Nullable
public <T> T get(Object key, final Callable<T> valueLoader) {
public <T> T get(Object key, Callable<T> valueLoader) {
return (T) fromStoreValue(this.cache.get(key, new LoadFunction(valueLoader)));
}
@@ -106,7 +106,7 @@ public class CaffeineCache extends AbstractValueAdaptingCache {
@Override
@Nullable
public ValueWrapper putIfAbsent(Object key, @Nullable final Object value) {
public ValueWrapper putIfAbsent(Object key, @Nullable Object value) {
PutIfAbsentFunction callable = new PutIfAbsentFunction(value);
Object result = this.cache.get(key, callable);
return (callable.called ? null : toValueWrapper(result));
@@ -140,7 +140,7 @@ public class CaffeineCache extends AbstractValueAdaptingCache {
@Nullable
private final Object value;
private boolean called;
boolean called;
public PutIfAbsentFunction(@Nullable Object value) {
this.value = value;
@@ -159,16 +159,17 @@ public class CaffeineCache extends AbstractValueAdaptingCache {
private final Callable<?> valueLoader;
public LoadFunction(Callable<?> valueLoader) {
Assert.notNull(valueLoader, "Callable must not be null");
this.valueLoader = valueLoader;
}
@Override
public Object apply(Object o) {
public Object apply(Object key) {
try {
return toStoreValue(this.valueLoader.call());
}
catch (Exception ex) {
throw new ValueRetrievalException(o, this.valueLoader, ex);
throw new ValueRetrievalException(key, this.valueLoader, ex);
}
}
}
@@ -110,7 +110,7 @@ public class CaffeineCacheManager implements CacheManager {
* Set the Caffeine to use for building each individual
* {@link CaffeineCache} instance.
* @see #createNativeCaffeineCache
* @see com.github.benmanes.caffeine.cache.Caffeine#build()
* @see Caffeine#build()
*/
public void setCaffeine(Caffeine<Object, Object> caffeine) {
Assert.notNull(caffeine, "Caffeine must not be null");
@@ -121,7 +121,7 @@ public class CaffeineCacheManager implements CacheManager {
* Set the {@link CaffeineSpec} to use for building each individual
* {@link CaffeineCache} instance.
* @see #createNativeCaffeineCache
* @see com.github.benmanes.caffeine.cache.Caffeine#from(CaffeineSpec)
* @see Caffeine#from(CaffeineSpec)
*/
public void setCaffeineSpec(CaffeineSpec caffeineSpec) {
doSetCaffeine(Caffeine.from(caffeineSpec));
@@ -132,7 +132,7 @@ public class CaffeineCacheManager implements CacheManager {
* individual {@link CaffeineCache} instance. The given value needs to
* comply with Caffeine's {@link CaffeineSpec} (see its javadoc).
* @see #createNativeCaffeineCache
* @see com.github.benmanes.caffeine.cache.Caffeine#from(String)
* @see Caffeine#from(String)
*/
public void setCacheSpecification(String cacheSpecification) {
doSetCaffeine(Caffeine.from(cacheSpecification));
@@ -149,7 +149,7 @@ public class CaffeineCacheManager implements CacheManager {
* Set the Caffeine CacheLoader to use for building each individual
* {@link CaffeineCache} instance, turning it into a LoadingCache.
* @see #createNativeCaffeineCache
* @see com.github.benmanes.caffeine.cache.Caffeine#build(CacheLoader)
* @see Caffeine#build(CacheLoader)
* @see com.github.benmanes.caffeine.cache.LoadingCache
*/
public void setCacheLoader(CacheLoader<Object, Object> cacheLoader) {
@@ -189,13 +189,11 @@ public class CaffeineCacheManager implements CacheManager {
@Override
@Nullable
public Cache getCache(String name) {
if (this.dynamic) {
Cache cache = this.cacheMap.get(name);
return (cache != null) ? cache : this.cacheMap.computeIfAbsent(name, this::createCaffeineCache);
}
else {
return this.cacheMap.get(name);
Cache cache = this.cacheMap.get(name);
if (cache == null && this.dynamic) {
cache = this.cacheMap.computeIfAbsent(name, this::createCaffeineCache);
}
return cache;
}
@@ -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.
@@ -38,19 +38,17 @@ public class CaffeineCacheManagerTests {
@Test
public void testDynamicMode() {
CacheManager cm = new CaffeineCacheManager();
Cache cache1 = cm.getCache("c1");
boolean condition2 = cache1 instanceof CaffeineCache;
assertThat(condition2).isTrue();
assertThat(cache1).isInstanceOf(CaffeineCache.class);
Cache cache1again = cm.getCache("c1");
assertThat(cache1).isSameAs(cache1again);
Cache cache2 = cm.getCache("c2");
boolean condition1 = cache2 instanceof CaffeineCache;
assertThat(condition1).isTrue();
assertThat(cache2).isInstanceOf(CaffeineCache.class);
Cache cache2again = cm.getCache("c2");
assertThat(cache2).isSameAs(cache2again);
Cache cache3 = cm.getCache("c3");
boolean condition = cache3 instanceof CaffeineCache;
assertThat(condition).isTrue();
assertThat(cache3).isInstanceOf(CaffeineCache.class);
Cache cache3again = cm.getCache("c3");
assertThat(cache3).isSameAs(cache3again);
@@ -62,19 +60,23 @@ public class CaffeineCacheManagerTests {
assertThat(cache1.get("key3").get()).isNull();
cache1.evict("key3");
assertThat(cache1.get("key3")).isNull();
assertThat(cache1.get("key3", () -> "value3")).isEqualTo("value3");
assertThat(cache1.get("key3", () -> "value3")).isEqualTo("value3");
cache1.evict("key3");
assertThat(cache1.get("key3", () -> (String) null)).isNull();
assertThat(cache1.get("key3", () -> (String) null)).isNull();
}
@Test
public void testStaticMode() {
CaffeineCacheManager cm = new CaffeineCacheManager("c1", "c2");
Cache cache1 = cm.getCache("c1");
boolean condition3 = cache1 instanceof CaffeineCache;
assertThat(condition3).isTrue();
assertThat(cache1).isInstanceOf(CaffeineCache.class);
Cache cache1again = cm.getCache("c1");
assertThat(cache1).isSameAs(cache1again);
Cache cache2 = cm.getCache("c2");
boolean condition2 = cache2 instanceof CaffeineCache;
assertThat(condition2).isTrue();
assertThat(cache2).isInstanceOf(CaffeineCache.class);
Cache cache2again = cm.getCache("c2");
assertThat(cache2).isSameAs(cache2again);
Cache cache3 = cm.getCache("c3");
@@ -91,13 +93,11 @@ public class CaffeineCacheManagerTests {
cm.setAllowNullValues(false);
Cache cache1x = cm.getCache("c1");
boolean condition1 = cache1x instanceof CaffeineCache;
assertThat(condition1).isTrue();
assertThat(cache1x != cache1).isTrue();
assertThat(cache1x).isInstanceOf(CaffeineCache.class);
assertThat(cache1x).isNotSameAs(cache1);
Cache cache2x = cm.getCache("c2");
boolean condition = cache2x instanceof CaffeineCache;
assertThat(condition).isTrue();
assertThat(cache2x != cache2).isTrue();
assertThat(cache2x).isInstanceOf(CaffeineCache.class);
assertThat(cache2x).isNotSameAs(cache2);
Cache cache3x = cm.getCache("c3");
assertThat(cache3x).isNull();
@@ -190,7 +190,7 @@ public class CaffeineCacheManagerTests {
assertThat(value.get()).isEqualTo("pong");
assertThatIllegalArgumentException().isThrownBy(() -> assertThat(cache1.get("foo")).isNull())
.withMessageContaining("I only know ping");
.withMessageContaining("I only know ping");
}
@Test
@@ -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.
@@ -114,7 +114,7 @@ class QuartzSupportTests {
trigger.setName("myTrigger");
trigger.setJobDetail(jobDetail);
trigger.setStartDelay(1);
trigger.setRepeatInterval(500);
trigger.setRepeatInterval(100);
trigger.setRepeatCount(1);
trigger.afterPropertiesSet();
@@ -126,14 +126,14 @@ class QuartzSupportTests {
bean.start();
Thread.sleep(500);
assertThat(DummyJob.count > 0).as("DummyJob should have been executed at least once.").isTrue();
assertThat(DummyJob.count).as("DummyJob should have been executed at least once.").isGreaterThan(0);
assertThat(taskExecutor.count).isEqualTo(DummyJob.count);
bean.destroy();
}
@Test
@SuppressWarnings({ "unchecked", "rawtypes" })
@SuppressWarnings({"unchecked", "rawtypes"})
void jobDetailWithRunnableInsteadOfJob() {
JobDetailImpl jobDetail = new JobDetailImpl();
assertThatIllegalArgumentException().isThrownBy(() ->
@@ -156,7 +156,7 @@ class QuartzSupportTests {
trigger.setName("myTrigger");
trigger.setJobDetail(jobDetail);
trigger.setStartDelay(1);
trigger.setRepeatInterval(500);
trigger.setRepeatInterval(100);
trigger.setRepeatCount(1);
trigger.afterPropertiesSet();
@@ -168,7 +168,7 @@ class QuartzSupportTests {
Thread.sleep(500);
assertThat(DummyJobBean.param).isEqualTo(10);
assertThat(DummyJobBean.count > 0).isTrue();
assertThat(DummyJobBean.count).isGreaterThan(0);
bean.destroy();
}
@@ -190,7 +190,7 @@ class QuartzSupportTests {
trigger.setName("myTrigger");
trigger.setJobDetail(jobDetail);
trigger.setStartDelay(1);
trigger.setRepeatInterval(500);
trigger.setRepeatInterval(100);
trigger.setRepeatCount(1);
trigger.afterPropertiesSet();
@@ -203,7 +203,7 @@ class QuartzSupportTests {
Thread.sleep(500);
assertThat(DummyJob.param).isEqualTo(10);
assertThat(DummyJob.count > 0).as("DummyJob should have been executed at least once.").isTrue();
assertThat(DummyJob.count).as("DummyJob should have been executed at least once.").isGreaterThan(0);
bean.destroy();
}
@@ -225,7 +225,7 @@ class QuartzSupportTests {
trigger.setName("myTrigger");
trigger.setJobDetail(jobDetail);
trigger.setStartDelay(1);
trigger.setRepeatInterval(500);
trigger.setRepeatInterval(100);
trigger.setRepeatCount(1);
trigger.afterPropertiesSet();
@@ -239,7 +239,7 @@ class QuartzSupportTests {
Thread.sleep(500);
assertThat(DummyJob.param).isEqualTo(0);
assertThat(DummyJob.count == 0).isTrue();
assertThat(DummyJob.count).isEqualTo(0);
bean.destroy();
}
@@ -260,7 +260,7 @@ class QuartzSupportTests {
trigger.setName("myTrigger");
trigger.setJobDetail(jobDetail);
trigger.setStartDelay(1);
trigger.setRepeatInterval(500);
trigger.setRepeatInterval(100);
trigger.setRepeatCount(1);
trigger.afterPropertiesSet();
@@ -273,7 +273,7 @@ class QuartzSupportTests {
Thread.sleep(500);
assertThat(DummyJobBean.param).isEqualTo(10);
assertThat(DummyJobBean.count > 0).isTrue();
assertThat(DummyJobBean.count).isGreaterThan(0);
bean.destroy();
}
@@ -292,7 +292,7 @@ class QuartzSupportTests {
Thread.sleep(500);
assertThat(DummyJob.param).isEqualTo(10);
assertThat(DummyJob.count > 0).as("DummyJob should have been executed at least once.").isTrue();
assertThat(DummyJob.count).as("DummyJob should have been executed at least once.").isGreaterThan(0);
bean.destroy();
}
@@ -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.
@@ -23,14 +23,20 @@ import org.springframework.lang.Nullable;
/**
* Interface that defines common cache operations.
*
* <b>Note:</b> Due to the generic use of caching, it is recommended that
* implementations allow storage of {@code null} values (for example to
* cache methods that return {@code null}).
* <p>Serves as an SPI for Spring's annotation-based caching model
* ({@link org.springframework.cache.annotation.Cacheable} and co)
* as well as an API for direct usage in applications.
*
* <p><b>Note:</b> Due to the generic use of caching, it is recommended
* that implementations allow storage of {@code null} values
* (for example to cache methods that return {@code null}).
*
* @author Costin Leau
* @author Juergen Hoeller
* @author Stephane Nicoll
* @since 3.1
* @see CacheManager
* @see org.springframework.cache.annotation.Cacheable
*/
public interface Cache {
@@ -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.
@@ -175,9 +175,9 @@ public @interface Cacheable {
* <li>Only one cache may be specified</li>
* <li>No other cache-related operation can be combined</li>
* </ol>
* This is effectively a hint and the actual cache provider that you are
* using may not support it in a synchronized fashion. Check your provider
* documentation for more details on the actual semantics.
* This is effectively a hint and the chosen cache provider might not actually
* support it in a synchronized fashion. Check your provider documentation for
* more details on the actual semantics.
* @since 4.3
* @see org.springframework.cache.Cache#get(Object, Callable)
*/
@@ -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.
@@ -166,13 +166,7 @@ public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderA
public Cache getCache(String name) {
Cache cache = this.cacheMap.get(name);
if (cache == null && this.dynamic) {
synchronized (this.cacheMap) {
cache = this.cacheMap.get(name);
if (cache == null) {
cache = createConcurrentMapCache(name);
this.cacheMap.put(name, cache);
}
}
cache = this.cacheMap.computeIfAbsent(name, this::createConcurrentMapCache);
}
return cache;
}
@@ -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.
@@ -82,12 +82,12 @@ public abstract class AbstractCacheInvoker {
* Execute {@link Cache#put(Object, Object)} on the specified {@link Cache}
* and invoke the error handler if an exception occurs.
*/
protected void doPut(Cache cache, Object key, @Nullable Object result) {
protected void doPut(Cache cache, Object key, @Nullable Object value) {
try {
cache.put(key, result);
cache.put(key, value);
}
catch (RuntimeException ex) {
getErrorHandler().handleCachePutError(ex, cache, key, result);
getErrorHandler().handleCachePutError(ex, cache, key, value);
}
}
@@ -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.
@@ -214,7 +214,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
@Override
public void afterSingletonsInstantiated() {
if (getCacheResolver() == null) {
// Lazily initialize cache resolver via default cache manager...
// Lazily initialize cache resolver via default cache manager
Assert.state(this.beanFactory != null, "CacheResolver or BeanFactory must be set on cache aspect");
try {
setCacheManager(this.beanFactory.getBean(CacheManager.class));
@@ -307,22 +307,22 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
}
/**
* Return a bean with the specified name and type. Used to resolve services that
* are referenced by name in a {@link CacheOperation}.
* @param beanName the name of the bean, as defined by the operation
* @param expectedType type for the bean
* @return the bean matching that name
* Retrieve a bean with the specified name and type.
* Used to resolve services that are referenced by name in a {@link CacheOperation}.
* @param name the name of the bean, as defined by the cache operation
* @param serviceType the type expected by the operation's service reference
* @return the bean matching the expected type, qualified by the given name
* @throws org.springframework.beans.factory.NoSuchBeanDefinitionException if such bean does not exist
* @see CacheOperation#getKeyGenerator()
* @see CacheOperation#getCacheManager()
* @see CacheOperation#getCacheResolver()
*/
protected <T> T getBean(String beanName, Class<T> expectedType) {
protected <T> T getBean(String name, Class<T> serviceType) {
if (this.beanFactory == null) {
throw new IllegalStateException(
"BeanFactory must be set on cache aspect for " + expectedType.getSimpleName() + " retrieval");
"BeanFactory must be set on cache aspect for " + serviceType.getSimpleName() + " retrieval");
}
return BeanFactoryAnnotationUtils.qualifiedBeanOfType(this.beanFactory, expectedType, beanName);
return BeanFactoryAnnotationUtils.qualifiedBeanOfType(this.beanFactory, serviceType, name);
}
/**
@@ -388,21 +388,20 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
}
}
else {
// No caching required, only call the underlying method
// No caching required, just call the underlying method
return invokeOperation(invoker);
}
}
// Process any early evictions
processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
CacheOperationExpressionEvaluator.NO_RESULT);
// Check if we have a cached item matching the conditions
// Check if we have a cached value matching the conditions
Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));
// Collect puts from any @Cacheable miss, if no cached item is found
List<CachePutRequest> cachePutRequests = new ArrayList<>();
// Collect puts from any @Cacheable miss, if no cached value is found
List<CachePutRequest> cachePutRequests = new ArrayList<>(1);
if (cacheHit == null) {
collectPutRequests(contexts.get(CacheableOperation.class),
CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
@@ -469,7 +468,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
private boolean hasCachePut(CacheOperationContexts contexts) {
// Evaluate the conditions *without* the result object because we don't have it yet...
Collection<CacheOperationContext> cachePutContexts = contexts.get(CachePutOperation.class);
Collection<CacheOperationContext> excluded = new ArrayList<>();
Collection<CacheOperationContext> excluded = new ArrayList<>(1);
for (CacheOperationContext context : cachePutContexts) {
try {
if (!context.isConditionPassing(CacheOperationExpressionEvaluator.RESULT_UNAVAILABLE)) {
@@ -522,9 +521,9 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
}
/**
* Find a cached item only for {@link CacheableOperation} that passes the condition.
* Find a cached value only for {@link CacheableOperation} that passes the condition.
* @param contexts the cacheable operations
* @return a {@link Cache.ValueWrapper} holding the cached item,
* @return a {@link Cache.ValueWrapper} holding the cached value,
* or {@code null} if none is found
*/
@Nullable
@@ -549,9 +548,9 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
/**
* Collect the {@link CachePutRequest} for all {@link CacheOperation} using
* the specified result item.
* the specified result value.
* @param contexts the contexts to handle
* @param result the result item (never {@code null})
* @param result the result value (never {@code null})
* @param putRequests the collection to update
*/
private void collectPutRequests(Collection<CacheOperationContext> contexts,
@@ -641,21 +640,22 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
if (syncEnabled) {
if (this.contexts.size() > 1) {
throw new IllegalStateException(
"@Cacheable(sync=true) cannot be combined with other cache operations on '" + method + "'");
"A sync=true operation cannot be combined with other cache operations on '" + method + "'");
}
if (cacheOperationContexts.size() > 1) {
throw new IllegalStateException(
"Only one @Cacheable(sync=true) entry is allowed on '" + method + "'");
"Only one sync=true operation is allowed on '" + method + "'");
}
CacheOperationContext cacheOperationContext = cacheOperationContexts.iterator().next();
CacheableOperation operation = (CacheableOperation) cacheOperationContext.getOperation();
CacheOperation operation = cacheOperationContext.getOperation();
if (cacheOperationContext.getCaches().size() > 1) {
throw new IllegalStateException(
"@Cacheable(sync=true) only allows a single cache on '" + operation + "'");
"A sync=true operation is restricted to a single cache on '" + operation + "'");
}
if (StringUtils.hasText(operation.getUnless())) {
if (operation instanceof CacheableOperation &&
StringUtils.hasText(((CacheableOperation) operation).getUnless())) {
throw new IllegalStateException(
"@Cacheable(sync=true) does not support unless attribute on '" + operation + "'");
"A sync=true operation does not support the unless attribute on '" + operation + "'");
}
return true;
}
@@ -722,7 +722,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
this.args = extractArgs(metadata.method, args);
this.target = target;
this.caches = CacheAspectSupport.this.getCaches(this, metadata.cacheResolver);
this.cacheNames = createCacheNames(this.caches);
this.cacheNames = prepareCacheNames(this.caches);
}
@Override
@@ -810,8 +810,8 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
return this.cacheNames;
}
private Collection<String> createCacheNames(Collection<? extends Cache> caches) {
Collection<String> names = new ArrayList<>();
private Collection<String> prepareCacheNames(Collection<? extends Cache> caches) {
Collection<String> names = new ArrayList<>(caches.size());
for (Cache cache : caches) {
names.add(cache.getName());
}
@@ -885,13 +885,13 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
}
}
/**
* Internal holder class for recording that a cache method was invoked.
*/
private static class InvocationAwareResult {
boolean invoked;
}
}
@@ -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.
@@ -47,7 +47,7 @@ public class NameMatchCacheOperationSource implements CacheOperationSource, Seri
/** Keys are method names; values are TransactionAttributes. */
private Map<String, Collection<CacheOperation>> nameMap = new LinkedHashMap<>();
private final Map<String, Collection<CacheOperation>> nameMap = new LinkedHashMap<>();
/**
@@ -117,8 +117,8 @@ public class NameMatchCacheOperationSource implements CacheOperationSource, Seri
if (!(other instanceof NameMatchCacheOperationSource)) {
return false;
}
NameMatchCacheOperationSource otherTas = (NameMatchCacheOperationSource) other;
return ObjectUtils.nullSafeEquals(this.nameMap, otherTas.nameMap);
NameMatchCacheOperationSource otherCos = (NameMatchCacheOperationSource) other;
return ObjectUtils.nullSafeEquals(this.nameMap, otherCos.nameMap);
}
@Override
@@ -130,4 +130,5 @@ public class NameMatchCacheOperationSource implements CacheOperationSource, Seri
public String toString() {
return getClass().getName() + ": " + this.nameMap;
}
}
@@ -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.
@@ -24,14 +24,16 @@ import org.springframework.cache.Cache;
/**
* Simple cache manager working against a given collection of caches.
* Useful for testing or simple caching declarations.
* <p>
* When using this implementation directly, i.e. not via a regular
*
* <p>When using this implementation directly, i.e. not via a regular
* bean registration, {@link #initializeCaches()} should be invoked
* to initialize its internal state once the
* {@linkplain #setCaches(Collection) caches have been provided}.
*
* @author Costin Leau
* @since 3.1
* @see NoOpCache
* @see org.springframework.cache.concurrent.ConcurrentMapCache
*/
public class SimpleCacheManager extends AbstractCacheManager {
@@ -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.
@@ -65,7 +65,7 @@ public class AnnotationConfigApplicationContext extends GenericApplicationContex
* through {@link #register} calls and then manually {@linkplain #refresh refreshed}.
*/
public AnnotationConfigApplicationContext() {
StartupStep createAnnotatedBeanDefReader = this.getApplicationStartup().start("spring.context.annotated-bean-reader.create");
StartupStep createAnnotatedBeanDefReader = getApplicationStartup().start("spring.context.annotated-bean-reader.create");
this.reader = new AnnotatedBeanDefinitionReader(this);
createAnnotatedBeanDefReader.end();
this.scanner = new ClassPathBeanDefinitionScanner(this);
@@ -163,7 +163,7 @@ public class AnnotationConfigApplicationContext extends GenericApplicationContex
@Override
public void register(Class<?>... componentClasses) {
Assert.notEmpty(componentClasses, "At least one component class must be specified");
StartupStep registerComponentClass = this.getApplicationStartup().start("spring.context.component-classes.register")
StartupStep registerComponentClass = getApplicationStartup().start("spring.context.component-classes.register")
.tag("classes", () -> Arrays.toString(componentClasses));
this.reader.register(componentClasses);
registerComponentClass.end();
@@ -180,7 +180,7 @@ public class AnnotationConfigApplicationContext extends GenericApplicationContex
@Override
public void scan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
StartupStep scanPackages = this.getApplicationStartup().start("spring.context.base-packages.scan")
StartupStep scanPackages = getApplicationStartup().start("spring.context.base-packages.scan")
.tag("packages", () -> Arrays.toString(basePackages));
this.scanner.scan(basePackages);
scanPackages.end();
@@ -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.
@@ -39,6 +39,7 @@ final class BeanMethod extends ConfigurationMethod {
super(metadata, configurationClass);
}
@Override
public void validate(ProblemReporter problemReporter) {
if (getMetadata().isStatic()) {
@@ -55,9 +56,9 @@ final class BeanMethod extends ConfigurationMethod {
}
@Override
public boolean equals(@Nullable Object obj) {
return ((this == obj) || ((obj instanceof BeanMethod) &&
this.metadata.equals(((BeanMethod) obj).metadata)));
public boolean equals(@Nullable Object other) {
return (this == other || (other instanceof BeanMethod &&
this.metadata.equals(((BeanMethod) other).metadata)));
}
@Override
@@ -70,6 +71,7 @@ final class BeanMethod extends ConfigurationMethod {
return "BeanMethod: " + this.metadata;
}
private class NonOverridableMethodError extends Problem {
NonOverridableMethodError() {
@@ -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.
@@ -329,8 +329,8 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo
* @return {@code true} if the bean can be registered as-is;
* {@code false} if it should be skipped because there is an
* existing, compatible bean definition for the specified name
* @throws ConflictingBeanDefinitionException if an existing, incompatible
* bean definition has been found for the specified name
* @throws IllegalStateException if an existing, incompatible bean definition
* has been found for the specified name
*/
protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {
if (!this.registry.containsBeanDefinition(beanName)) {
@@ -354,16 +354,16 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo
* the given existing bean definition.
* <p>The default implementation considers them as compatible when the existing
* bean definition comes from the same source or from a non-scanning source.
* @param newDefinition the new bean definition, originated from scanning
* @param existingDefinition the existing bean definition, potentially an
* @param newDef the new bean definition, originated from scanning
* @param existingDef the existing bean definition, potentially an
* explicitly defined one or a previously generated one from scanning
* @return whether the definitions are considered as compatible, with the
* new definition to be skipped in favor of the existing definition
*/
protected boolean isCompatible(BeanDefinition newDefinition, BeanDefinition existingDefinition) {
return (!(existingDefinition instanceof ScannedGenericBeanDefinition) || // explicitly registered overriding bean
(newDefinition.getSource() != null && newDefinition.getSource().equals(existingDefinition.getSource())) || // scanned same file twice
newDefinition.equals(existingDefinition)); // scanned equivalent class twice
protected boolean isCompatible(BeanDefinition newDef, BeanDefinition existingDef) {
return (!(existingDef instanceof ScannedGenericBeanDefinition) || // explicitly registered overriding bean
(newDef.getSource() != null && newDef.getSource().equals(existingDef.getSource())) || // scanned same file twice
newDef.equals(existingDef)); // scanned equivalent class twice
}
@@ -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.
@@ -104,8 +104,8 @@ import org.springframework.stereotype.Component;
*
* }</pre>
*
* <p>{@code @Configuration} classes may not only be bootstrapped using
* component scanning, but may also themselves <em>configure</em> component scanning using
* <p>{@code @Configuration} classes may not only be bootstrapped using component
* scanning, but may also themselves <em>configure</em> component scanning using
* the {@link ComponentScan @ComponentScan} annotation:
*
* <pre class="code">
@@ -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.
@@ -424,6 +424,7 @@ class ConfigurationClassBeanDefinitionReader {
public ConfigurationClassBeanDefinition(RootBeanDefinition original,
ConfigurationClass configClass, MethodMetadata beanMethodMetadata, String derivedBeanName) {
super(original);
this.annotationMetadata = configClass.getMetadata();
this.factoryMethodMetadata = beanMethodMetadata;
@@ -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.
@@ -409,11 +409,14 @@ class ConfigurationClassParser {
this.metadataReaderFactory.getMetadataReader(original.getClassName()).getAnnotationMetadata();
Set<MethodMetadata> asmMethods = asm.getAnnotatedMethods(Bean.class.getName());
if (asmMethods.size() >= beanMethods.size()) {
Set<MethodMetadata> candidateMethods = new LinkedHashSet<>(beanMethods);
Set<MethodMetadata> selectedMethods = new LinkedHashSet<>(asmMethods.size());
for (MethodMetadata asmMethod : asmMethods) {
for (MethodMetadata beanMethod : beanMethods) {
for (Iterator<MethodMetadata> it = candidateMethods.iterator(); it.hasNext();) {
MethodMetadata beanMethod = it.next();
if (beanMethod.getMethodName().equals(asmMethod.getMethodName())) {
selectedMethods.add(beanMethod);
it.remove();
break;
}
}
@@ -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.
@@ -21,8 +21,8 @@ import org.springframework.instrument.classloading.LoadTimeWeaver;
/**
* Interface to be implemented by
* {@link org.springframework.context.annotation.Configuration @Configuration}
* classes annotated with {@link EnableLoadTimeWeaving @EnableLoadTimeWeaving} that wish to
* customize the {@link LoadTimeWeaver} instance to be used.
* classes annotated with {@link EnableLoadTimeWeaving @EnableLoadTimeWeaving}
* that wish to customize the {@link LoadTimeWeaver} instance to be used.
*
* <p>See {@link org.springframework.scheduling.annotation.EnableAsync @EnableAsync}
* for usage examples and information on how a default {@code LoadTimeWeaver}
@@ -36,9 +36,9 @@ import org.springframework.instrument.classloading.LoadTimeWeaver;
public interface LoadTimeWeavingConfigurer {
/**
* Create, configure and return the {@code LoadTimeWeaver} instance to be used. Note
* that it is unnecessary to annotate this method with {@code @Bean}, because the
* object returned will automatically be registered as a bean by
* Create, configure and return the {@code LoadTimeWeaver} instance to be used.
* Note that it is unnecessary to annotate this method with {@code @Bean}
* because the object returned will automatically be registered as a bean by
* {@link LoadTimeWeavingConfiguration#loadTimeWeaver()}
*/
LoadTimeWeaver getLoadTimeWeaver();
@@ -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.
@@ -274,7 +274,7 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
handleAsyncError(ex);
}
else if (event != null) {
publishEvent(event);
publishEvents(event);
}
});
}
@@ -367,7 +367,7 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
* Return the target bean instance to use.
*/
protected Object getTargetBean() {
Assert.notNull(this.applicationContext, "ApplicationContext must no be null");
Assert.notNull(this.applicationContext, "ApplicationContext must not be null");
return this.applicationContext.getBean(this.beanName);
}
@@ -468,6 +468,9 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
}
/**
* Inner class to avoid a hard dependency on the Reactive Streams API at runtime.
*/
private class ReactiveResultHandler {
public boolean subscribeToPublisher(Object result) {
@@ -481,6 +484,9 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
}
/**
* Reactive Streams Subscriber for publishing follow-up events.
*/
private class EventPublicationSubscriber implements Subscriber<Object> {
@Override
@@ -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.
@@ -79,10 +79,11 @@ public class SimpleApplicationEventMulticaster extends AbstractApplicationEventM
* to invoke each listener with.
* <p>Default is equivalent to {@link org.springframework.core.task.SyncTaskExecutor},
* executing all listeners synchronously in the calling thread.
* <p>Consider specifying an asynchronous task executor here to not block the
* caller until all listeners have been executed. However, note that asynchronous
* execution will not participate in the caller's thread context (class loader,
* transaction association) unless the TaskExecutor explicitly supports this.
* <p>Consider specifying an asynchronous task executor here to not block the caller
* until all listeners have been executed. However, note that asynchronous execution
* will not participate in the caller's thread context (class loader, transaction context)
* unless the TaskExecutor explicitly supports this.
* @since 2.0
* @see org.springframework.core.task.SyncTaskExecutor
* @see org.springframework.core.task.SimpleAsyncTaskExecutor
*/
@@ -92,6 +93,7 @@ public class SimpleApplicationEventMulticaster extends AbstractApplicationEventM
/**
* Return the current task executor for this multicaster.
* @since 2.0
*/
@Nullable
protected Executor getTaskExecutor() {
@@ -137,31 +137,39 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext {
/**
* Name of the MessageSource bean in the factory.
* If none is supplied, message resolution is delegated to the parent.
* @see MessageSource
*/
public static final String MESSAGE_SOURCE_BEAN_NAME = "messageSource";
/**
* Name of the LifecycleProcessor bean in the factory.
* If none is supplied, a DefaultLifecycleProcessor is used.
* The name of the {@link LifecycleProcessor} bean in the context.
* If none is supplied, a {@link DefaultLifecycleProcessor} is used.
* @since 3.0
* @see org.springframework.context.LifecycleProcessor
* @see org.springframework.context.support.DefaultLifecycleProcessor
* @see #start()
* @see #stop()
*/
public static final String LIFECYCLE_PROCESSOR_BEAN_NAME = "lifecycleProcessor";
/**
* Name of the ApplicationEventMulticaster bean in the factory.
* If none is supplied, a default SimpleApplicationEventMulticaster is used.
* The name of the {@link MessageSource} bean in the context.
* If none is supplied, message resolution is delegated to the parent.
* @see org.springframework.context.MessageSource
* @see org.springframework.context.support.ResourceBundleMessageSource
* @see org.springframework.context.support.ReloadableResourceBundleMessageSource
* @see #getMessage(MessageSourceResolvable, Locale)
*/
public static final String MESSAGE_SOURCE_BEAN_NAME = "messageSource";
/**
* The name of the {@link ApplicationEventMulticaster} bean in the context.
* If none is supplied, a {@link SimpleApplicationEventMulticaster} is used.
* @see org.springframework.context.event.ApplicationEventMulticaster
* @see org.springframework.context.event.SimpleApplicationEventMulticaster
* @see #publishEvent(ApplicationEvent)
* @see #addApplicationListener(ApplicationListener)
*/
public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";
/**
* Boolean flag controlled by a {@code spring.spel.ignore} system property that instructs Spring to
* ignore SpEL, i.e. to not initialize the SpEL infrastructure.
* Boolean flag controlled by a {@code spring.spel.ignore} system property that
* instructs Spring to ignore SpEL, i.e. to not initialize the SpEL infrastructure.
* <p>The default is "false".
*/
private static final boolean shouldIgnoreSpel = SpringProperties.getFlag("spring.spel.ignore");
@@ -211,7 +219,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
private Thread shutdownHook;
/** ResourcePatternResolver used by this context. */
private ResourcePatternResolver resourcePatternResolver;
private final ResourcePatternResolver resourcePatternResolver;
/** LifecycleProcessor for managing the lifecycle of beans within this context. */
@Nullable
@@ -447,7 +455,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
@Override
public void setApplicationStartup(ApplicationStartup applicationStartup) {
Assert.notNull(applicationStartup, "applicationStartup should not be null");
Assert.notNull(applicationStartup, "ApplicationStartup must not be null");
this.applicationStartup = applicationStartup;
}
@@ -749,7 +757,8 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
// Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
// (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
if (!NativeDetector.inNativeImage() && beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
if (!NativeDetector.inNativeImage() && beanFactory.getTempClassLoader() == null &&
beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
}
@@ -1082,6 +1091,9 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
// Let subclasses do some final clean-up if they wish...
onClose();
// Reset common introspection caches to avoid class reference leaks.
resetCommonCaches();
// Reset local application listeners to pre-refresh state.
if (this.earlyApplicationListeners != null) {
this.applicationListeners.clear();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 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.IOException;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.BeanDefinitionDocumentReader;
import org.springframework.beans.factory.xml.ResourceEntityResolver;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.ApplicationContext;
@@ -84,7 +85,7 @@ public abstract class AbstractXmlApplicationContext extends AbstractRefreshableC
// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setEnvironment(getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
@@ -95,12 +96,13 @@ public abstract class AbstractXmlApplicationContext extends AbstractRefreshableC
}
/**
* Initialize the bean definition reader used for loading the bean
* definitions of this context. Default implementation is empty.
* Initialize the bean definition reader used for loading the bean definitions
* of this context. The default implementation sets the validating flag.
* <p>Can be overridden in subclasses, e.g. for turning off XML validation
* or using a different XmlBeanDefinitionParser implementation.
* or using a different {@link BeanDefinitionDocumentReader} implementation.
* @param reader the bean definition reader used by this context
* @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader#setDocumentReaderClass
* @see XmlBeanDefinitionReader#setValidating
* @see XmlBeanDefinitionReader#setDocumentReaderClass
*/
protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader) {
reader.setValidating(this.validating);
@@ -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.
@@ -21,6 +21,8 @@ import java.security.ProtectionDomain;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.DecoratingClassLoader;
import org.springframework.core.OverridingClassLoader;
import org.springframework.core.SmartClassLoader;
@@ -45,15 +47,27 @@ class ContextTypeMatchClassLoader extends DecoratingClassLoader implements Smart
}
private static Method findLoadedClassMethod;
@Nullable
private static final Method findLoadedClassMethod;
static {
// Try to enable findLoadedClass optimization which allows us to selectively
// override classes that have not been loaded yet. If not accessible, we will
// always override requested classes, even when the classes have been loaded
// by the parent ClassLoader already and cannot be transformed anymore anyway.
Method method;
try {
findLoadedClassMethod = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class);
method = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class);
ReflectionUtils.makeAccessible(method);
}
catch (NoSuchMethodException ex) {
throw new IllegalStateException("Invalid [java.lang.ClassLoader] class: no 'findLoadedClass' method defined!");
catch (Throwable ex) {
// Typically a JDK 9+ InaccessibleObjectException...
// Avoid through JVM startup with --add-opens=java.base/java.lang=ALL-UNNAMED
method = null;
LogFactory.getLog(ContextTypeMatchClassLoader.class).debug(
"ClassLoader.findLoadedClass not accessible -> will always override requested class", ex);
}
findLoadedClassMethod = method;
}
@@ -96,13 +110,14 @@ class ContextTypeMatchClassLoader extends DecoratingClassLoader implements Smart
if (isExcluded(className) || ContextTypeMatchClassLoader.this.isExcluded(className)) {
return false;
}
ReflectionUtils.makeAccessible(findLoadedClassMethod);
ClassLoader parent = getParent();
while (parent != null) {
if (ReflectionUtils.invokeMethod(findLoadedClassMethod, parent, className) != null) {
return false;
if (findLoadedClassMethod != null) {
ClassLoader parent = getParent();
while (parent != null) {
if (ReflectionUtils.invokeMethod(findLoadedClassMethod, parent, className) != null) {
return false;
}
parent = parent.getParent();
}
parent = parent.getParent();
}
return true;
}
@@ -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.
@@ -64,9 +64,10 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor
/**
* Specify the maximum time allotted in milliseconds for the shutdown of
* any phase (group of SmartLifecycle beans with the same 'phase' value).
* <p>The default value is 30 seconds.
* Specify the maximum time allotted in milliseconds for the shutdown of any
* phase (group of {@link SmartLifecycle} beans with the same 'phase' value).
* <p>The default value is 30000 milliseconds (30 seconds).
* @see SmartLifecycle#getPhase()
*/
public void setTimeoutPerShutdownPhase(long timeoutPerShutdownPhase) {
this.timeoutPerShutdownPhase = timeoutPerShutdownPhase;
@@ -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.
@@ -28,8 +28,8 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar;
/**
* Annotation that marks a method to be scheduled. Exactly one of the
* {@link #cron}, {@link #fixedDelay}, or {@link #fixedRate} attributes must be
* specified.
* {@link #cron}, {@link #fixedDelay}, or {@link #fixedRate} attributes
* must be specified.
*
* <p>The annotated method must expect no arguments. It will typically have
* a {@code void} return type; if not, the returned value will be ignored
@@ -40,6 +40,12 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar;
* done manually or, more conveniently, through the {@code <task:annotation-driven/>}
* XML element or {@link EnableScheduling @EnableScheduling} annotation.
*
* <p>This annotation can be used as a <em>{@linkplain Repeatable repeatable}</em>
* annotation. If several scheduled declarations are found on the same method,
* each of them will be processed independently, with a separate trigger firing
* for each of them. As a consequence, such co-located schedules may overlap
* and execute multiple times in parallel or in immediate succession.
*
* <p>This annotation may be used as a <em>meta-annotation</em> to create custom
* <em>composed annotations</em> with attribute overrides.
*
@@ -117,9 +123,12 @@ public @interface Scheduled {
* last invocation and the start of the next.
* <p>The time unit is milliseconds by default but can be overridden via
* {@link #timeUnit}.
* <p>This attribute variant supports Spring-style "${...}" placeholders
* as well as SpEL expressions.
* @return the delay as a String value &mdash; for example, a placeholder
* or a {@link java.time.Duration#parse java.time.Duration} compliant value
* @since 3.2.2
* @see #fixedDelay()
*/
String fixedDelayString() default "";
@@ -135,9 +144,12 @@ public @interface Scheduled {
* Execute the annotated method with a fixed period between invocations.
* <p>The time unit is milliseconds by default but can be overridden via
* {@link #timeUnit}.
* <p>This attribute variant supports Spring-style "${...}" placeholders
* as well as SpEL expressions.
* @return the period as a String value &mdash; for example, a placeholder
* or a {@link java.time.Duration#parse java.time.Duration} compliant value
* @since 3.2.2
* @see #fixedRate()
*/
String fixedRateString() default "";
@@ -156,9 +168,12 @@ public @interface Scheduled {
* {@link #fixedRate} or {@link #fixedDelay} task.
* <p>The time unit is milliseconds by default but can be overridden via
* {@link #timeUnit}.
* <p>This attribute variant supports Spring-style "${...}" placeholders
* as well as SpEL expressions.
* @return the initial delay as a String value &mdash; for example, a placeholder
* or a {@link java.time.Duration#parse java.time.Duration} compliant value
* @since 3.2.2
* @see #initialDelay()
*/
String initialDelayString() default "";
@@ -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.
@@ -160,7 +160,7 @@ public class ScheduledAnnotationBeanPostProcessor
* @since 5.1
*/
public ScheduledAnnotationBeanPostProcessor(ScheduledTaskRegistrar registrar) {
Assert.notNull(registrar, "ScheduledTaskRegistrar is required");
Assert.notNull(registrar, "ScheduledTaskRegistrar must not be null");
this.registrar = registrar;
}
@@ -580,7 +580,7 @@ public class ScheduledAnnotationBeanPostProcessor
}
if (tasks != null) {
for (ScheduledTask task : tasks) {
task.cancel();
task.cancel(false);
}
}
}
@@ -598,7 +598,7 @@ public class ScheduledAnnotationBeanPostProcessor
Collection<Set<ScheduledTask>> allTasks = this.scheduledTasks.values();
for (Set<ScheduledTask> tasks : allTasks) {
for (ScheduledTask task : tasks) {
task.cancel();
task.cancel(false);
}
}
this.scheduledTasks.clear();
@@ -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.
@@ -40,10 +40,10 @@ import org.springframework.scheduling.config.ScheduledTaskRegistrar;
public interface SchedulingConfigurer {
/**
* Callback allowing a {@link org.springframework.scheduling.TaskScheduler
* TaskScheduler} and specific {@link org.springframework.scheduling.config.Task Task}
* instances to be registered against the given the {@link ScheduledTaskRegistrar}.
* @param taskRegistrar the registrar to be configured.
* Callback allowing a {@link org.springframework.scheduling.TaskScheduler}
* and specific {@link org.springframework.scheduling.config.Task} instances
* to be registered against the given the {@link ScheduledTaskRegistrar}.
* @param taskRegistrar the registrar to be configured
*/
void configureTasks(ScheduledTaskRegistrar taskRegistrar);
@@ -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.
@@ -83,6 +83,9 @@ public class ConcurrentTaskExecutor implements AsyncListenableTaskExecutor, Sche
private TaskExecutorAdapter adaptedExecutor;
@Nullable
private TaskDecorator taskDecorator;
/**
* Create a new ConcurrentTaskExecutor, using a single thread executor as default.
@@ -138,6 +141,7 @@ public class ConcurrentTaskExecutor implements AsyncListenableTaskExecutor, Sche
* @since 4.3
*/
public final void setTaskDecorator(TaskDecorator taskDecorator) {
this.taskDecorator = taskDecorator;
this.adaptedExecutor.setTaskDecorator(taskDecorator);
}
@@ -174,11 +178,15 @@ public class ConcurrentTaskExecutor implements AsyncListenableTaskExecutor, Sche
}
private static TaskExecutorAdapter getAdaptedExecutor(Executor concurrentExecutor) {
private TaskExecutorAdapter getAdaptedExecutor(Executor concurrentExecutor) {
if (managedExecutorServiceClass != null && managedExecutorServiceClass.isInstance(concurrentExecutor)) {
return new ManagedTaskExecutorAdapter(concurrentExecutor);
}
return new TaskExecutorAdapter(concurrentExecutor);
TaskExecutorAdapter adapter = new TaskExecutorAdapter(concurrentExecutor);
if (this.taskDecorator != null) {
adapter.setTaskDecorator(this.taskDecorator);
}
return adapter;
}
@@ -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.
@@ -100,7 +100,7 @@ public class ConcurrentTaskScheduler extends ConcurrentTaskExecutor implements T
*/
public ConcurrentTaskScheduler() {
super();
this.scheduledExecutor = initScheduledExecutor(null);
initScheduledExecutor(null);
}
/**
@@ -115,7 +115,7 @@ public class ConcurrentTaskScheduler extends ConcurrentTaskExecutor implements T
*/
public ConcurrentTaskScheduler(ScheduledExecutorService scheduledExecutor) {
super(scheduledExecutor);
this.scheduledExecutor = initScheduledExecutor(scheduledExecutor);
initScheduledExecutor(scheduledExecutor);
}
/**
@@ -131,11 +131,11 @@ public class ConcurrentTaskScheduler extends ConcurrentTaskExecutor implements T
*/
public ConcurrentTaskScheduler(Executor concurrentExecutor, ScheduledExecutorService scheduledExecutor) {
super(concurrentExecutor);
this.scheduledExecutor = initScheduledExecutor(scheduledExecutor);
initScheduledExecutor(scheduledExecutor);
}
private ScheduledExecutorService initScheduledExecutor(@Nullable ScheduledExecutorService scheduledExecutor) {
private void initScheduledExecutor(@Nullable ScheduledExecutorService scheduledExecutor) {
if (scheduledExecutor != null) {
this.scheduledExecutor = scheduledExecutor;
this.enterpriseConcurrentScheduler = (managedScheduledExecutorServiceClass != null &&
@@ -145,7 +145,6 @@ public class ConcurrentTaskScheduler extends ConcurrentTaskExecutor implements T
this.scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
this.enterpriseConcurrentScheduler = false;
}
return this.scheduledExecutor;
}
/**
@@ -207,9 +206,9 @@ public class ConcurrentTaskScheduler extends ConcurrentTaskExecutor implements T
@Override
public ScheduledFuture<?> schedule(Runnable task, Date startTime) {
long initialDelay = startTime.getTime() - this.clock.millis();
long delay = startTime.getTime() - this.clock.millis();
try {
return this.scheduledExecutor.schedule(decorateTask(task, false), initialDelay, TimeUnit.MILLISECONDS);
return this.scheduledExecutor.schedule(decorateTask(task, false), delay, TimeUnit.MILLISECONDS);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, 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.
@@ -36,7 +36,10 @@ import org.springframework.lang.Nullable;
* Base class for setting up a {@link java.util.concurrent.ExecutorService}
* (typically a {@link java.util.concurrent.ThreadPoolExecutor} or
* {@link java.util.concurrent.ScheduledThreadPoolExecutor}).
* Defines common configuration settings and common lifecycle handling.
*
* <p>Defines common configuration settings and common lifecycle handling,
* inheriting thread customization options (name, priority, etc) from
* {@link org.springframework.util.CustomizableThreadCreator}.
*
* @author Juergen Hoeller
* @since 3.0
@@ -105,9 +108,9 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
/**
* Set whether to wait for scheduled tasks to complete on shutdown,
* not interrupting running tasks and executing all tasks in the queue.
* <p>Default is "false", shutting down immediately through interrupting
* ongoing tasks and clearing the queue. Switch this flag to "true" if you
* prefer fully completed tasks at the expense of a longer shutdown phase.
* <p>Default is {@code false}, shutting down immediately through interrupting
* ongoing tasks and clearing the queue. Switch this flag to {@code true} if
* you prefer fully completed tasks at the expense of a longer shutdown phase.
* <p>Note that Spring's container shutdown continues while ongoing tasks
* are being completed. If you want this executor to block and wait for the
* termination of tasks before the rest of the container continues to shut
@@ -199,8 +202,7 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
/**
* Calls {@code shutdown} when the BeanFactory destroys
* the task executor instance.
* Calls {@code shutdown} when the BeanFactory destroys the executor instance.
* @see #shutdown()
*/
@Override
@@ -209,9 +211,13 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
}
/**
* Perform a shutdown on the underlying ExecutorService.
* Perform a full shutdown on the underlying ExecutorService,
* according to the corresponding configuration settings.
* @see #setWaitForTasksToCompleteOnShutdown
* @see #setAwaitTerminationMillis
* @see java.util.concurrent.ExecutorService#shutdown()
* @see java.util.concurrent.ExecutorService#shutdownNow()
* @see java.util.concurrent.ExecutorService#awaitTermination
*/
public void shutdown() {
if (logger.isDebugEnabled()) {
@@ -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.
@@ -78,8 +78,8 @@ class ReschedulingRunnable extends DelegatingErrorHandlingRunnable implements Sc
if (this.scheduledExecutionTime == null) {
return null;
}
long initialDelay = this.scheduledExecutionTime.getTime() - this.triggerContext.getClock().millis();
this.currentFuture = this.executor.schedule(this, initialDelay, TimeUnit.MILLISECONDS);
long delay = this.scheduledExecutionTime.getTime() - this.triggerContext.getClock().millis();
this.currentFuture = this.executor.schedule(this, delay, TimeUnit.MILLISECONDS);
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.
@@ -380,9 +380,9 @@ public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport
@Override
public ScheduledFuture<?> schedule(Runnable task, Date startTime) {
ScheduledExecutorService executor = getScheduledExecutor();
long initialDelay = startTime.getTime() - this.clock.millis();
long delay = startTime.getTime() - this.clock.millis();
try {
return executor.schedule(errorHandlingTask(task, false), initialDelay, TimeUnit.MILLISECONDS);
return executor.schedule(errorHandlingTask(task, false), delay, TimeUnit.MILLISECONDS);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
@@ -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.
@@ -41,8 +41,8 @@ import org.springframework.util.CollectionUtils;
* Helper bean for registering tasks with a {@link TaskScheduler}, typically using cron
* expressions.
*
* <p>As of Spring 3.1, {@code ScheduledTaskRegistrar} has a more prominent user-facing
* role when used in conjunction with the {@link
* <p>{@code ScheduledTaskRegistrar} has a more prominent user-facing role when used in
* conjunction with the {@link
* org.springframework.scheduling.annotation.EnableAsync @EnableAsync} annotation and its
* {@link org.springframework.scheduling.annotation.SchedulingConfigurer
* SchedulingConfigurer} callback interface.
@@ -552,7 +552,7 @@ public class ScheduledTaskRegistrar implements ScheduledTaskHolder, Initializing
@Override
public void destroy() {
for (ScheduledTask task : this.scheduledTasks) {
task.cancel();
task.cancel(false);
}
if (this.localExecutor != null) {
this.localExecutor.shutdownNow();
@@ -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.
@@ -102,8 +102,8 @@ public abstract class AbstractBindingResult extends AbstractErrors implements Bi
}
@Override
public void rejectValue(@Nullable String field, String errorCode, @Nullable Object[] errorArgs,
@Nullable String defaultMessage) {
public void rejectValue(@Nullable String field, String errorCode,
@Nullable Object[] errorArgs, @Nullable String defaultMessage) {
if (!StringUtils.hasLength(getNestedPath()) && !StringUtils.hasLength(field)) {
// We're at the top of the nested object hierarchy,
@@ -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.
@@ -28,13 +28,14 @@ import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
/**
* Abstract implementation of the {@link Errors} interface. Provides common
* access to evaluated errors; however, does not define concrete management
* Abstract implementation of the {@link Errors} interface.
* Provides nested path handling but does not define concrete management
* of {@link ObjectError ObjectErrors} and {@link FieldError FieldErrors}.
*
* @author Juergen Hoeller
* @author Rossen Stoyanchev
* @since 2.5.3
* @see AbstractBindingResult
*/
@SuppressWarnings("serial")
public abstract class AbstractErrors implements Errors, Serializable {
@@ -81,8 +82,8 @@ public abstract class AbstractErrors implements Errors, Serializable {
nestedPath = "";
}
nestedPath = canonicalFieldName(nestedPath);
if (nestedPath.length() > 0 && !nestedPath.endsWith(Errors.NESTED_PATH_SEPARATOR)) {
nestedPath += Errors.NESTED_PATH_SEPARATOR;
if (nestedPath.length() > 0 && !nestedPath.endsWith(NESTED_PATH_SEPARATOR)) {
nestedPath += NESTED_PATH_SEPARATOR;
}
this.nestedPath = nestedPath;
}
@@ -97,7 +98,7 @@ public abstract class AbstractErrors implements Errors, Serializable {
}
else {
String path = getNestedPath();
return (path.endsWith(Errors.NESTED_PATH_SEPARATOR) ?
return (path.endsWith(NESTED_PATH_SEPARATOR) ?
path.substring(0, path.length() - NESTED_PATH_SEPARATOR.length()) : path);
}
}
@@ -201,9 +202,9 @@ public abstract class AbstractErrors implements Errors, Serializable {
List<FieldError> fieldErrors = getFieldErrors();
List<FieldError> result = new ArrayList<>();
String fixedField = fixedField(field);
for (FieldError error : fieldErrors) {
if (isMatchingFieldError(fixedField, error)) {
result.add(error);
for (FieldError fieldError : fieldErrors) {
if (isMatchingFieldError(fixedField, fieldError)) {
result.add(fieldError);
}
}
return Collections.unmodifiableList(result);
@@ -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.
@@ -55,7 +55,7 @@ public class BeanPropertyBindingResult extends AbstractPropertyBindingResult imp
/**
* Creates a new instance of the {@link BeanPropertyBindingResult} class.
* Create a new {@code BeanPropertyBindingResult} for the given target.
* @param target the target bean to bind onto
* @param objectName the name of the target object
*/
@@ -64,7 +64,7 @@ public class BeanPropertyBindingResult extends AbstractPropertyBindingResult imp
}
/**
* Creates a new instance of the {@link BeanPropertyBindingResult} class.
* Create a new {@code BeanPropertyBindingResult} for the given target.
* @param target the target bean to bind onto
* @param objectName the name of the target object
* @param autoGrowNestedPaths whether to "auto-grow" a nested path that contains a null value
@@ -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.
@@ -128,7 +128,9 @@ public class BindException extends Exception implements BindingResult {
}
@Override
public void rejectValue(@Nullable String field, String errorCode, @Nullable Object[] errorArgs, @Nullable String defaultMessage) {
public void rejectValue(@Nullable String field, String errorCode,
@Nullable Object[] errorArgs, @Nullable String defaultMessage) {
this.bindingResult.rejectValue(field, errorCode, errorArgs, defaultMessage);
}
@@ -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.
@@ -46,7 +46,7 @@ public class DirectFieldBindingResult extends AbstractPropertyBindingResult {
/**
* Create a new DirectFieldBindingResult instance.
* Create a new {@code DirectFieldBindingResult} for the given target.
* @param target the target object to bind onto
* @param objectName the name of the target object
*/
@@ -55,7 +55,7 @@ public class DirectFieldBindingResult extends AbstractPropertyBindingResult {
}
/**
* Create a new DirectFieldBindingResult instance.
* Create a new {@code DirectFieldBindingResult} for the given target.
* @param target the target object to bind onto
* @param objectName the name of the target object
* @param autoGrowNestedPaths whether to "auto-grow" a nested path that contains a null value
@@ -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,24 +22,24 @@ import org.springframework.beans.PropertyAccessor;
import org.springframework.lang.Nullable;
/**
* Stores and exposes information about data-binding and validation
* errors for a specific object.
* Stores and exposes information about data-binding and validation errors
* for a specific object.
*
* <p>Field names can be properties of the target object (e.g. "name"
* when binding to a customer object), or nested fields in case of
* subobjects (e.g. "address.street"). Supports subtree navigation
* via {@link #setNestedPath(String)}: for example, an
* {@code AddressValidator} validates "address", not being aware
* that this is a subobject of customer.
* <p>Field names are typically properties of the target object (e.g. "name"
* when binding to a customer object). Implementations may also support nested
* fields in case of nested objects (e.g. "address.street"), in conjunction
* with subtree navigation via {@link #setNestedPath}: for example, an
* {@code AddressValidator} may validate "address", not being aware that this
* is a nested object of a top-level customer object.
*
* <p>Note: {@code Errors} objects are single-threaded.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @see #setNestedPath
* @see BindException
* @see DataBinder
* @see Validator
* @see ValidationUtils
* @see BindException
* @see BindingResult
*/
public interface Errors {
@@ -66,6 +66,7 @@ public interface Errors {
* @param nestedPath nested path within this object,
* e.g. "address" (defaults to "", {@code null} is also acceptable).
* Can end with a dot: both "address" and "address." are valid.
* @see #getNestedPath()
*/
void setNestedPath(String nestedPath);
@@ -73,6 +74,7 @@ public interface Errors {
* Return the current nested path of this {@link Errors} object.
* <p>Returns a nested path with a dot, i.e. "address.", for easy
* building of concatenated paths. Default is an empty String.
* @see #setNestedPath(String)
*/
String getNestedPath();
@@ -86,14 +88,14 @@ public interface Errors {
* <p>For example: current path "spouse.", pushNestedPath("child") &rarr;
* result path "spouse.child."; popNestedPath() &rarr; "spouse." again.
* @param subPath the sub path to push onto the nested path stack
* @see #popNestedPath
* @see #popNestedPath()
*/
void pushNestedPath(String subPath);
/**
* Pop the former nested path from the nested path stack.
* @throws IllegalStateException if there is no former nested path on the stack
* @see #pushNestedPath
* @see #pushNestedPath(String)
*/
void popNestedPath() throws IllegalStateException;
@@ -101,6 +103,7 @@ public interface Errors {
* Register a global error for the entire target object,
* using the given error description.
* @param errorCode error code, interpretable as a message key
* @see #reject(String, Object[], String)
*/
void reject(String errorCode);
@@ -109,6 +112,7 @@ public interface Errors {
* using the given error description.
* @param errorCode error code, interpretable as a message key
* @param defaultMessage fallback default message
* @see #reject(String, Object[], String)
*/
void reject(String errorCode, String defaultMessage);
@@ -119,6 +123,7 @@ public interface Errors {
* @param errorArgs error arguments, for argument binding via MessageFormat
* (can be {@code null})
* @param defaultMessage fallback default message
* @see #rejectValue(String, String, Object[], String)
*/
void reject(String errorCode, @Nullable Object[] errorArgs, @Nullable String defaultMessage);
@@ -132,7 +137,7 @@ public interface Errors {
* global error if the current object is the top object.
* @param field the field name (may be {@code null} or empty String)
* @param errorCode error code, interpretable as a message key
* @see #getNestedPath()
* @see #rejectValue(String, String, Object[], String)
*/
void rejectValue(@Nullable String field, String errorCode);
@@ -147,7 +152,7 @@ public interface Errors {
* @param field the field name (may be {@code null} or empty String)
* @param errorCode error code, interpretable as a message key
* @param defaultMessage fallback default message
* @see #getNestedPath()
* @see #rejectValue(String, String, Object[], String)
*/
void rejectValue(@Nullable String field, String errorCode, String defaultMessage);
@@ -164,7 +169,7 @@ public interface Errors {
* @param errorArgs error arguments, for argument binding via MessageFormat
* (can be {@code null})
* @param defaultMessage fallback default message
* @see #getNestedPath()
* @see #reject(String, Object[], String)
*/
void rejectValue(@Nullable String field, String errorCode,
@Nullable Object[] errorArgs, @Nullable String defaultMessage);
@@ -179,35 +184,40 @@ public interface Errors {
* to refer to the same target object, or at least contain compatible errors
* that apply to the target object of this {@code Errors} instance.
* @param errors the {@code Errors} instance to merge in
* @see #getAllErrors()
*/
void addAllErrors(Errors errors);
/**
* Return if there were any errors.
* Determine if there were any errors.
* @see #hasGlobalErrors()
* @see #hasFieldErrors()
*/
boolean hasErrors();
/**
* Return the total number of errors.
* Determine the total number of errors.
* @see #getGlobalErrorCount()
* @see #getFieldErrorCount()
*/
int getErrorCount();
/**
* Get all errors, both global and field ones.
* @return a list of {@link ObjectError} instances
* @return a list of {@link ObjectError}/{@link FieldError} instances
* @see #getGlobalErrors()
* @see #getFieldErrors()
*/
List<ObjectError> getAllErrors();
/**
* Are there any global errors?
* @return {@code true} if there are any global errors
* Determine if there were any global errors.
* @see #hasFieldErrors()
*/
boolean hasGlobalErrors();
/**
* Return the number of global errors.
* @return the number of global errors
* Determine the number of global errors.
* @see #getFieldErrorCount()
*/
int getGlobalErrorCount();
@@ -215,26 +225,26 @@ public interface Errors {
/**
* Get all global errors.
* @return a list of {@link ObjectError} instances
* @see #getFieldErrors()
*/
List<ObjectError> getGlobalErrors();
/**
* Get the <i>first</i> global error, if any.
* @return the global error, or {@code null}
* @see #getFieldError()
*/
@Nullable
ObjectError getGlobalError();
/**
* Are there any field errors?
* @return {@code true} if there are any errors associated with a field
* Determine if there were any errors associated with a field.
* @see #hasGlobalErrors()
*/
boolean hasFieldErrors();
/**
* Return the number of errors associated with a field.
* @return the number of errors associated with a field
* Determine the number of errors associated with a field.
* @see #getGlobalErrorCount()
*/
int getFieldErrorCount();
@@ -242,36 +252,39 @@ public interface Errors {
/**
* Get all errors associated with a field.
* @return a List of {@link FieldError} instances
* @see #getGlobalErrors()
*/
List<FieldError> getFieldErrors();
/**
* Get the <i>first</i> error associated with a field, if any.
* @return the field-specific error, or {@code null}
* @see #getGlobalError()
*/
@Nullable
FieldError getFieldError();
/**
* Are there any errors associated with the given field?
* Determine if there were any errors associated with the given field.
* @param field the field name
* @return {@code true} if there were any errors associated with the given field
* @see #hasFieldErrors()
*/
boolean hasFieldErrors(String field);
/**
* Return the number of errors associated with the given field.
* Determine the number of errors associated with the given field.
* @param field the field name
* @return the number of errors associated with the given field
* @see #getFieldErrorCount()
*/
int getFieldErrorCount(String field);
/**
* Get all errors associated with the given field.
* <p>Implementations should support not only full field names like
* "name" but also pattern matches like "na*" or "address.*".
* <p>Implementations may support not only full field names like
* "address.street" but also pattern matches like "address.*".
* @param field the field name
* @return a List of {@link FieldError} instances
* @see #getFieldErrors()
*/
List<FieldError> getFieldErrors(String field);
@@ -279,6 +292,7 @@ public interface Errors {
* Get the first error associated with the given field, if any.
* @param field the field name
* @return the field-specific error, or {@code null}
* @see #getFieldError()
*/
@Nullable
FieldError getFieldError(String field);
@@ -290,17 +304,19 @@ public interface Errors {
* even if there were type mismatches.
* @param field the field name
* @return the current value of the given field
* @see #getFieldType(String)
*/
@Nullable
Object getFieldValue(String field);
/**
* Return the type of a given field.
* Determine the type of the given field, as far as possible.
* <p>Implementations should be able to determine the type even
* when the field value is {@code null}, for example from some
* associated descriptor.
* @param field the field name
* @return the type of the field, or {@code null} if not determinable
* @see #getFieldValue(String)
*/
@Nullable
Class<?> getFieldType(String field);
@@ -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.
@@ -124,6 +124,8 @@ public class FieldError extends ObjectError {
@Override
public String toString() {
// We would preferably use ObjectUtils.nullSafeConciseToString(rejectedValue) here but
// keep including the full nullSafeToString representation for backwards compatibility.
return "Field error in object '" + getObjectName() + "' on field '" + this.field +
"': rejected value [" + ObjectUtils.nullSafeToString(this.rejectedValue) + "]; " +
resolvableToString();
@@ -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.
@@ -54,14 +54,14 @@ package org.springframework.validation;
* }
* }</pre>
*
* <p>See also the Spring reference manual for a fuller discussion of
* the {@code Validator} interface and its role in an enterprise
* application.
* <p>See also the Spring reference manual for a fuller discussion of the
* {@code Validator} interface and its role in an enterprise application.
*
* @author Rod Johnson
* @see SmartValidator
* @see Errors
* @see ValidationUtils
* @see DataBinder#setValidator
*/
public interface Validator {
@@ -81,11 +81,14 @@ public interface Validator {
boolean supports(Class<?> clazz);
/**
* Validate the supplied {@code target} object, which must be
* of a {@link Class} for which the {@link #supports(Class)} method
* typically has (or would) return {@code true}.
* Validate the given {@code target} object which must be of a
* {@link Class} for which the {@link #supports(Class)} method
* typically has returned (or would return) {@code true}.
* <p>The supplied {@link Errors errors} instance can be used to report
* any resulting validation errors.
* any resulting validation errors, typically as part of a larger
* binding process which this validator is meant to participate in.
* Binding errors have typically been pre-registered with the
* {@link Errors errors} instance before this invocation already.
* @param target the object that is to be validated
* @param errors contextual state about the validation process
* @see ValidationUtils
@@ -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.
@@ -26,37 +26,46 @@ import org.springframework.lang.Nullable;
* Mainly for internal use within the framework.
*
* @author Christoph Dreis
* @author Juergen Hoeller
* @since 5.3.7
*/
public abstract class ValidationAnnotationUtils {
private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
/**
* Determine any validation hints by the given annotation.
* <p>This implementation checks for {@code @javax.validation.Valid},
* Spring's {@link org.springframework.validation.annotation.Validated},
* and custom annotations whose name starts with "Valid".
* <p>This implementation checks for Spring's
* {@link org.springframework.validation.annotation.Validated},
* {@code @javax.validation.Valid}, and custom annotations whose
* name starts with "Valid" which may optionally declare validation
* hints through the "value" attribute.
* @param ann the annotation (potentially a validation annotation)
* @return the validation hints to apply (possibly an empty array),
* or {@code null} if this annotation does not trigger any validation
*/
@Nullable
public static Object[] determineValidationHints(Annotation ann) {
// Direct presence of @Validated ?
if (ann instanceof Validated) {
return ((Validated) ann).value();
}
// Direct presence of @Valid ?
Class<? extends Annotation> annotationType = ann.annotationType();
String annotationName = annotationType.getName();
if ("javax.validation.Valid".equals(annotationName)) {
if ("javax.validation.Valid".equals(annotationType.getName())) {
return EMPTY_OBJECT_ARRAY;
}
// Meta presence of @Validated ?
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
if (validatedAnn != null) {
Object hints = validatedAnn.value();
return convertValidationHints(hints);
return validatedAnn.value();
}
// Custom validation annotation ?
if (annotationType.getSimpleName().startsWith("Valid")) {
Object hints = AnnotationUtils.getValue(ann);
return convertValidationHints(hints);
return convertValidationHints(AnnotationUtils.getValue(ann));
}
// No validation triggered
return null;
}
@@ -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.
@@ -108,7 +108,7 @@ open class BeanDefinitionDsl internal constructor (private val init: BeanDefinit
SINGLETON,
/**
* Scope constant for the standard singleton scope
* Scope constant for the standard prototype scope
* @see org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE
*/
PROTOTYPE
@@ -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.
@@ -28,6 +28,7 @@ import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.AopUtils;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
import org.springframework.aop.testfixture.advice.CountingBeforeAdvice;
import org.springframework.aop.testfixture.interceptor.NopInterceptor;
import org.springframework.beans.testfixture.beans.ITestBean;
@@ -35,6 +36,8 @@ import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -87,8 +90,7 @@ public class CglibProxyTests extends AbstractAopProxyTests implements Serializab
AdvisedSupport pc = new AdvisedSupport(ITestBean.class);
pc.addAdvice(new NopInterceptor());
AopProxy aop = createAopProxy(pc);
assertThatExceptionOfType(AopConfigException.class).isThrownBy(
aop::getProxy);
assertThatExceptionOfType(AopConfigException.class).isThrownBy(aop::getProxy);
}
@Test
@@ -136,8 +138,8 @@ public class CglibProxyTests extends AbstractAopProxyTests implements Serializab
Object proxy = aop.getProxy();
assertThat(AopUtils.isCglibProxy(proxy)).isTrue();
assertThat(proxy instanceof ITestBean).isTrue();
assertThat(proxy instanceof TestBean).isTrue();
assertThat(proxy).isInstanceOf(ITestBean.class);
assertThat(proxy).isInstanceOf(TestBean.class);
TestBean tb = (TestBean) proxy;
assertThat(tb.getAge()).isEqualTo(32);
@@ -304,6 +306,8 @@ public class CglibProxyTests extends AbstractAopProxyTests implements Serializab
CglibAopProxy cglib = new CglibAopProxy(as);
ITestBean proxy1 = (ITestBean) cglib.getProxy();
ITestBean proxy1a = (ITestBean) cglib.getProxy();
assertThat(proxy1a.getClass()).isSameAs(proxy1.getClass());
mockTargetSource.setTarget(proxy1);
as = new AdvisedSupport(new Class<?>[]{});
@@ -312,7 +316,40 @@ public class CglibProxyTests extends AbstractAopProxyTests implements Serializab
cglib = new CglibAopProxy(as);
ITestBean proxy2 = (ITestBean) cglib.getProxy();
assertThat(proxy2 instanceof Serializable).isTrue();
assertThat(proxy2).isInstanceOf(Serializable.class);
assertThat(proxy2.getClass()).isNotSameAs(proxy1.getClass());
ITestBean proxy2a = (ITestBean) cglib.getProxy();
assertThat(proxy2a).isInstanceOf(Serializable.class);
assertThat(proxy2a.getClass()).isSameAs(proxy2.getClass());
mockTargetSource.setTarget(proxy1);
as = new AdvisedSupport(new Class<?>[]{});
as.setTargetSource(mockTargetSource);
as.addAdvisor(new DefaultPointcutAdvisor(new AnnotationMatchingPointcut(Nullable.class), new NopInterceptor()));
cglib = new CglibAopProxy(as);
ITestBean proxy3 = (ITestBean) cglib.getProxy();
assertThat(proxy3).isInstanceOf(Serializable.class);
assertThat(proxy3.getClass()).isNotSameAs(proxy2.getClass());
ITestBean proxy3a = (ITestBean) cglib.getProxy();
assertThat(proxy3a).isInstanceOf(Serializable.class);
assertThat(proxy3a.getClass()).isSameAs(proxy3.getClass());
mockTargetSource.setTarget(proxy1);
as = new AdvisedSupport(new Class<?>[]{});
as.setTargetSource(mockTargetSource);
as.addAdvisor(new DefaultPointcutAdvisor(new AnnotationMatchingPointcut(NonNull.class), new NopInterceptor()));
cglib = new CglibAopProxy(as);
ITestBean proxy4 = (ITestBean) cglib.getProxy();
assertThat(proxy4).isInstanceOf(Serializable.class);
assertThat(proxy4.getClass()).isNotSameAs(proxy3.getClass());
ITestBean proxy4a = (ITestBean) cglib.getProxy();
assertThat(proxy4a).isInstanceOf(Serializable.class);
assertThat(proxy4a.getClass()).isSameAs(proxy4.getClass());
}
@Test
@@ -331,7 +368,7 @@ public class CglibProxyTests extends AbstractAopProxyTests implements Serializab
proxy.doTest();
}
catch (Exception ex) {
assertThat(ex instanceof ApplicationContextException).as("Invalid exception class").isTrue();
assertThat(ex).as("Invalid exception class").isInstanceOf(ApplicationContextException.class);
}
assertThat(proxy.isCatchInvoked()).as("Catch was not invoked").isTrue();
@@ -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.
@@ -92,7 +92,7 @@ public class ProxyFactoryBeanTests {
@BeforeEach
public void setUp() throws Exception {
public void setup() throws Exception {
DefaultListableBeanFactory parent = new DefaultListableBeanFactory();
parent.registerBeanDefinition("target2", new RootBeanDefinition(TestApplicationListener.class));
this.factory = new DefaultListableBeanFactory(parent);
@@ -139,22 +139,24 @@ public class ProxyFactoryBeanTests {
private void testDoubleTargetSourceIsRejected(String name) {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(new ClassPathResource(DBL_TARGETSOURCE_CONTEXT, CLASS));
assertThatExceptionOfType(BeanCreationException.class).as("Should not allow TargetSource to be specified in interceptorNames as well as targetSource property")
.isThrownBy(() -> bf.getBean(name))
.havingCause()
.isInstanceOf(AopConfigException.class)
.withMessageContaining("TargetSource");
.isThrownBy(() -> bf.getBean(name))
.havingCause()
.isInstanceOf(AopConfigException.class)
.withMessageContaining("TargetSource");
}
@Test
public void testTargetSourceNotAtEndOfInterceptorNamesIsRejected() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(new ClassPathResource(NOTLAST_TARGETSOURCE_CONTEXT, CLASS));
assertThatExceptionOfType(BeanCreationException.class).as("TargetSource or non-advised object must be last in interceptorNames")
.isThrownBy(() -> bf.getBean("targetSourceNotLast"))
.havingCause()
.isInstanceOf(AopConfigException.class)
.withMessageContaining("interceptorNames");
.isThrownBy(() -> bf.getBean("targetSourceNotLast"))
.havingCause()
.isInstanceOf(AopConfigException.class)
.withMessageContaining("interceptorNames");
}
@Test
@@ -167,11 +169,11 @@ public class ProxyFactoryBeanTests {
assertThat(cba.getCalls()).isEqualTo(0);
ITestBean tb = (ITestBean) bf.getBean("directTarget");
assertThat(tb.getName().equals("Adam")).isTrue();
assertThat(tb.getName()).isEqualTo("Adam");
assertThat(cba.getCalls()).isEqualTo(1);
ProxyFactoryBean pfb = (ProxyFactoryBean) bf.getBean("&directTarget");
assertThat(TestBean.class.isAssignableFrom(pfb.getObjectType())).as("Has correct object type").isTrue();
assertThat(pfb.getObjectType()).isAssignableTo(TestBean.class);
}
@Test
@@ -179,9 +181,9 @@ public class ProxyFactoryBeanTests {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(new ClassPathResource(TARGETSOURCE_CONTEXT, CLASS));
ITestBean tb = (ITestBean) bf.getBean("viaTargetSource");
assertThat(tb.getName().equals("Adam")).isTrue();
assertThat(tb.getName()).isEqualTo("Adam");
ProxyFactoryBean pfb = (ProxyFactoryBean) bf.getBean("&viaTargetSource");
assertThat(TestBean.class.isAssignableFrom(pfb.getObjectType())).as("Has correct object type").isTrue();
assertThat(pfb.getObjectType()).isAssignableTo(TestBean.class);
}
@Test
@@ -190,11 +192,15 @@ public class ProxyFactoryBeanTests {
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(new ClassPathResource(TARGETSOURCE_CONTEXT, CLASS));
ITestBean tb = (ITestBean) bf.getBean("noTarget");
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() ->
tb.getName())
.withMessage("getName");
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(tb::getName).withMessage("getName");
FactoryBean<?> pfb = (ProxyFactoryBean) bf.getBean("&noTarget");
assertThat(ITestBean.class.isAssignableFrom(pfb.getObjectType())).as("Has correct object type").isTrue();
assertThat(pfb.getObjectType()).isAssignableTo(ITestBean.class);
}
@Test
public void testGetObjectTypeOnUninitializedFactoryBean() {
ProxyFactoryBean pfb = new ProxyFactoryBean();
assertThat(pfb.getObjectType()).isNull();
}
/**
@@ -219,20 +225,20 @@ public class ProxyFactoryBeanTests {
pc1.addAdvice(1, di);
assertThat(pc2.getAdvisors()).isEqualTo(pc1.getAdvisors());
assertThat(pc2.getAdvisors().length).as("Now have one more advisor").isEqualTo((oldLength + 1));
assertThat(0).isEqualTo(di.getCount());
assertThat(di.getCount()).isEqualTo(0);
test1.setAge(5);
assertThat(test1.getAge()).isEqualTo(test1_1.getAge());
assertThat(3).isEqualTo(di.getCount());
assertThat(di.getCount()).isEqualTo(3);
}
@Test
public void testPrototypeInstancesAreNotEqual() {
assertThat(ITestBean.class.isAssignableFrom(factory.getType("prototype"))).as("Has correct object type").isTrue();
assertThat(factory.getType("prototype")).isAssignableTo(ITestBean.class);
ITestBean test2 = (ITestBean) factory.getBean("prototype");
ITestBean test2_1 = (ITestBean) factory.getBean("prototype");
assertThat(test2 != test2_1).as("Prototype instances !=").isTrue();
assertThat(test2.equals(test2_1)).as("Prototype instances equal").isTrue();
assertThat(ITestBean.class.isAssignableFrom(factory.getType("prototype"))).as("Has correct object type").isTrue();
assertThat(test2).as("Prototype instances !=").isNotSameAs(test2_1);
assertThat(test2).as("Prototype instances equal").isEqualTo(test2_1);
assertThat(factory.getType("prototype")).isAssignableTo(ITestBean.class);
}
/**
@@ -262,7 +268,7 @@ public class ProxyFactoryBeanTests {
assertThat(prototype2FirstInstance.getCount()).isEqualTo(INITIAL_COUNT + 1);
SideEffectBean prototype2SecondInstance = (SideEffectBean) bf.getBean(beanName);
assertThat(prototype2FirstInstance == prototype2SecondInstance).as("Prototypes are not ==").isFalse();
assertThat(prototype2FirstInstance).as("Prototypes are not ==").isNotSameAs(prototype2SecondInstance);
assertThat(prototype2SecondInstance.getCount()).isEqualTo(INITIAL_COUNT);
assertThat(prototype2FirstInstance.getCount()).isEqualTo(INITIAL_COUNT + 1);
@@ -285,19 +291,19 @@ public class ProxyFactoryBeanTests {
TestBean target = (TestBean) factory.getBean("test");
target.setName(name);
ITestBean autoInvoker = (ITestBean) factory.getBean("autoInvoker");
assertThat(autoInvoker.getName().equals(name)).isTrue();
assertThat(autoInvoker.getName()).isEqualTo(name);
}
@Test
public void testCanGetFactoryReferenceAndManipulate() {
ProxyFactoryBean config = (ProxyFactoryBean) factory.getBean("&test1");
assertThat(ITestBean.class.isAssignableFrom(config.getObjectType())).as("Has correct object type").isTrue();
assertThat(ITestBean.class.isAssignableFrom(factory.getType("test1"))).as("Has correct object type").isTrue();
assertThat(config.getObjectType()).isAssignableTo(ITestBean.class);
assertThat(factory.getType("test1")).isAssignableTo(ITestBean.class);
// Trigger lazy initialization.
config.getObject();
assertThat(config.getAdvisors().length).as("Have one advisors").isEqualTo(1);
assertThat(ITestBean.class.isAssignableFrom(config.getObjectType())).as("Has correct object type").isTrue();
assertThat(ITestBean.class.isAssignableFrom(factory.getType("test1"))).as("Has correct object type").isTrue();
assertThat(config.getObjectType()).isAssignableTo(ITestBean.class);
assertThat(factory.getType("test1")).isAssignableTo(ITestBean.class);
ITestBean tb = (ITestBean) factory.getBean("test1");
// no exception
@@ -308,7 +314,7 @@ public class ProxyFactoryBeanTests {
config.addAdvice(0, (MethodInterceptor) invocation -> {
throw ex;
});
assertThat(config.getAdvisors().length).as("Have correct advisor count").isEqualTo(2);
assertThat(config.getAdvisors()).as("Have correct advisor count").hasSize(2);
ITestBean tb1 = (ITestBean) factory.getBean("test1");
assertThatException()
@@ -348,31 +354,31 @@ public class ProxyFactoryBeanTests {
// Add to head of interceptor chain
int oldCount = config.getAdvisors().length;
config.addAdvisor(0, new DefaultIntroductionAdvisor(ti, TimeStamped.class));
assertThat(config.getAdvisors().length == oldCount + 1).isTrue();
assertThat(config.getAdvisors()).hasSize(oldCount + 1);
TimeStamped ts = (TimeStamped) factory.getBean("test2");
assertThat(ts.getTimeStamp()).isEqualTo(time);
// Can remove
config.removeAdvice(ti);
assertThat(config.getAdvisors().length == oldCount).isTrue();
assertThat(config.getAdvisors()).hasSize(oldCount);
// Check no change on existing object reference
assertThat(ts.getTimeStamp() == time).isTrue();
assertThat(ts.getTimeStamp()).isEqualTo(time);
assertThat(factory.getBean("test2")).as("Should no longer implement TimeStamped")
.isNotInstanceOf(TimeStamped.class);
// Now check non-effect of removing interceptor that isn't there
config.removeAdvice(new DebugInterceptor());
assertThat(config.getAdvisors().length == oldCount).isTrue();
assertThat(config.getAdvisors()).hasSize(oldCount);
ITestBean it = (ITestBean) ts;
DebugInterceptor debugInterceptor = new DebugInterceptor();
config.addAdvice(0, debugInterceptor);
it.getSpouse();
// Won't affect existing reference
assertThat(debugInterceptor.getCount() == 0).isTrue();
assertThat(debugInterceptor.getCount()).isEqualTo(0);
it = (ITestBean) factory.getBean("test2");
it.getSpouse();
assertThat(debugInterceptor.getCount()).isEqualTo(1);
@@ -412,16 +418,16 @@ public class ProxyFactoryBeanTests {
public void testMethodPointcuts() {
ITestBean tb = (ITestBean) factory.getBean("pointcuts");
PointcutForVoid.reset();
assertThat(PointcutForVoid.methodNames.isEmpty()).as("No methods intercepted").isTrue();
assertThat(PointcutForVoid.methodNames).as("No methods intercepted").isEmpty();
tb.getAge();
assertThat(PointcutForVoid.methodNames.isEmpty()).as("Not void: shouldn't have intercepted").isTrue();
assertThat(PointcutForVoid.methodNames).as("Not void: shouldn't have intercepted").isEmpty();
tb.setAge(1);
tb.getAge();
tb.setName("Tristan");
tb.toString();
assertThat(PointcutForVoid.methodNames.size()).as("Recorded wrong number of invocations").isEqualTo(2);
assertThat(PointcutForVoid.methodNames.get(0).equals("setAge")).isTrue();
assertThat(PointcutForVoid.methodNames.get(1).equals("setName")).isTrue();
assertThat(PointcutForVoid.methodNames).as("Recorded wrong number of invocations").hasSize(2);
assertThat(PointcutForVoid.methodNames.get(0)).isEqualTo("setAge");
assertThat(PointcutForVoid.methodNames.get(1)).isEqualTo("setName");
}
@Test
@@ -498,17 +504,17 @@ public class ProxyFactoryBeanTests {
@Test
public void testGlobalsCanAddAspectInterfaces() {
AddedGlobalInterface agi = (AddedGlobalInterface) factory.getBean("autoInvoker");
assertThat(agi.globalsAdded() == -1).isTrue();
assertThat(agi.globalsAdded()).isEqualTo(-1);
ProxyFactoryBean pfb = (ProxyFactoryBean) factory.getBean("&validGlobals");
// Trigger lazy initialization.
pfb.getObject();
// 2 globals + 2 explicit
assertThat(pfb.getAdvisors().length).as("Have 2 globals and 2 explicit advisors").isEqualTo(3);
assertThat(pfb.getAdvisors()).as("Have 2 globals and 2 explicit advisors").hasSize(3);
ApplicationListener<?> l = (ApplicationListener<?>) factory.getBean("validGlobals");
agi = (AddedGlobalInterface) l;
assertThat(agi.globalsAdded() == -1).isTrue();
assertThat(agi.globalsAdded()).isEqualTo(-1);
assertThat(factory.getBean("test1")).as("Aspect interface shouldn't be implemented without globals")
.isNotInstanceOf(AddedGlobalInterface.class);
@@ -709,6 +715,7 @@ public class ProxyFactoryBeanTests {
}
}
/**
* Aspect interface
*/
@@ -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.
@@ -118,6 +118,7 @@ class CacheReproTests {
assertThat(cacheResolver.getCache("foo").get("foo")).isNull();
Object result = bean.getSimple("foo"); // cache name = id
assertThat(cacheResolver.getCache("foo").get("foo").get()).isEqualTo(result);
context.close();
}
@@ -127,7 +128,7 @@ class CacheReproTests {
Spr13081Service bean = context.getBean(Spr13081Service.class);
assertThatIllegalStateException().isThrownBy(() -> bean.getSimple(null))
.withMessageContaining(MyCacheResolver.class.getName());
.withMessageContaining(MyCacheResolver.class.getName());
context.close();
}
@@ -146,6 +147,7 @@ class CacheReproTests {
TestBean tb2 = bean.findById("tb1").get();
assertThat(tb2).isNotSameAs(tb);
assertThat(cache.get("tb1").get()).isSameAs(tb2);
context.close();
}
@@ -177,6 +179,7 @@ class CacheReproTests {
bean.insertItem(tb);
assertThat(bean.findById("tb1").get()).isSameAs(tb);
assertThat(cache.get("tb1").get()).isSameAs(tb);
context.close();
}
@@ -190,6 +193,7 @@ class CacheReproTests {
bean.insertItem(tb);
assertThat(bean.findById("tb1").get()).isSameAs(tb);
assertThat(cache.get("tb1").get()).isSameAs(tb);
context.close();
}
@@ -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.
@@ -48,8 +48,9 @@ public class CacheSyncFailureTests {
private SimpleService simpleService;
@BeforeEach
public void setUp() {
public void setup() {
this.context = new AnnotationConfigApplicationContext(Config.class);
this.simpleService = this.context.getBean(SimpleService.class);
}
@@ -61,39 +62,40 @@ public class CacheSyncFailureTests {
}
}
@Test
public void unlessSync() {
assertThatIllegalStateException().isThrownBy(() ->
this.simpleService.unlessSync("key"))
.withMessageContaining("@Cacheable(sync=true) does not support unless attribute");
assertThatIllegalStateException()
.isThrownBy(() -> this.simpleService.unlessSync("key"))
.withMessageContaining("A sync=true operation does not support the unless attribute");
}
@Test
public void severalCachesSync() {
assertThatIllegalStateException().isThrownBy(() ->
this.simpleService.severalCachesSync("key"))
.withMessageContaining("@Cacheable(sync=true) only allows a single cache");
assertThatIllegalStateException()
.isThrownBy(() -> this.simpleService.severalCachesSync("key"))
.withMessageContaining("A sync=true operation is restricted to a single cache");
}
@Test
public void severalCachesWithResolvedSync() {
assertThatIllegalStateException().isThrownBy(() ->
this.simpleService.severalCachesWithResolvedSync("key"))
.withMessageContaining("@Cacheable(sync=true) only allows a single cache");
assertThatIllegalStateException()
.isThrownBy(() -> this.simpleService.severalCachesWithResolvedSync("key"))
.withMessageContaining("A sync=true operation is restricted to a single cache");
}
@Test
public void syncWithAnotherOperation() {
assertThatIllegalStateException().isThrownBy(() ->
this.simpleService.syncWithAnotherOperation("key"))
.withMessageContaining("@Cacheable(sync=true) cannot be combined with other cache operations");
assertThatIllegalStateException()
.isThrownBy(() -> this.simpleService.syncWithAnotherOperation("key"))
.withMessageContaining("A sync=true operation cannot be combined with other cache operations");
}
@Test
public void syncWithTwoGetOperations() {
assertThatIllegalStateException().isThrownBy(() ->
this.simpleService.syncWithTwoGetOperations("key"))
.withMessageContaining("Only one @Cacheable(sync=true) entry is allowed");
assertThatIllegalStateException()
.isThrownBy(() -> this.simpleService.syncWithTwoGetOperations("key"))
.withMessageContaining("Only one sync=true operation is allowed");
}
@@ -131,6 +133,7 @@ public class CacheSyncFailureTests {
}
}
@Configuration
@EnableCaching
static class Config implements CachingConfigurer {
@@ -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.
@@ -197,6 +197,7 @@ public class ClassPathBeanDefinitionScannerTests {
context.registerBeanDefinition("stubFooDao", new RootBeanDefinition(TestBean.class));
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
scanner.setIncludeAnnotationConfig(false);
// should not fail!
scanner.scan(BASE_PACKAGE);
}
@@ -207,10 +208,10 @@ public class ClassPathBeanDefinitionScannerTests {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
scanner.setIncludeAnnotationConfig(false);
scanner.scan("org.springframework.context.annotation3");
assertThatIllegalStateException().isThrownBy(() ->
scanner.scan(BASE_PACKAGE))
.withMessageContaining("stubFooDao")
.withMessageContaining(StubFooDao.class.getName());
assertThatIllegalStateException().isThrownBy(() -> scanner.scan(BASE_PACKAGE))
.withMessageContaining("stubFooDao")
.withMessageContaining(StubFooDao.class.getName());
}
@Test
@@ -267,11 +268,10 @@ public class ClassPathBeanDefinitionScannerTests {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
scanner.setIncludeAnnotationConfig(false);
scanner.scan("org.springframework.context.annotation2");
assertThatIllegalStateException().isThrownBy(() ->
scanner.scan(BASE_PACKAGE))
.withMessageContaining("myNamedDao")
.withMessageContaining(NamedStubDao.class.getName())
.withMessageContaining(NamedStubDao2.class.getName());
assertThatIllegalStateException().isThrownBy(() -> scanner.scan(BASE_PACKAGE))
.withMessageContaining("myNamedDao")
.withMessageContaining(NamedStubDao.class.getName())
.withMessageContaining(NamedStubDao2.class.getName());
}
@Test
@@ -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.
@@ -215,7 +215,7 @@ public class CommonAnnotationBeanPostProcessorTests {
bf.registerBeanDefinition("testBean4", tbd);
bf.registerResolvableDependency(BeanFactory.class, bf);
bf.registerResolvableDependency(INestedTestBean.class, (ObjectFactory<Object>) () -> new NestedTestBean());
bf.registerResolvableDependency(INestedTestBean.class, (ObjectFactory<Object>) NestedTestBean::new);
@SuppressWarnings("deprecation")
org.springframework.beans.factory.config.PropertyPlaceholderConfigurer ppc = new org.springframework.beans.factory.config.PropertyPlaceholderConfigurer();
@@ -233,7 +233,7 @@ public class CommonAnnotationBeanPostProcessorTests {
assertThat(tb).isNotSameAs(anotherBean.getTestBean6());
String[] depBeans = bf.getDependenciesForBean("annotatedBean");
assertThat(depBeans.length).isEqualTo(1);
assertThat(depBeans).hasSize(1);
assertThat(depBeans[0]).isEqualTo("testBean4");
}
@@ -508,6 +508,25 @@ public class CommonAnnotationBeanPostProcessorTests {
assertThat(tb.getName()).isEqualTo("notLazyAnymore");
}
@Test
public void testLazyResolutionWithFallbackTypeMatch() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
CommonAnnotationBeanPostProcessor bpp = new CommonAnnotationBeanPostProcessor();
bpp.setBeanFactory(bf);
bf.addBeanPostProcessor(bpp);
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(LazyResourceCglibInjectionBean.class));
bf.registerBeanDefinition("tb", new RootBeanDefinition(TestBean.class));
LazyResourceCglibInjectionBean bean = (LazyResourceCglibInjectionBean) bf.getBean("annotatedBean");
assertThat(bf.containsSingleton("tb")).isFalse();
bean.testBean.setName("notLazyAnymore");
assertThat(bf.containsSingleton("tb")).isTrue();
TestBean tb = (TestBean) bf.getBean("tb");
assertThat(tb.getName()).isEqualTo("notLazyAnymore");
}
public static class AnnotatedInitDestroyBean {
@@ -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.
@@ -79,7 +79,7 @@ class EnableLoadTimeWeavingTests {
@Configuration
@EnableLoadTimeWeaving(aspectjWeaving=AspectJWeaving.DISABLED)
@EnableLoadTimeWeaving(aspectjWeaving = AspectJWeaving.DISABLED)
static class EnableLTWConfig_withAjWeavingDisabled implements LoadTimeWeavingConfigurer {
@Override
@@ -88,8 +88,9 @@ class EnableLoadTimeWeavingTests {
}
}
@Configuration
@EnableLoadTimeWeaving(aspectjWeaving=AspectJWeaving.AUTODETECT)
@EnableLoadTimeWeaving(aspectjWeaving = AspectJWeaving.AUTODETECT)
static class EnableLTWConfig_withAjWeavingAutodetect implements LoadTimeWeavingConfigurer {
@Override
@@ -98,8 +99,9 @@ class EnableLoadTimeWeavingTests {
}
}
@Configuration
@EnableLoadTimeWeaving(aspectjWeaving=AspectJWeaving.ENABLED)
@EnableLoadTimeWeaving(aspectjWeaving = AspectJWeaving.ENABLED)
static class EnableLTWConfig_withAjWeavingEnabled implements LoadTimeWeavingConfigurer {
@Override
@@ -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,7 @@ class ImportTests {
private DefaultListableBeanFactory processConfigurationClasses(Class<?>... classes) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.setAllowBeanDefinitionOverriding(false);
for (Class<?> clazz : classes) {
beanFactory.registerBeanDefinition(clazz.getSimpleName(), new RootBeanDefinition(clazz));
}
@@ -56,9 +57,10 @@ class ImportTests {
for (Class<?> clazz : classes) {
beanFactory.getBean(clazz);
}
}
// ------------------------------------------------------------------------
@Test
void testProcessImportsWithAsm() {
int configClasses = 2;
@@ -158,6 +160,13 @@ class ImportTests {
assertBeanDefinitionCount(configClasses + beansInClasses, FirstLevel.class);
}
@Test
void testImportAnnotationWithThreeLevelRecursionAndDoubleImport() {
int configClasses = 5;
int beansInClasses = 5;
assertBeanDefinitionCount(configClasses + beansInClasses, FirstLevel.class, FirstLevelPlus.class);
}
// ------------------------------------------------------------------------
@Test
@@ -167,7 +176,6 @@ class ImportTests {
assertBeanDefinitionCount((configClasses + beansInClasses), WithMultipleArgumentsToImportAnnotation.class);
}
@Test
void testImportAnnotationWithMultipleArgumentsResultingInOverriddenBeanDefinition() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
@@ -245,6 +253,11 @@ class ImportTests {
}
}
@Configuration
@Import(ThirdLevel.class)
static class FirstLevelPlus {
}
@Configuration
@Import({ThirdLevel.class, InitBean.class})
static class SecondLevel {
@@ -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.
@@ -36,6 +36,10 @@ public abstract class AbstractApplicationEventListenerTests {
}
}
protected <T> GenericTestEvent<T> createGenericTestEvent(T payload) {
return new GenericTestEvent<>(this, payload);
}
protected static class GenericTestEvent<T> extends ApplicationEvent {
@@ -51,6 +55,7 @@ public abstract class AbstractApplicationEventListenerTests {
}
}
protected static class SmartGenericTestEvent<T> extends GenericTestEvent<T> implements ResolvableTypeProvider {
private final ResolvableType resolvableType;
@@ -67,6 +72,7 @@ public abstract class AbstractApplicationEventListenerTests {
}
}
protected static class StringEvent extends GenericTestEvent<String> {
public StringEvent(Object source, String payload) {
@@ -74,6 +80,7 @@ public abstract class AbstractApplicationEventListenerTests {
}
}
protected static class LongEvent extends GenericTestEvent<Long> {
public LongEvent(Object source, Long payload) {
@@ -81,31 +88,31 @@ public abstract class AbstractApplicationEventListenerTests {
}
}
protected <T> GenericTestEvent<T> createGenericTestEvent(T payload) {
return new GenericTestEvent<>(this, payload);
}
static class GenericEventListener implements ApplicationListener<GenericTestEvent<?>> {
@Override
public void onApplicationEvent(GenericTestEvent<?> event) {
}
}
static class ObjectEventListener implements ApplicationListener<GenericTestEvent<Object>> {
@Override
public void onApplicationEvent(GenericTestEvent<Object> event) {
}
}
static class UpperBoundEventListener
implements ApplicationListener<GenericTestEvent<? extends RuntimeException>> {
static class UpperBoundEventListener implements ApplicationListener<GenericTestEvent<? extends RuntimeException>> {
@Override
public void onApplicationEvent(GenericTestEvent<? extends RuntimeException> event) {
}
}
static class StringEventListener implements ApplicationListener<GenericTestEvent<String>> {
@Override
@@ -113,6 +120,7 @@ public abstract class AbstractApplicationEventListenerTests {
}
}
@SuppressWarnings("rawtypes")
static class RawApplicationListener implements ApplicationListener {
@@ -121,10 +129,10 @@ public abstract class AbstractApplicationEventListenerTests {
}
}
static class TestEvents {
public GenericTestEvent<?> wildcardEvent;
}
}
@@ -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.
@@ -164,12 +164,12 @@ class AnnotationDrivenEventListenerTests {
ContextEventListener listener = this.context.getBean(ContextEventListener.class);
List<Object> events = this.eventCollector.getEvents(listener);
assertThat(events.size()).as("Wrong number of initial context events").isEqualTo(1);
assertThat(events).as("Wrong number of initial context events").hasSize(1);
assertThat(events.get(0).getClass()).isEqualTo(ContextRefreshedEvent.class);
this.context.stop();
List<Object> eventsAfterStop = this.eventCollector.getEvents(listener);
assertThat(eventsAfterStop.size()).as("Wrong number of context events on shutdown").isEqualTo(2);
assertThat(eventsAfterStop).as("Wrong number of context events on shutdown").hasSize(2);
assertThat(eventsAfterStop.get(1).getClass()).isEqualTo(ContextStoppedEvent.class);
this.eventCollector.assertTotalEventsCount(2);
}
@@ -334,7 +334,7 @@ class AnnotationDrivenEventListenerTests {
load(ScopedProxyTestBean.class);
SimpleService proxy = this.context.getBean(SimpleService.class);
assertThat(proxy instanceof Advised).as("bean should be a proxy").isTrue();
assertThat(proxy).as("bean should be a proxy").isInstanceOf(Advised.class);
this.eventCollector.assertNoEventReceived(proxy.getId());
this.context.publishEvent(new ContextRefreshedEvent(this.context));
@@ -351,7 +351,7 @@ class AnnotationDrivenEventListenerTests {
load(AnnotatedProxyTestBean.class);
AnnotatedSimpleService proxy = this.context.getBean(AnnotatedSimpleService.class);
assertThat(proxy instanceof Advised).as("bean should be a proxy").isTrue();
assertThat(proxy).as("bean should be a proxy").isInstanceOf(Advised.class);
this.eventCollector.assertNoEventReceived(proxy.getId());
this.context.publishEvent(new ContextRefreshedEvent(this.context));
@@ -517,7 +517,6 @@ class AnnotationDrivenEventListenerTests {
ReplyEventListener replyEventListener = this.context.getBean(ReplyEventListener.class);
TestEventListener listener = this.context.getBean(TestEventListener.class);
this.eventCollector.assertNoEventReceived(listener);
this.eventCollector.assertNoEventReceived(replyEventListener);
this.context.publishEvent(event);
@@ -634,6 +633,17 @@ class AnnotationDrivenEventListenerTests {
assertThat(listener.order).contains("first", "second", "third");
}
@Test
void publicSubclassWithInheritedEventListener() {
load(PublicSubclassWithInheritedEventListener.class);
TestEventListener listener = this.context.getBean(PublicSubclassWithInheritedEventListener.class);
this.eventCollector.assertNoEventReceived(listener);
this.context.publishEvent("test");
this.eventCollector.assertEvent(listener, "test");
this.eventCollector.assertTotalEventsCount(1);
}
@Test @Disabled // SPR-15122
void listenersReceiveEarlyEvents() {
load(EventOnPostConstruct.class, OrderedTestListener.class);
@@ -646,7 +656,7 @@ class AnnotationDrivenEventListenerTests {
void missingListenerBeanIgnored() {
load(MissingEventListener.class);
context.getBean(UseMissingEventListener.class);
context.getBean(ApplicationEventMulticaster.class).multicastEvent(new TestEvent(this));
context.publishEvent(new TestEvent(this));
}
@@ -753,7 +763,6 @@ class AnnotationDrivenEventListenerTests {
public void handleContextEvent(ApplicationContextEvent event) {
collectEvent(event);
}
}
@@ -980,7 +989,6 @@ class AnnotationDrivenEventListenerTests {
}
@EventListener
@Retention(RetentionPolicy.RUNTIME)
public @interface ConditionalEvent {
@@ -1032,7 +1040,7 @@ class AnnotationDrivenEventListenerTests {
}
@Configuration
@Component
static class OrderedTestListener extends TestEventListener {
public final List<String> order = new ArrayList<>();
@@ -1056,6 +1064,11 @@ class AnnotationDrivenEventListenerTests {
}
@Component
public static class PublicSubclassWithInheritedEventListener extends TestEventListener {
}
static class EventOnPostConstruct {
@Autowired
@@ -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.
@@ -19,6 +19,7 @@ package org.springframework.context.event;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.List;
import org.junit.jupiter.api.Test;
@@ -46,6 +47,8 @@ import static org.mockito.Mockito.verify;
/**
* @author Stephane Nicoll
* @author Juergen Hoeller
* @author Simon Baslé
*/
public class ApplicationListenerMethodAdapterTests extends AbstractApplicationEventListenerTests {
@@ -80,16 +83,23 @@ public class ApplicationListenerMethodAdapterTests extends AbstractApplicationEv
supportsEventType(false, method, ResolvableType.forClassWithGenerics(GenericTestEvent.class, Long.class));
}
@Test
public void genericListenerWithUnresolvedGenerics() {
Method method = ReflectionUtils.findMethod(
SampleEvents.class, "handleGenericString", GenericTestEvent.class);
supportsEventType(true, method, ResolvableType.forClass(GenericTestEvent.class));
}
@Test
public void listenerWithPayloadAndGenericInformation() {
Method method = ReflectionUtils.findMethod(SampleEvents.class, "handleString", String.class);
supportsEventType(true, method, createGenericEventType(String.class));
supportsEventType(true, method, createPayloadEventType(String.class));
}
@Test
public void listenerWithInvalidPayloadAndGenericInformation() {
Method method = ReflectionUtils.findMethod(SampleEvents.class, "handleString", String.class);
supportsEventType(false, method, createGenericEventType(Integer.class));
supportsEventType(false, method, createPayloadEventType(Integer.class));
}
@Test
@@ -113,28 +123,28 @@ public class ApplicationListenerMethodAdapterTests extends AbstractApplicationEv
@Test
public void listenerWithAnnotationValue() {
Method method = ReflectionUtils.findMethod(SampleEvents.class, "handleStringAnnotationValue");
supportsEventType(true, method, createGenericEventType(String.class));
supportsEventType(true, method, createPayloadEventType(String.class));
}
@Test
public void listenerWithAnnotationClasses() {
Method method = ReflectionUtils.findMethod(SampleEvents.class, "handleStringAnnotationClasses");
supportsEventType(true, method, createGenericEventType(String.class));
supportsEventType(true, method, createPayloadEventType(String.class));
}
@Test
public void listenerWithAnnotationValueAndParameter() {
Method method = ReflectionUtils.findMethod(
SampleEvents.class, "handleStringAnnotationValueAndParameter", String.class);
supportsEventType(true, method, createGenericEventType(String.class));
supportsEventType(true, method, createPayloadEventType(String.class));
}
@Test
public void listenerWithSeveralTypes() {
Method method = ReflectionUtils.findMethod(SampleEvents.class, "handleStringOrInteger");
supportsEventType(true, method, createGenericEventType(String.class));
supportsEventType(true, method, createGenericEventType(Integer.class));
supportsEventType(false, method, createGenericEventType(Double.class));
supportsEventType(true, method, createPayloadEventType(String.class));
supportsEventType(true, method, createPayloadEventType(Integer.class));
supportsEventType(false, method, createPayloadEventType(Double.class));
}
@Test
@@ -325,6 +335,88 @@ public class ApplicationListenerMethodAdapterTests extends AbstractApplicationEv
verify(this.context, times(2)).getBean("testBean");
}
@Test // gh-30399
void simplePayloadDoesNotSupportArbitraryGenericEventType() throws Exception {
Method method = SampleEvents.class.getDeclaredMethod("handleString", String.class);
ApplicationListenerMethodAdapter adapter = createTestInstance(method);
assertThat(adapter.supportsEventType(createPayloadEventType(ResolvableType.forClassWithGenerics(EntityWrapper.class, Integer.class))))
.as("handleString(String) with EntityWrapper<Integer>").isFalse();
assertThat(adapter.supportsEventType(createPayloadEventType(ResolvableType.forClass(EntityWrapper.class))))
.as("handleString(String) with EntityWrapper<?>").isFalse();
assertThat(adapter.supportsEventType(createPayloadEventType(ResolvableType.forClass(String.class))))
.as("handleString(String) with String").isTrue();
}
@Test // gh-30399
void genericPayloadDoesNotSupportArbitraryGenericEventType() throws Exception {
Method method = SampleEvents.class.getDeclaredMethod("handleGenericStringPayload", EntityWrapper.class);
ApplicationListenerMethodAdapter adapter = createTestInstance(method);
assertThat(adapter.supportsEventType(createPayloadEventType(ResolvableType.forClass(EntityWrapper.class))))
.as("handleGenericStringPayload(EntityWrapper<String>) with EntityWrapper<?>").isFalse();
assertThat(adapter.supportsEventType(createPayloadEventType(ResolvableType.forClassWithGenerics(EntityWrapper.class, Integer.class))))
.as("handleGenericStringPayload(EntityWrapper<String>) with EntityWrapper<Integer>").isFalse();
assertThat(adapter.supportsEventType(createPayloadEventType(ResolvableType.forClassWithGenerics(EntityWrapper.class, String.class))))
.as("handleGenericStringPayload(EntityWrapper<String>) with EntityWrapper<String>").isTrue();
}
@Test // gh-30399
void rawGenericPayloadDoesNotSupportArbitraryGenericEventType() throws Exception {
Method method = SampleEvents.class.getDeclaredMethod("handleGenericAnyPayload", EntityWrapper.class);
ApplicationListenerMethodAdapter adapter = createTestInstance(method);
assertThat(adapter.supportsEventType(createPayloadEventType(ResolvableType.forClass(EntityWrapper.class))))
.as("handleGenericAnyPayload(EntityWrapper<?>) with EntityWrapper<?>").isTrue();
assertThat(adapter.supportsEventType(createPayloadEventType(ResolvableType.forClassWithGenerics(EntityWrapper.class, Integer.class))))
.as("handleGenericAnyPayload(EntityWrapper<?>) with EntityWrapper<Integer>").isTrue();
assertThat(adapter.supportsEventType(createPayloadEventType(ResolvableType.forClassWithGenerics(EntityWrapper.class, String.class))))
.as("handleGenericAnyPayload(EntityWrapper<?>) with EntityWrapper<String>").isTrue();
assertThat(adapter.supportsEventType(createPayloadEventType(ResolvableType.forClass(List.class))))
.as("handleGenericAnyPayload(EntityWrapper<?>) with List<?>").isFalse();
assertThat(adapter.supportsEventType(createPayloadEventType(ResolvableType.forClassWithGenerics(List.class, String.class))))
.as("handleGenericAnyPayload(EntityWrapper<?>) with List<String>").isFalse();
}
@Test // gh-30399
void genericApplicationEventSupportsSpecificType() throws Exception {
Method method = SampleEvents.class.getDeclaredMethod("handleGenericString", GenericTestEvent.class);
ApplicationListenerMethodAdapter adapter = createTestInstance(method);
assertThat(adapter.supportsEventType(ResolvableType.forClass(GenericTestEvent.class)))
.as("handleGenericString(GenericTestEvent<String>) with GenericTestEvent<?>").isTrue();
assertThat(adapter.supportsEventType(ResolvableType.forClassWithGenerics(GenericTestEvent.class, Integer.class)))
.as("handleGenericString(GenericTestEvent<String>) with GenericTestEvent<Integer>").isFalse();
assertThat(adapter.supportsEventType(ResolvableType.forClassWithGenerics(GenericTestEvent.class, String.class)))
.as("handleGenericString(GenericTestEvent<String>) with GenericTestEvent<String>").isTrue();
}
@Test // gh-30399
void genericRawApplicationEventSupportsRawTypeAndAnySpecificType() throws Exception {
Method method = SampleEvents.class.getDeclaredMethod("handleGenericRaw", GenericTestEvent.class);
ApplicationListenerMethodAdapter adapter = createTestInstance(method);
assertThat(adapter.supportsEventType(ResolvableType.forClass(GenericTestEvent.class)))
.as("handleGenericRaw(GenericTestEvent<?>) with GenericTestEvent<?>").isTrue();
assertThat(adapter.supportsEventType(ResolvableType.forClassWithGenerics(GenericTestEvent.class, String.class)))
.as("handleGenericRaw(GenericTestEvent<?>) with GenericTestEvent<String>").isTrue();
assertThat(adapter.supportsEventType(ResolvableType.forClassWithGenerics(GenericTestEvent.class, Integer.class)))
.as("handleGenericRaw(GenericTestEvent<?>) with GenericTestEvent<Integer>").isTrue();
}
@Test // gh-30399
void unrelatedApplicationEventDoesNotSupportRawTypeOrAnySpecificType() throws Exception {
Method method = SampleEvents.class.getDeclaredMethod("handleUnrelated", ContextRefreshedEvent.class);
ApplicationListenerMethodAdapter adapter = createTestInstance(method);
assertThat(adapter.supportsEventType(ResolvableType.forClass(GenericTestEvent.class)))
.as("handleUnrelated(ContextRefreshedEvent) with GenericTestEvent<?>").isTrue(); // known bug in 5.3.x
assertThat(adapter.supportsEventType(ResolvableType.forClassWithGenerics(GenericTestEvent.class, String.class)))
.as("handleUnrelated(ContextRefreshedEvent) with GenericTestEvent<String>").isFalse();
assertThat(adapter.supportsEventType(ResolvableType.forClassWithGenerics(GenericTestEvent.class, Integer.class)))
.as("handleUnrelated(ContextRefreshedEvent) with GenericTestEvent<Integer>").isFalse();
}
private void supportsEventType(boolean match, Method method, ResolvableType eventType) {
ApplicationListenerMethodAdapter adapter = createTestInstance(method);
@@ -341,7 +433,11 @@ public class ApplicationListenerMethodAdapterTests extends AbstractApplicationEv
return new StaticApplicationListenerMethodAdapter(method, this.sampleEvents);
}
private ResolvableType createGenericEventType(Class<?> payloadType) {
private ResolvableType createPayloadEventType(Class<?> payloadType) {
return ResolvableType.forClassWithGenerics(PayloadApplicationEvent.class, payloadType);
}
private ResolvableType createPayloadEventType(ResolvableType payloadType) {
return ResolvableType.forClassWithGenerics(PayloadApplicationEvent.class, payloadType);
}
@@ -373,6 +469,14 @@ public class ApplicationListenerMethodAdapterTests extends AbstractApplicationEv
public void handleGenericString(GenericTestEvent<String> event) {
}
@EventListener
public void handleGenericRaw(GenericTestEvent<?> event) {
}
@EventListener
public void handleUnrelated(ContextRefreshedEvent event) {
}
@EventListener
public void handleString(String payload) {
}
@@ -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.
@@ -104,7 +104,8 @@ public class GenericApplicationListenerAdapterTests extends AbstractApplicationE
@Test
public void genericListenerStrictTypeSubClass() {
supportsEventType(false, ObjectEventListener.class, ResolvableType.forClassWithGenerics(GenericTestEvent.class, Long.class));
supportsEventType(false, ObjectEventListener.class,
ResolvableType.forClassWithGenerics(GenericTestEvent.class, Long.class));
}
@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.
@@ -133,7 +133,7 @@ public class DateFormattingTests {
assertThat(exception)
.hasMessageContaining("for property 'styleDate'")
.hasCauseInstanceOf(ConversionFailedException.class).cause()
.hasMessageContaining("for value '99/01/01'")
.hasMessageContaining("for value [99/01/01]")
.hasCauseInstanceOf(IllegalArgumentException.class).cause()
.hasMessageContaining("Parse attempt failed for value [99/01/01]")
.hasCauseInstanceOf(ParseException.class).cause()
@@ -353,7 +353,7 @@ public class DateFormattingTests {
assertThat(fieldError.unwrap(TypeMismatchException.class))
.hasMessageContaining("for property 'patternDateWithFallbackPatterns'")
.hasCauseInstanceOf(ConversionFailedException.class).cause()
.hasMessageContaining("for value '210302'")
.hasMessageContaining("for value [210302]")
.hasCauseInstanceOf(IllegalArgumentException.class).cause()
.hasMessageContaining("Parse attempt failed for value [210302]")
.hasCauseInstanceOf(ParseException.class).cause()

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