Compare commits

...

181 Commits

Author SHA1 Message Date
Spring Builds 73a1f4f82e Release v6.0.12 2023-09-14 07:51:30 +00:00
Juergen Hoeller a51eb29e50 Clarify IN clause resolution with List/Iterable parameter
Closes gh-31228
2023-09-14 09:24:18 +02:00
Stephane Nicoll 50d4a44dfc Upgrade to Context Propagation 1.0.5
Closes gh-31223
2023-09-13 18:19:06 +02:00
Stephane Nicoll ebf2cef94e Upgrade to Reactor 2022.0.11
Closes gh-31222
2023-09-13 18:17:52 +02:00
Stephane Nicoll c89002a0fb Upgrade to Micrometer 1.10.11
Closes gh-31221
2023-09-13 18:16:59 +02:00
Sébastien Deleuze 29a4dabbe7 Support @ModelAttribute with suspending function in WebFlux
Closes gh-30894
2023-09-13 17:59:39 +02:00
Sam Brannen f5f8eab405 Remove duplicated section links for test annotations
Since the auto-generated "Section Summary" includes the exact same
links, there's no need to manually duplicate them.
2023-09-13 17:50:12 +02:00
Juergen Hoeller 659500bc1f Polishing 2023-09-13 17:27:32 +02:00
Juergen Hoeller 4235a11c4f Throw IllegalArgumentException for unsupported Duration values
Closes gh-31210
2023-09-13 17:15:32 +02:00
Juergen Hoeller 966b0a92c6 Defensively call Resource.getFile() for fallback resolution
Closes gh-31216
2023-09-13 17:14:04 +02:00
Sam Brannen 4ca70256d6 Backport polishing 2023-09-13 16:09:59 +02:00
Sébastien Deleuze c892ce5537 Refine CORS documentation for wildcard processing
This commit adds a reference documentation section dedicated
to CORS credentialed requests and related wildcard processing.

Closes gh-31143
2023-09-11 18:07:47 +02:00
Sébastien Deleuze 76b8bb2c75 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-31143
2023-09-11 18:07:31 +02:00
Juergen Hoeller db7654225e Polishing 2023-09-11 17:36:57 +02:00
Juergen Hoeller 78fce80c43 AnnotationUtils.clearCache() includes all annotation caches
Closes gh-31170
2023-09-11 17:36:32 +02:00
Juergen Hoeller 268043e9c9 Align abstract method signatures with original Commons Logging API
Closes gh-31166
2023-09-11 17:36:07 +02:00
Juergen Hoeller 2880e6fba5 Consistently release savepoint after nested transaction
Closes gh-31133
2023-09-11 17:36:00 +02:00
Zakaria Shahen 11dc11e989 Fix typo in comment in XML configuration example
Closes gh-31194
2023-09-10 14:36:17 +02:00
Sam Brannen 10de295a72 Document StandardTypeLocator configuration to support user types
Prior to this commit, it was unclear to users and third parties that it
is necessary to manually configure a StandardTypeLocator with a
specific ClassLoader to ensure that the SpEL expression parser is able
to reliably locate user types.

For example, the StandardBeanExpressionResolver in the spring-context
module configures a StandardTypeLocator using the bean ClassLoader of
the corresponding BeanFactory.

This commit improves the documentation to raise awareness of this fact.

Closes gh-26253
2023-09-08 17:19:51 +02:00
Sam Brannen 1227fe5774 Polishing 2023-09-08 16:30:53 +02:00
Sam Brannen ea41051651 Do not invoke [Map|Collection].isEmpty() in nullSafeConciseToString()
gh-30810 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-31138
2023-09-08 16:03:00 +02:00
Sam Brannen 311c58ea2d Polish [Standard]TypeLocator 2023-09-08 15:21:48 +02:00
Sam Brannen 40f1cf67bd Polish DefaultClientResponseTests and suppress "unchecked" warnings 2023-09-08 15:21:48 +02:00
Sam Brannen 6da9aed055 Update copyright header 2023-09-08 15:21:48 +02:00
Sébastien Deleuze 12a01a680b Document some non-nullable Kotlin extensions can throw NoSuchElementException
Closes gh-31189
2023-09-08 13:07:58 +02:00
rstoyanchev 740f3b797f Polishing
See gh-31185
2023-09-08 10:07:21 +01:00
Sébastien Deleuze ab48b88f91 Refine BeanValidationBeanRegistrationAotProcessor logging
This commit prints a log message at debug level without
a stacktrace for TypeNotPresentException and uses
warn level instead of error level for other exceptions
since the processing of such bean will just be skipped.

Closes gh-31147
2023-09-08 10:50:57 +02:00
rstoyanchev eda35e8074 Add note to the interceptor section of the MVC config
Closes gh-31185
2023-09-08 09:17:59 +01:00
Brian Clozel e4887f3ed9 Upgrade Ubuntu in CI image
Upgrade to ubuntu:jammy-20230816
2023-09-07 17:27:41 +02:00
Sam Brannen 1cdbd68aec Revise FilePatternResourceHintsRegistrar API and improve documentation
This commit revises the FilePatternResourceHintsRegistrar API and
introduces List<String> overrides of various var-args methods used with
the new builder API.

Closes gh-29161
2023-09-07 16:45:50 +02:00
Sam Brannen 453c0e5191 Backport changes from main 2023-09-07 16:45:50 +02:00
rstoyanchev a6b567a630 Polishing 2023-09-07 15:27:40 +01:00
rstoyanchev 7a516170ef WebClientResponseException supports decoding empty content
Closes gh-31179
2023-09-07 15:27:40 +01:00
shin-mallang 249f6f2da5 Polish resolveArgument method in RequestResponseBodyMethodProcessor
Closes gh-31175
2023-09-07 13:09:40 +02:00
Johnny Lim 73766c01e6 Add Javadoc since tags in FilePatternResourceHintsRegistrar
See gh-29161
Closes gh-31174
2023-09-05 16:31:46 +02:00
Juergen Hoeller 278f228688 Upgrade to Groovy 4.0.14, Tomcat 10.1.13, Jetty 11.0.16, Netty 4.1.97, Undertow 2.3.8, Mockito 5.5 2023-09-03 01:25:47 +02:00
Juergen Hoeller c0c4298048 Polishing 2023-09-03 01:14:55 +02:00
Juergen Hoeller 61f89a1f04 Refine note on listener container setup with CachingConnectionFactory
Closes gh-25503
2023-09-03 01:13:51 +02:00
Sam Brannen 3e3f05109f Polishing 2023-09-02 19:06:10 +02:00
Sam Brannen 2d1d14b145 Add /framework-*/build to .gitignore
Due to the introduction of the `framework-api` project in the main branch,
we now have 3 `framework-*` projects that should be ignored. I have
therefore removed `/framework-bom/build` and `/framework-docs/build` and
added `/framework-*/build` to ignore all 3 framework projects.

See gh-31049
2023-09-02 18:28:24 +02:00
Sébastien Deleuze 1641cb75e2 Refine Reactor field precomputing on native
This commit refines Reactor field precomputing on native
to only compute at build-time fields in the reactor.core
package, since doing so in reactor.netty has unwanted side
effects like Epoll always disabled.

Closes gh-31141
2023-09-01 12:52:27 +02:00
Sam Brannen c01e1b8901 Document purpose of the name attribute in @PropertySource
Closes gh-30195
2023-08-31 13:39:22 +02:00
Sam Brannen 2e4e43b5bd Polish org.springframework.core.env.PropertySource
See gh-30195
2023-08-31 13:38:13 +02:00
Sam Brannen 6876c284f4 Upgrade to Checkstyle 10.12.3 2023-08-28 14:43:40 +02:00
Sam Brannen b167152108 Upgrade to spring-javaformat-checkstyle 0.0.39 2023-08-28 14:43:05 +02:00
Sam Brannen bbf73848b5 Update warning for use of convention-based annotation attribute overrides
See gh-28761
2023-08-27 19:02:57 +02:00
Stephane Nicoll 1af259f928 Merge pull request #29753 from perlun
* pr/29753:
  Polish "Make sure NoUniqueBeanDefinitionException to be serializable"
  Make sure NoUniqueBeanDefinitionException to be serializable

Closes gh-29753
2023-08-27 18:03:44 +02:00
Stephane Nicoll 1396daa4b6 Polish "Make sure NoUniqueBeanDefinitionException to be serializable"
See gh-29753
2023-08-27 17:59:27 +02:00
Per Lundberg 1b409d5290 Make sure NoUniqueBeanDefinitionException to be serializable
See gh-29753
2023-08-27 17:56:58 +02:00
Stephane Nicoll ca14202d78 Merge pull request #27115 from gnagy
* pr/27115:
  Update copyright of changed file
  Allow null attribute value in Model.set()

Closes gh-27115
2023-08-26 16:41:05 +02:00
Stephane Nicoll 7231f22c23 Update copyright of changed file
See gh-27115
2023-08-26 16:39:25 +02:00
Gergely Nagy 3470240ef0 Allow null attribute value in Model.set()
See gh-27115
2023-08-26 16:38:19 +02:00
Sam Brannen 7598bca799 Revise Checkstyle rules to prohibit use of assertions other than AssertJ
Closes gh-31116
2023-08-26 15:28:25 +02:00
Sam Brannen f9588de247 Reinstate FailingBeforeAndAfterMethodsTestNGTests
The tests were ignored due to "Fails against TestNG 6.11"; however,
these tests pass against the current version of TestNG that we build
against (7.8.0).
2023-08-26 15:22:38 +02:00
Stephane Nicoll 2b3539a6de Merge pull request #26761 from 1zg12
* pr/26761:
  Polish "Restore customization of PropertyResolver"
  Restore customization of PropertyResolver

Closes gh-26761
2023-08-26 10:17:45 +02:00
Stephane Nicoll 2731d4f100 Polish "Restore customization of PropertyResolver"
See gh-26761
2023-08-26 10:17:02 +02:00
lwpro2 00fffb7ab0 Restore customization of PropertyResolver
This commit reintroduces the ability to customize the PropertyResolver
to use in PropertySourcesPropertyResolver

See gh-26761
2023-08-26 10:09:42 +02:00
Sam Brannen 89b7a6bf47 Skip searching of nonexistent directory in PathMatchingResourcePatternResolver
Prior to this commit, when PathMatchingResourcePatternResolver
processed a `file:` pattern (for example, `file:/app-config/**`) for a
`rootPath` that did not exist in the filesystem, the resolver attempted
to search the directory and logged a WARNING message similar to the
following when it failed to do so.

Failed to search in directory [/app-config/] for files matching pattern
[**]: java.nio.file.NoSuchFileException: /app-config/

To avoid unnecessary attempts to search a nonexistent directory,
PathMatchingResourcePatternResolver now skips searching of a nonexistent
directory and preemptively logs an INFO message similar to the
following.

Skipping search for files matching pattern [**]: directory [/app-config]
does not exist

Closes gh-31111
2023-08-25 16:54:10 +02:00
Sam Brannen 19570338c9 Ensure doFindPathMatchingFileResources() returns a mutable Set
This commit ensures that PathMatchingResourcePatternResolver's
doFindPathMatchingFileResources() method returns a mutable Set in order
to comply with the documented contract.
2023-08-25 16:53:00 +02:00
Juergen Hoeller 906a9f7982 Polishing 2023-08-23 18:52:55 +02:00
Juergen Hoeller 6fed3a0d6b Consistently throw ParseException instead of IllegalStateException
Closes gh-31097
2023-08-23 18:50:52 +02:00
Sébastien Deleuze 8934eb8464 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-30272
2023-08-23 18:07:54 +02:00
Stephane Nicoll 4b9f89101d Clarify handling of several representations for JOpt options
See gh-22168
2023-08-23 10:46:21 +02:00
Stephane Nicoll b204f2e396 Merge pull request #30107 from izeye
* pr/30107:
  Use IllegalStateException in ReactiveTestTransactionManager.doCommit()

Closes gh-30107
2023-08-22 17:01:15 +02:00
Johnny Lim 112f755e17 Use IllegalStateException in ReactiveTestTransactionManager.doCommit()
See gh-30107
2023-08-22 17:00:11 +02:00
Stephane Nicoll 542d0ef0b4 Merge pull request #31091 from aahlenst
* pr/31091:
  Fix invalid type name in RSocket code example

Closes gh-31091
2023-08-22 16:57:21 +02:00
Andreas Ahlenstorf 452b2df849 Fix invalid type name in RSocket code example
See gh-31091
2023-08-22 16:55:59 +02:00
Sam Brannen 229b4782ee Add @Nullable in doSetValue() in Argument[Type]PreparedStatementSetter
This commit adds @Nullable to the argValue parameters in the
doSetValue() methods in ArgumentPreparedStatementSetter and
ArgumentTypePreparedStatementSetter.

Closes gh-31086
2023-08-21 16:01:42 +02:00
Sam Brannen ad1554a631 Polishing 2023-08-21 16:01:22 +02:00
Juergen Hoeller 8be77cc650 Revise documentation for cache infrastructure setup
Closes gh-28250
2023-08-21 15:42:50 +02:00
Sam Brannen d0d0ed0578 Update copyright headers 2023-08-21 15:18:04 +02:00
Sam Brannen 9911d91f08 Add implementation note
See gh-31083
2023-08-21 15:17:57 +02:00
Sam Brannen 620a87bcbc Polishing 2023-08-21 14:17:05 +02:00
Yanming Zhou 368036cab4 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:03:24 +02:00
Juergen Hoeller 8a6c0cd221 Polishing 2023-08-21 12:37:58 +02:00
Juergen Hoeller b0fc2fe473 Document destroy method inference more prominently
Closes gh-29546
2023-08-21 12:32:45 +02:00
Juergen Hoeller 2952cb95f5 Document custom SimpleApplicationEventMulticaster setup
Closes gh-29996
2023-08-21 12:32:33 +02:00
Sébastien Deleuze c91708c1c0 Add missing proxy hints for Hibernate native query
This commit contributes proxy hints needed by
SharedEntityManagerCreator for
org.hibernate.query.sql.internal.NativeQueryImpl interfaces.

A related smoke test has been contributed via
spring-projects/spring-aot-smoke-tests#188.

Closes gh-29603
2023-08-18 17:22:57 +02:00
Stephane Nicoll 47b1a2bc55 Clarify usage of FilePatternResourceHintsRegistrar
This commit review the API using a builder to make it more clear what
the registrar does.

Closes gh-29161
2023-08-18 15:35:20 +02:00
Sébastien Deleuze e6565c600a Refine type conversion errors in ModelAttributeMethodProcessor
This commit turns TypeMismatchException thrown in
ModelAttributeMethodArgumentResolver#createAttribute into
proper ServerWebInputException in order get HTTP response
with 400 Bad Request status code instead of 500 Internal error.

Closes gh-31045
2023-08-18 14:31:43 +02:00
Sébastien Deleuze 8af9648c43 Polish ModelAttributeMethodArgumentResolverTests 2023-08-18 12:47:44 +02:00
Juergen Hoeller c5bdd9d79d Optimize whitespace checks in StringUtils
Closes gh-31067
2023-08-18 11:25:48 +02:00
Sam Brannen 9efa99e0d8 Update link to "Method visibility and @Transactional in proxy mode"
See gh-31057
See gh-25582
2023-08-17 17:54:44 +02:00
Sam Brannen 957eb021d7 Upgrade to Gradle 8.3
Closes gh-31061
2023-08-17 17:32:05 +02:00
Juergen Hoeller c8a4026512 Revise note on non-public transactional methods for 6.0
Closes gh-31057
See gh-25582
2023-08-17 10:24:03 +02:00
Juergen Hoeller 2111bf9b9d Upgrade to Caffeine 3.1.8 and Apache HttpComponents Core Reactive 5.2.2 2023-08-16 17:53:02 +02:00
Juergen Hoeller c72dd1ff66 Change "!void" pointcut expression to "int" for AspectJ 1.9.20 2023-08-16 17:32:26 +02:00
Juergen Hoeller 43bd78913c Polishing 2023-08-16 16:52:53 +02:00
Juergen Hoeller 5458e0dccc Upgrade to Tomcat 10.1.12 and AspectJ 1.9.20 2023-08-16 16:52:45 +02:00
Sébastien Deleuze 78a73e5f57 Add missing Hibernate 6.2 proxy hints
This commit contributes proxy hints needed by
SharedEntityManagerCreator for
org.hibernate.query.sqm.internal.QuerySqmImpl interfaces.

Until Hibernate 6.1, those hints were erroneously provided
at GraalVM reachability metadata level. As of Hibernate 6.2,
they are not, hence the need to contribute them at Spring
Framework level.

A related smoke test has been contributed via
spring-projects/spring-aot-smoke-tests#188.

Closes gh-31050
2023-08-16 15:57:05 +02:00
Juergen Hoeller c7269feeaa 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
2023-08-16 12:48:06 +02:00
Juergen Hoeller 1e75041b00 Consistent references to scalar values vs Parameter objects
See gh-27282
2023-08-16 11:00:17 +02:00
Juergen Hoeller 08bc7ed8f0 Polishing 2023-08-15 23:51:41 +02:00
Juergen Hoeller 2ce75dc415 Polishing 2023-08-14 19:28:19 +02:00
Juergen Hoeller 8b3ddeed05 Test factory-bean/method placeholders as well
See gh-20189
2023-08-14 19:28:12 +02:00
Juergen Hoeller 389238f622 Add registerReactiveTypeOverride method to ReactiveAdapterRegistry
Closes gh-31047
2023-08-14 15:14:22 +02:00
Sam Brannen 6f2a13fafd Polishing 2023-08-12 15:14:05 +02:00
Juergen Hoeller 6baa60d454 Polishing 2023-08-12 14:51:02 +02:00
Juergen Hoeller 92410395e3 Remove outdated documentation references to WebLogic/WebSphere
See gh-22093
2023-08-12 14:50:45 +02:00
Juergen Hoeller d03af15516 Explicit note on connection pool deadlock with REQUIRES_NEW
Closes gh-26250
2023-08-12 14:50:36 +02:00
Juergen Hoeller d781f299c0 Use extracted attributes instead of annotation access
See gh-31034
2023-08-12 11:34:25 +02:00
Juergen Hoeller 6fc4898a1b Find TransactionalEventListener annotation on target method
Closes gh-31034
2023-08-12 11:19:21 +02:00
Sébastien Deleuze 0c15be004e Use Any? in ProceedingJoinPoint Kotlin examples
This commit changes Any to Any? in ProceedingJoinPoint
Kotlin examples in order to be consistent with Java
and avoid a "NullPointerException: pjp.proceed() must
not be null" error.

Closes gh-31015
2023-08-10 19:12:55 +02:00
AlmostFamiliar 1c6ef3fe38 Use Any? in ProceedingJoinPoint Kotlin examples
This commit changes Any to Any? in ProceedingJoinPoint
Kotlin examples in order to be consistent with Java
and avoid a "NullPointerException: pjp.proceed() must
not be null" error.

See gh-31015
2023-08-10 19:12:42 +02:00
Juergen Hoeller d254bff197 Polishing 2023-08-09 23:53:40 +02:00
Juergen Hoeller 6fc5a78252 Cancel without interruption of currently running tasks
Leave potential interruption up to scheduler shutdown.

Closes gh-31019
2023-08-09 23:53:35 +02:00
Juergen Hoeller d58e48d9f5 Explicit note on FactoryBean resolution with MBeanExporter
Closes gh-21676
2023-08-08 20:11:28 +02:00
Juergen Hoeller 8973d1ad8a Polishing 2023-08-08 20:10:23 +02:00
Juergen Hoeller 2aae0a4e0c Polishing 2023-08-07 14:51:58 +02:00
Juergen Hoeller 156b3696a7 Reinstate Introspector.flushFromCaches() call for JDK ClassInfo cache
Closes gh-27781
2023-08-07 14:51:49 +02:00
Juergen Hoeller c36174b263 Polishing 2023-08-06 14:59:44 +02:00
Juergen Hoeller 6e5af9dccb Polishing 2023-08-06 14:25:39 +02:00
Sam Brannen 4a81814dbb Check exception cause for @PropertySource(ignoreResourceNotFound) support
Prior to this commit, the ignoreResourceNotFound flag in
@PropertySource was ignored by PropertySourceProcessor if a
PropertySourceFactory threw an exception which wrapped an exception
that would otherwise be ignored -- for example, a FileNotFoundException.

To address this issue, this commit updates PropertySourceFactory so
that it catches RuntimeException and IOException and then checks if the
exception or its cause is an "ignorable" exception in terms of
ignoreResourceNotFound semantics.

Closes gh-22276
2023-08-05 10:19:43 +03:00
Sam Brannen 1451f30781 Polish PropertySourceProcessor 2023-08-05 10:19:43 +03:00
Sam Brannen 4b54ca46d3 Polish PropertySourceDescriptor 2023-08-05 10:19:43 +03:00
Sam Brannen 169392e132 Polish StringHttpMessageConverterTests 2023-08-04 15:53:37 +03:00
Sam Brannen c050642290 Polish contribution
See gh-30942
2023-08-04 15:50:07 +03:00
Patrick Strawderman 7636eecb48 Use readNBytes in StringHttpMessageConverter when contentLength is available
When the content length is known, use readNBytes on the InputStream in
StringHttpMessageConverter, which avoids some extra copying and allocations.

Closes gh-30942
2023-08-04 15:43:10 +03:00
Sam Brannen d890827bae Fisk asterisk formatting in Asciidoc 2023-08-04 15:41:39 +03:00
xumengqi 07a1aea9c7 Skip array sort when the length of array not greater than 1
Closes gh-30934
2023-08-04 15:25:46 +03:00
Sébastien Deleuze da7b68a643 Support Kotlin Serialization custom serializers
This commit updates WebMVC converters and WebFlux
encoders/decoders to support custom serializers
with Kotlin Serialization when specified via
a custom SerialFormat.

It also turns the serializers cache to a non-static
field in order to allow per converter/encoder/decoder
configuration.

Closes gh-30870
2023-08-04 11:25:40 +02:00
Brian Clozel e83793ba7f Batch SSE events writes when possible
Prior to this commit, the `SseEventBuilder` would be used to create SSE
events and write them to the connection using the `ResponseBodyEmitter`.
This would send each data item one by one, effectively writing and
flushing to the network for each. Since multiple data lines are prepared
by the `SseEventBuilder`, a typical write of an SSE event performs
multiple flushes operations.

This commit adds a method on `ResponseBodyEmitter` to perform batch
writes (given a `Set<DataWithMediaType>`) and only flush once all
elements of the set have been written.
This also applies in case of early writes, where now all buffered
elements are written then flushed altogether.

Fixes gh-30912
2023-08-04 10:31:43 +02:00
Juergen Hoeller 18966d048c Consistent equals/hashCode style (and related polishing) 2023-08-04 02:39:31 +02:00
Juergen Hoeller 7e6612a920 Sort multiple @Autowired methods on same bean class via ASM
Closes gh-30359
2023-08-04 00:47:18 +02:00
Juergen Hoeller 9333ed22f6 Avoid repeated FactoryBean targetType check
See gh-30987
2023-08-04 00:47:04 +02:00
Juergen Hoeller 4b6fabbd2f Polishing 2023-08-03 18:10:13 +02:00
Juergen Hoeller cba2b6eaf4 Check FactoryBean targetType for generic type as well
Closes gh-30987
2023-08-03 18:10:07 +02:00
Sam Brannen 34747baed0 Fix broken links to AOT sections 2023-08-03 11:27:16 +03:00
Sam Brannen 7c5b2db5bf Suppress warnings in tests 2023-08-02 11:09:56 +03:00
Sam Brannen 2e07a72119 Delete duplicate DummyFactory 2023-08-02 11:09:15 +03:00
Sam Brannen 9ba5622efd Update outdated Javadoc for PathPatternParser.defaultInstance
Spring Framework 6.0 changed the default value of
matchOptionalTrailingSeparator from true to false.

Closes gh-30976
2023-08-02 10:35:00 +03:00
Sam Brannen 3ff81a47c9 Polish PathPatternParser 2023-08-02 10:33:34 +03:00
Sam Brannen dcec61ab7a Remove obsolete dependency on picocli
The dependency on picocli was removed from the code in commit 019785a72e.

This commit removes the dependency management from the build.

See gh-28825
2023-08-02 09:54:32 +03:00
Philippe Marschall 4922e0e439 Give spring-core access to org.jboss.vfs for VfsUtils support on WildFly
This commit gives spring-core access to the org.jboss.vfs module to make
VfsUtils work out of the box on WildFly 28+.

Closes gh-30973
2023-08-02 09:45:28 +03:00
Juergen Hoeller 7adacd5ce5 Upgrade to Netty 4.1.96 2023-08-02 01:34:18 +02:00
Juergen Hoeller 08d89f7aac Avoid Aalto XML parser override 2023-08-02 01:32:29 +02:00
Juergen Hoeller d250a5155a Consistent dependency declarations 2023-08-02 00:56:50 +02:00
Juergen Hoeller 52176edcbf Polishing 2023-08-02 00:06:49 +02:00
Juergen Hoeller ae279eaced Polishing 2023-08-01 23:52:48 +02:00
Juergen Hoeller 18e72d5c01 Always use given fallback producer in case of TypeBootstrapContext
Closes gh-30924
2023-08-01 23:52:33 +02:00
Sam Brannen 148f5c459e Update copyright headers 2023-08-01 14:54:09 +03:00
Juergen Hoeller abbea39855 Polishing 2023-07-27 21:47:54 +02:00
Juergen Hoeller 6fd40a8f60 Upgrade to Caffeine 3.1.7 and H2 2.2.220 2023-07-26 14:15:58 +02:00
Juergen Hoeller 333249e7b0 Polishing 2023-07-26 14:07:08 +02:00
Juergen Hoeller 2573ba4a50 Polishing 2023-07-26 12:07:11 +02:00
Juergen Hoeller bbde68c49e Polishing 2023-07-25 19:12:07 +02:00
Juergen Hoeller fdf1418dfb Polishing 2023-07-24 11:21:13 +02:00
Juergen Hoeller 5bcf5c6f7c Clarify DataAccessException/ScriptException declarations for R2DBC
Closes gh-30932
2023-07-24 11:21:07 +02:00
Juergen Hoeller 87d4afda81 Upgrade to Netty 4.1.95 2023-07-22 00:31:19 +02:00
Juergen Hoeller 3a9e0ea8a7 Polishing 2023-07-22 00:31:09 +02:00
Juergen Hoeller 4ce1ac0dcb Polishing 2023-07-21 20:36:43 +02:00
Juergen Hoeller 8cc6dd629a Polishing 2023-07-19 22:58:27 +02:00
Juergen Hoeller 391d7f2c6a Polishing 2023-07-19 22:47:20 +02:00
Juergen Hoeller c64a322e19 Polishing 2023-07-19 01:25:20 +02:00
Juergen Hoeller 2f33e77ab4 Consistent equals/hashCode style (and related polishing) 2023-07-19 00:35:19 +02:00
Juergen Hoeller bbcc788f60 Decouple exception messages for sync=true from @Cacheable 2023-07-18 22:02:09 +02:00
Juergen Hoeller 038dda97f8 Document EntityManager injection via constructors/@Autowired
Closes gh-15076
2023-07-18 22:01:57 +02:00
Juergen Hoeller 1ac0549881 Polishing 2023-07-18 12:55:32 +02:00
Juergen Hoeller 616f728afa MethodIntrospector handles overriding bridge method correctly
Closes gh-30906
2023-07-18 12:54:59 +02:00
Juergen Hoeller 161a717639 Avoid synchronization for shortcut re-resolution
See gh-30883
2023-07-16 16:22:17 +02:00
Sam Brannen 28e63e9279 Polishing 2023-07-16 14:35:22 +02:00
Sam Brannen 11de70ed08 Update Javadoc for SimpleTriggerFactoryBean.setMisfireInstructionName()
org.quartz.Trigger#MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY is also
supported.
2023-07-16 14:34:42 +02:00
Sam Brannen 9283fd2162 Update Javadoc for CronTriggerFactoryBean.setMisfireInstructionName()
org.quartz.Trigger#MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY is also
supported.
2023-07-16 14:05:24 +02:00
Sam Brannen 544f594592 Update copyright headers 2023-07-15 18:23:37 +02:00
jongwooo c21a8aa8b0 Wrap ternary operator within parentheses as outlined in Code Style
Closes gh-30358
2023-07-15 18:04:01 +02:00
Sam Brannen 30d6ec3398 Update copyright headers 2023-07-15 16:01:15 +02:00
Johnny Lim ab83972c3e Add missing @Nullable annotations in AbstractResourceResolver subclasses
See gh-30601
Closes gh-30893
2023-07-15 16:00:32 +02:00
Sam Brannen 9e3f3bee71 Consistently throw TestContextAotException in TestContextAotGenerator
See gh-30898
2023-07-15 14:55:26 +02:00
Sam Brannen 2ba9939bd8 Revise changes to DefaultGenerationContext and GeneratedClasses
In order to reduce the surface area of published APIs in the affected
classes, this commit:

- Reverts the changes made to GeneratedClasses in c354b1014d.

- Reverts the changes made to DefaultGenerationContext in a28ec3a0a8.

- Makes the DefaultGenerationContext(DefaultGenerationContext, String)
  constructor protected.

- Reworks the internals of TestContextGenerationContext to align with
  the above changes.

See gh-30861
Closes gh-30895
Closes gh-30897
2023-07-15 14:43:39 +02:00
Juergen Hoeller 3a278cc66d Polishing 2023-07-15 14:20:00 +02:00
Juergen Hoeller 6183f06846 Cache DependencyDescriptor per autowired constructor argument
Aligned with shortcut handling in AutowiredAnnotationBeanPostProcessor.
Includes minor MethodInvoker optimization for pre-resolved targetClass.

Closes gh-30883
2023-07-15 14:17:52 +02:00
Sam Brannen a34f9fa66c Update copyright headers 2023-07-15 13:10:46 +02:00
Sam Brannen f5db8bd1e8 Retain existing feature name as prefix in test AOT processing
Prior to this commit, test AOT processing failed if a feature name for
generated class names was used for more than one ApplicationContext.

For example, when generating code for org.example.MessageService with a
"Management" feature name, the "BeanDefinitions" class was named as
follows (without a uniquely identifying TestContext###_ feature name
prefix).

org/example/MessageService__ManagementBeanDefinitions.java

When another attempt was made to generate code for the MessageService
using the same "Management" feature name, a FileAlreadyExistsException
was thrown denoting that the class/file name was already in use.

To avoid such naming collisions, this commit introduces a
TestContextGenerationContext which provides a custom implementation of
withName(String) that prepends an existing feature name (if present) to
a new feature name, thereby treating any existing feature name as a
prefix to a new, nested feature name.

Consequently, code generation for the above example now results in
unique class/file names like the following (which retain the uniquely
identifying TestContext###_ prefixes).

org/example/MessageService__TestContext002_ManagementBeanDefinitions.java
org/example/MessageService__TestContext003_ManagementBeanDefinitions.java

Closes gh-30861
2023-07-15 12:02:11 +02:00
Sam Brannen 317c6fbec2 Introduce failOnError flag in TestContextAotGenerator
This commit introduces a `failOnError` flag in TestContextAotGenerator.
When set to `true`, any error encountered during AOT processing will
result in an exception that fails the overall process. When set to
`false` (the default), the previous behavior remains unchanged: a DEBUG
or WARN message will be logged, and processing will continue.

This feature is currently only used for internal testing.

See gh-30861
Closes gh-30898
2023-07-15 12:02:04 +02:00
Sam Brannen c354b1014d Make GeneratedClasses#withFeatureNamePrefix(String) public
See gh-30861
Closes gh-30897
2023-07-15 12:01:56 +02:00
Sam Brannen a28ec3a0a8 Make DefaultGenerationContext(<classes>,<files>,<hints>) constructor protected
See gh-30861
Closes gh-30895
2023-07-15 12:01:49 +02:00
Juergen Hoeller 6ad647d7ce Explicit hints for @PostConstruct methods (preventing deadlocks)
Closes gh-25074
2023-07-14 16:43:23 +02:00
Juergen Hoeller 384246c360 Polishing 2023-07-14 14:37:34 +02:00
Juergen Hoeller e30391661d Document repeatable annotation semantics for @Scheduled
Closes gh-23959
2023-07-14 14:37:28 +02:00
Juergen Hoeller 75f5dac16b Polishing 2023-07-14 12:16:37 +02:00
Spring Builds bddd3feb83 Next development version (v6.0.12-SNAPSHOT) 2023-07-13 08:37:04 +00:00
477 changed files with 5477 additions and 4065 deletions
+1 -2
View File
@@ -21,8 +21,7 @@ derby.log
/build
buildSrc/build
/spring-*/build
/framework-bom/build
/framework-docs/build
/framework-*/build
/integration-tests/build
/src/asciidoc/build
spring-test/test-output/
+2 -2
View File
@@ -75,7 +75,7 @@ configure([rootProject] + javaProjects) { project ->
}
checkstyle {
toolVersion = "10.12.1"
toolVersion = "10.12.3"
configDirectory.set(rootProject.file("src/checkstyle"))
}
@@ -106,7 +106,7 @@ configure([rootProject] + javaProjects) { project ->
// JSR-305 only used for non-required meta-annotations
compileOnly("com.google.code.findbugs:jsr305")
testCompileOnly("com.google.code.findbugs:jsr305")
checkstyle("io.spring.javaformat:spring-javaformat-checkstyle:0.0.38")
checkstyle("io.spring.javaformat:spring-javaformat-checkstyle:0.0.39")
}
ext.javadocLinks = [
+1 -1
View File
@@ -1,4 +1,4 @@
FROM ubuntu:jammy-20230425
FROM ubuntu:jammy-20230816
ADD setup.sh /setup.sh
ADD get-jdk-url.sh /get-jdk-url.sh
@@ -176,7 +176,7 @@ Kotlin::
@AfterReturning(
pointcut = "execution(* com.xyz.dao.*.*(..))",
returning = "retVal")
fun doAccessCheck(retVal: Any) {
fun doAccessCheck(retVal: Any?) {
// ...
}
}
@@ -448,7 +448,7 @@ Kotlin::
class AroundExample {
@Around("execution(* com.xyz..service.*.*(..))")
fun doBasicProfiling(pjp: ProceedingJoinPoint): Any {
fun doBasicProfiling(pjp: ProceedingJoinPoint): Any? {
// start stopwatch
val retVal = pjp.proceed()
// stop stopwatch
@@ -893,7 +893,7 @@ Kotlin::
"com.xyz.CommonPointcuts.inDataAccessLayer() && " +
"args(accountHolderNamePattern)") // <1>
fun preProcessQueryPattern(pjp: ProceedingJoinPoint,
accountHolderNamePattern: String): Any {
accountHolderNamePattern: String): Any? {
val newPattern = preProcess(accountHolderNamePattern)
return pjp.proceed(arrayOf<Any>(newPattern))
}
@@ -85,7 +85,7 @@ Kotlin::
}
@Around("com.xyz.CommonPointcuts.businessService()") // <1>
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any {
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any? {
var numAttempts = 0
var lockFailureException: PessimisticLockingFailureException
do {
@@ -173,7 +173,7 @@ Kotlin::
----
@Around("execution(* com.xyz..service.*.*(..)) && " +
"@annotation(com.xyz.service.Idempotent)")
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any {
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any? {
// ...
}
----
@@ -435,7 +435,7 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
----
fun doBasicProfiling(pjp: ProceedingJoinPoint): Any {
fun doBasicProfiling(pjp: ProceedingJoinPoint): Any? {
// start stopwatch
val retVal = pjp.proceed()
// stop stopwatch
@@ -554,7 +554,7 @@ Kotlin::
class SimpleProfiler {
fun profile(call: ProceedingJoinPoint, name: String, age: Int): Any {
fun profile(call: ProceedingJoinPoint, name: String, age: Int): Any? {
val clock = StopWatch("Profiling for '$name' and '$age'")
try {
clock.start(call.toShortString())
@@ -890,7 +890,7 @@ Kotlin::
this.order = order
}
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any {
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any? {
var numAttempts = 0
var lockFailureException: PessimisticLockingFailureException
do {
@@ -493,7 +493,7 @@ Kotlin::
class ProfilingAspect {
@Around("methodsToBeProfiled()")
fun profile(pjp: ProceedingJoinPoint): Any {
fun profile(pjp: ProceedingJoinPoint): Any? {
val sw = StopWatch(javaClass.simpleName)
try {
sw.start(pjp.getSignature().getName())
@@ -828,13 +828,6 @@ The following table summarizes various `LoadTimeWeaver` implementations:
| Running in Red Hat's https://www.jboss.org/jbossas/[JBoss AS] or https://www.wildfly.org/[WildFly]
| `JBossLoadTimeWeaver`
| Running in IBM's https://www-01.ibm.com/software/webservers/appserv/was/[WebSphere]
| `WebSphereLoadTimeWeaver`
| Running in Oracle's
https://www.oracle.com/technetwork/middleware/weblogic/overview/index-085209.html[WebLogic]
| `WebLogicLoadTimeWeaver`
| JVM started with Spring `InstrumentationSavingAgent`
(`java -javaagent:path/to/spring-instrument.jar`)
| `InstrumentationLoadTimeWeaver`
@@ -949,11 +942,11 @@ when you use Spring's LTW support in environments such as application servers an
containers.
[[aop-aj-ltw-environments-tomcat-jboss-etc]]
==== Tomcat, JBoss, WebSphere, WebLogic
==== Tomcat, JBoss, WildFly
Tomcat, JBoss/WildFly, IBM WebSphere Application Server and Oracle WebLogic Server all
provide a general app `ClassLoader` that is capable of local instrumentation. Spring's
native LTW may leverage those ClassLoader implementations to provide AspectJ weaving.
Tomcat and JBoss/WildFly provide a general app `ClassLoader` that is capable of local
instrumentation. Spring's native LTW may leverage those ClassLoader implementations
to provide AspectJ weaving.
You can simply enable load-time weaving, as xref:core/aop/using-aspectj.adoc[described earlier].
Specifically, you do not need to modify the JVM launch script to add
`-javaagent:path/to/spring-instrument.jar`.
@@ -364,8 +364,6 @@ ignored if it cannot be autowired. This allows properties to be assigned default
that can be optionally overridden via dependency injection.
====
[[beans-autowired-annotation-constructor-resolution]]
Injected constructor and factory method arguments are a special case since the `required`
attribute in `@Autowired` has a somewhat different meaning due to Spring's constructor
@@ -22,10 +22,11 @@ additional metadata formats.
In most application scenarios, explicit user code is not required to instantiate one or
more instances of a Spring IoC container. For example, in a web application scenario, a
simple eight (or so) lines of boilerplate web descriptor XML in the `web.xml` file
of the application typically suffices (see xref:core/beans/context-introduction.adoc#context-create[Convenient ApplicationContext Instantiation for Web Applications]). If you use the
https://spring.io/tools[Spring Tools for Eclipse] (an Eclipse-powered development
environment), you can easily create this boilerplate configuration with a few mouse clicks or
keystrokes.
of the application typically suffices (see
xref:core/beans/context-introduction.adoc#context-create[Convenient ApplicationContext Instantiation for Web Applications]).
If you use the https://spring.io/tools[Spring Tools for Eclipse] (an Eclipse-powered
development environment), you can easily create this boilerplate configuration with a
few mouse clicks or keystrokes.
The following diagram shows a high-level view of how Spring works. Your application classes
are combined with configuration metadata so that, after the `ApplicationContext` is
@@ -139,9 +140,11 @@ Kotlin::
[NOTE]
====
After you learn about Spring's IoC container, you may want to know more about Spring's
`Resource` abstraction (as described in xref:web/webflux-webclient/client-builder.adoc#webflux-client-builder-reactor-resources[Resources]), which provides a convenient
mechanism for reading an InputStream from locations defined in a URI syntax. In particular,
`Resource` paths are used to construct applications contexts, as described in xref:core/resources.adoc#resources-app-ctx[Application Contexts and Resource Paths].
`Resource` abstraction (as described in
xref:web/webflux-webclient/client-builder.adoc#webflux-client-builder-reactor-resources[Resources])
which provides a convenient mechanism for reading an InputStream from locations defined
in a URI syntax. In particular, `Resource` paths are used to construct applications contexts,
as described in xref:core/resources.adoc#resources-app-ctx[Application Contexts and Resource Paths].
====
The following example shows the service layer objects `(services.xml)` configuration file:
@@ -208,9 +211,9 @@ XML configuration file represents a logical layer or module in your architecture
You can use the application context constructor to load bean definitions from all these
XML fragments. This constructor takes multiple `Resource` locations, as was shown in the
xref:core/beans/basics.adoc#beans-factory-instantiation[previous section]. Alternatively, use one or more
occurrences of the `<import/>` element to load bean definitions from another file or
files. The following example shows how to do so:
xref:core/beans/basics.adoc#beans-factory-instantiation[previous section]. Alternatively,
use one or more occurrences of the `<import/>` element to load bean definitions from
another file or files. The following example shows how to do so:
[source,xml,indent=0,subs="verbatim,quotes"]
----
@@ -484,13 +484,14 @@ custom event (`BlockedListEvent` in the preceding example). This means that the
You can register as many event listeners as you wish, but note that, by default, event
listeners receive events synchronously. This means that the `publishEvent()` method
blocks until all listeners have finished processing the event. One advantage of this
synchronous and single-threaded approach is that, when a listener receives an event, it
operates inside the transaction context of the publisher if a transaction context is
available. If another strategy for event publication becomes necessary, see the javadoc
for Spring's
synchronous and single-threaded approach is that, when a listener receives an event,
it operates inside the transaction context of the publisher if a transaction context
is available. If another strategy for event publication becomes necessary, e.g.
asynchronous event processing by default, see the javadoc for Spring's
{api-spring-framework}/context/event/ApplicationEventMulticaster.html[`ApplicationEventMulticaster`] interface
and {api-spring-framework}/context/event/SimpleApplicationEventMulticaster.html[`SimpleApplicationEventMulticaster`]
implementation for configuration options.
implementation for configuration options which can be applied to a custom
"applicationEventMulticaster" bean definition.
The following example shows the bean definitions used to register and configure each of
the classes above:
@@ -510,6 +511,12 @@ the classes above:
<bean id="blockedListNotifier" class="example.BlockedListNotifier">
<property name="notificationAddress" value="blockedlist@example.org"/>
</bean>
<!-- optional: a custom ApplicationEventMulticaster definition -->
<bean id="applicationEventMulticaster" class="org.springframework.context.event.SimpleApplicationEventMulticaster">
<property name="taskExecutor" ref="..."/>
<property name="errorHandler" ref="..."/>
</bean>
----
Putting it all together, when the `sendEmail()` method of the `emailService` bean is
@@ -849,6 +856,23 @@ Kotlin::
TIP: This works not only for `ApplicationEvent` but any arbitrary object that you send as
an event.
Finally, as with classic `ApplicationListener` implementations, the actual multicasting
happens via a context-wide `ApplicationEventMulticaster` at runtime. By default, this is a
`SimpleApplicationEventMulticaster` with synchronous event publication in the caller thread.
This can be replaced/customized through an "applicationEventMulticaster" bean definition,
e.g. for processing all events asynchronously and/or for handling listener exceptions:
[source,java,indent=0,subs="verbatim,quotes"]
----
@Bean
ApplicationEventMulticaster applicationEventMulticaster() {
SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
multicaster.setTaskExecutor(...);
multicaster.setErrorHandler(...);
return multicaster;
}
----
[[context-functionality-resources]]
@@ -910,7 +934,7 @@ Java::
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
// create a startup step and start recording
StartupStep scanPackages = this.getApplicationStartup().start("spring.context.base-packages.scan");
StartupStep scanPackages = getApplicationStartup().start("spring.context.base-packages.scan");
// add tagging information to the current step
scanPackages.tag("packages", () -> Arrays.toString(basePackages));
// perform the actual phase we're instrumenting
@@ -924,7 +948,7 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
// create a startup step and start recording
val scanPackages = this.getApplicationStartup().start("spring.context.base-packages.scan")
val scanPackages = getApplicationStartup().start("spring.context.base-packages.scan")
// add tagging information to the current step
scanPackages.tag("packages", () -> Arrays.toString(basePackages))
// perform the actual phase we're instrumenting
@@ -309,7 +309,7 @@ how to configure such a bean:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<!-- the factory bean, which contains a method called createInstance() -->
<!-- the factory bean, which contains a method called createClientServiceInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
@@ -23,9 +23,8 @@ You can set this property only if the `BeanPostProcessor` implements the `Ordere
interface. If you write your own `BeanPostProcessor`, you should consider implementing
the `Ordered` interface, too. For further details, see the javadoc of the
{api-spring-framework}/beans/factory/config/BeanPostProcessor.html[`BeanPostProcessor`]
and {api-spring-framework}/core/Ordered.html[`Ordered`] interfaces. See also the note
on xref:core/beans/factory-extension.adoc#beans-factory-programmatically-registering-beanpostprocessors[programmatic registration of `BeanPostProcessor` instances]
.
and {api-spring-framework}/core/Ordered.html[`Ordered`] interfaces. See also the note on
xref:core/beans/factory-extension.adoc#beans-factory-programmatically-registering-beanpostprocessors[programmatic registration of `BeanPostProcessor` instances].
[NOTE]
====
@@ -280,11 +279,12 @@ and {api-spring-framework}/core/Ordered.html[`Ordered`] interfaces for more deta
====
If you want to change the actual bean instances (that is, the objects that are created
from the configuration metadata), then you instead need to use a `BeanPostProcessor`
(described earlier in xref:core/beans/factory-extension.adoc#beans-factory-extension-bpp[Customizing Beans by Using a `BeanPostProcessor`]). While it is technically possible
to work with bean instances within a `BeanFactoryPostProcessor` (for example, by using
`BeanFactory.getBean()`), doing so causes premature bean instantiation, violating the
standard container lifecycle. This may cause negative side effects, such as bypassing
bean post processing.
(described earlier in
xref:core/beans/factory-extension.adoc#beans-factory-extension-bpp[Customizing Beans by Using a `BeanPostProcessor`]).
While it is technically possible to work with bean instances within a `BeanFactoryPostProcessor`
(for example, by using `BeanFactory.getBean()`), doing so causes premature bean instantiation,
violating the standard container lifecycle. This may cause negative side effects, such as
bypassing bean post processing.
Also, `BeanFactoryPostProcessor` instances are scoped per-container. This is only relevant
if you use container hierarchies. If you define a `BeanFactoryPostProcessor` in one
@@ -331,8 +331,7 @@ with placeholder values is defined:
<property name="locations" value="classpath:com/something/jdbc.properties"/>
</bean>
<bean id="dataSource" destroy-method="close"
class="org.apache.commons.dbcp.BasicDataSource">
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
@@ -433,8 +432,8 @@ This example file can be used with a container definition that contains a bean c
Compound property names are also supported, as long as every component of the path
except the final property being overridden is already non-null (presumably initialized
by the constructors). In the following example, the `sammy` property of the `bob` property of the `fred` property of the `tom` bean
is set to the scalar value `123`:
by the constructors). In the following example, the `sammy` property of the `bob`
property of the `fred` property of the `tom` bean is set to the scalar value `123`:
[literal,subs="verbatim,quotes"]
----
@@ -42,6 +42,7 @@ startup and shutdown process, as driven by the container's own lifecycle.
The lifecycle callback interfaces are described in this section.
[[beans-factory-lifecycle-initializingbean]]
=== Initialization Callbacks
@@ -132,6 +133,30 @@ Kotlin::
However, the first of the two preceding examples does not couple the code to Spring.
[NOTE]
====
Be aware that `@PostConstruct` and initialization methods in general are executed
within the container's singleton creation lock. The bean instance is only considered
as fully initialized and ready to be published to others after returning from the
`@PostConstruct` method. Such individual initialization methods are only meant
for validating the configuration state and possibly preparing some data structures
based on the given configuration but no further activity with external bean access.
Otherwise there is a risk for an initialization deadlock.
For a scenario where expensive post-initialization activity is to be triggered,
e.g. asynchronous database preparation steps, your bean should either implement
`SmartInitializingSingleton.afterSingletonsInstantiated()` or rely on the context
refresh event: implementing `ApplicationListener<ContextRefreshedEvent>` or
declaring its annotation equivalent `@EventListener(ContextRefreshedEvent.class)`.
Those variants come after all regular singleton initialization and therefore
outside of any singleton creation lock.
Alternatively, you may implement the `(Smart)Lifecycle` interface and integrate with
the container's overall lifecycle management, including an auto-startup mechanism,
a pre-destroy stop step, and potential stop/restart callbacks (see below).
====
[[beans-factory-lifecycle-disposablebean]]
=== Destruction Callbacks
@@ -155,7 +180,7 @@ xref:core/beans/java/bean-annotation.adoc#beans-java-lifecycle-callbacks[Receivi
[source,xml,indent=0,subs="verbatim,quotes"]
----
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
<bean id="exampleDestructionBean" class="examples.ExampleBean" destroy-method="cleanup"/>
----
[tabs]
@@ -189,7 +214,7 @@ The preceding definition has almost exactly the same effect as the following def
[source,xml,indent=0,subs="verbatim,quotes"]
----
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
<bean id="exampleDestructionBean" class="examples.AnotherExampleBean"/>
----
[tabs]
@@ -222,32 +247,45 @@ Kotlin::
However, the first of the two preceding definitions does not couple the code to Spring.
TIP: You can assign the `destroy-method` attribute of a `<bean>` element a special
`(inferred)` value, which instructs Spring to automatically detect a public `close` or
`shutdown` method on the specific bean class. (Any class that implements
`java.lang.AutoCloseable` or `java.io.Closeable` would therefore match.) You can also set
this special `(inferred)` value on the `default-destroy-method` attribute of a
`<beans>` element to apply this behavior to an entire set of beans (see
xref:core/beans/factory-nature.adoc#beans-factory-lifecycle-default-init-destroy-methods[Default Initialization and Destroy Methods]). Note that this is the
default behavior with Java configuration.
Note that Spring also supports inference of destroy methods, detecting a public `close` or
`shutdown` method. This is the default behavior for `@Bean` methods in Java configuration
classes and automatically matches `java.lang.AutoCloseable` or `java.io.Closeable`
implementations, not coupling the destruction logic to Spring either.
TIP: For destroy method inference with XML, you may assign the `destroy-method` attribute
of a `<bean>` element a special `(inferred)` value, which instructs Spring to automatically
detect a public `close` or `shutdown` method on the bean class for a specific bean definition.
You can also set this special `(inferred)` value on the `default-destroy-method` attribute
of a `<beans>` element to apply this behavior to an entire set of bean definitions (see
xref:core/beans/factory-nature.adoc#beans-factory-lifecycle-default-init-destroy-methods[Default Initialization and Destroy Methods]).
[NOTE]
====
For extended shutdown phases, you may implement the `Lifecycle` interface and receive
an early stop signal before the destroy methods of any singleton beans are called.
You may also implement `SmartLifecycle` for a time-bound stop step where the container
will wait for all such stop processing to complete before moving on to destroy methods.
====
[[beans-factory-lifecycle-default-init-destroy-methods]]
=== Default Initialization and Destroy Methods
When you write initialization and destroy method callbacks that do not use the
Spring-specific `InitializingBean` and `DisposableBean` callback interfaces, you
typically write methods with names such as `init()`, `initialize()`, `dispose()`, and so
on. Ideally, the names of such lifecycle callback methods are standardized across a
project so that all developers use the same method names and ensure consistency.
typically write methods with names such as `init()`, `initialize()`, `dispose()`,
and so on. Ideally, the names of such lifecycle callback methods are standardized across
a project so that all developers use the same method names and ensure consistency.
You can configure the Spring container to "`look`" for named initialization and destroy
callback method names on every bean. This means that you, as an application
developer, can write your application classes and use an initialization callback called
`init()`, without having to configure an `init-method="init"` attribute with each bean
definition. The Spring IoC container calls that method when the bean is created (and in
accordance with the standard lifecycle callback contract xref:core/beans/factory-nature.adoc#beans-factory-lifecycle[described previously]
). This feature also enforces a consistent naming convention for
initialization and destroy method callbacks.
callback method names on every bean. This means that you, as an application developer,
can write your application classes and use an initialization callback called `init()`,
without having to configure an `init-method="init"` attribute with each bean definition.
The Spring IoC container calls that method when the bean is created (and in accordance
with the standard lifecycle callback contract xref:core/beans/factory-nature.adoc#beans-factory-lifecycle[described previously]).
This feature also enforces a consistent naming convention for initialization and
destroy method callbacks.
Suppose that your initialization callback methods are named `init()` and your destroy
callback methods are named `destroy()`. Your class then resembles the class in the
@@ -407,14 +445,15 @@ and closed.
[TIP]
====
Note that the regular `org.springframework.context.Lifecycle` interface is a plain
contract for explicit start and stop notifications and does not imply auto-startup at context
refresh time. For fine-grained control over auto-startup of a specific bean (including startup phases),
consider implementing `org.springframework.context.SmartLifecycle` instead.
contract for explicit start and stop notifications and does not imply auto-startup
at context refresh time. For fine-grained control over auto-startup and for graceful
stopping of a specific bean (including startup and stop phases), consider implementing
the extended `org.springframework.context.SmartLifecycle` interface instead.
Also, please note that stop notifications are not guaranteed to come before destruction.
On regular shutdown, all `Lifecycle` beans first receive a stop notification before
the general destruction callbacks are being propagated. However, on hot refresh during a
context's lifetime or on stopped refresh attempts, only destroy methods are called.
the general destruction callbacks are being propagated. However, on hot refresh during
a context's lifetime or on stopped refresh attempts, only destroy methods are called.
====
The order of startup and shutdown invocations can be important. If a "`depends-on`"
@@ -125,8 +125,8 @@ objects regardless of scope, in the case of prototypes, configured destruction
lifecycle callbacks are not called. The client code must clean up prototype-scoped
objects and release expensive resources that the prototype beans hold. To get
the Spring container to release resources held by prototype-scoped beans, try using a
custom xref:core/beans/factory-extension.adoc#beans-factory-extension-bpp[bean post-processor], which holds a reference to
beans that need to be cleaned up.
custom xref:core/beans/factory-extension.adoc#beans-factory-extension-bpp[bean post-processor]
which holds a reference to beans that need to be cleaned up.
In some respects, the Spring container's role in regard to a prototype-scoped bean is a
replacement for the Java `new` operator. All lifecycle management past that point must
@@ -449,12 +449,13 @@ understand the "`why`" as well as the "`how`" behind it:
----
<1> The line that defines the proxy.
To create such a proxy, you insert a child `<aop:scoped-proxy/>` element into a
scoped bean definition (see
xref:core/beans/factory-scopes.adoc#beans-factory-scopes-other-injection-proxies[Choosing the Type of Proxy to Create]
and xref:core/appendix/xsd-schemas.adoc[XML Schema-based configuration]).
To create such a proxy, you insert a child `<aop:scoped-proxy/>` element into a scoped
bean definition (see xref:core/beans/factory-scopes.adoc#beans-factory-scopes-other-injection-proxies[Choosing the Type of Proxy to Create] and
xref:core/appendix/xsd-schemas.adoc[XML Schema-based configuration]).
Why do definitions of beans scoped at the `request`, `session` and custom-scope
levels require the `<aop:scoped-proxy/>` element?
levels require the `<aop:scoped-proxy/>` element in common scenarios?
Consider the following singleton bean definition and contrast it with
what you need to define for the aforementioned scopes (note that the following
`userPreferences` bean definition as it stands is incomplete):
@@ -180,9 +180,10 @@ Kotlin::
----
======
The resolution mechanism is pretty much identical to constructor-based dependency
injection. See xref:core/beans/dependencies/factory-collaborators.adoc#beans-constructor-injection[the relevant section] for more details.
injection. See
xref:core/beans/dependencies/factory-collaborators.adoc#beans-constructor-injection[the relevant section]
for more details.
[[beans-java-lifecycle-callbacks]]
@@ -317,8 +318,9 @@ type, making it harder to use for cross-reference calls in other `@Bean` methods
intend to refer to the provided resource here).
=====
In the case of `BeanOne` from the example above the preceding note, it would be equally valid to call the `init()`
method directly during construction, as the following example shows:
In the case of `BeanOne` from the example above the preceding note, it would be
equally valid to call the `init()` method directly during construction, as the
following example shows:
[tabs]
======
@@ -506,10 +508,10 @@ Kotlin::
[[beans-java-bean-aliasing]]
== Bean Aliasing
As discussed in xref:core/beans/definition.adoc#beans-beanname[Naming Beans], it is sometimes desirable to give a single bean
multiple names, otherwise known as bean aliasing. The `name` attribute of the `@Bean`
annotation accepts a String array for this purpose. The following example shows how to set
a number of aliases for a bean:
As discussed in xref:core/beans/definition.adoc#beans-beanname[Naming Beans], it is
sometimes desirable to give a single bean multiple names, otherwise known as bean aliasing.
The `name` attribute of the `@Bean` annotation accepts a String array for this purpose.
The following example shows how to set a number of aliases for a bean:
[tabs]
======
@@ -38,5 +38,15 @@ Kotlin::
----
======
[NOTE]
====
If your application or framework manages its own `EvaluationContext`, you may need to
manually configure a `StandardTypeLocator` with a specific `ClassLoader` to ensure that
the SpEL expression parser is able to reliably locate user types.
For example, the `StandardBeanExpressionResolver` in the `spring-context` module
configures a `StandardTypeLocator` using the bean `ClassLoader` of the corresponding
`BeanFactory`.
====
@@ -501,8 +501,8 @@ extend from it, your sub-class inherits a `setDataSource(..)` method from the
Regardless of which of the above template initialization styles you choose to use (or
not), it is seldom necessary to create a new instance of a `JdbcTemplate` class each
time you want to run SQL. Once configured, a `JdbcTemplate` instance is thread-safe.
If your application accesses multiple
databases, you may want multiple `JdbcTemplate` instances, which requires multiple `DataSources` and, subsequently, multiple differently
If your application accesses multiple databases, you may want multiple `JdbcTemplate`
instances, which requires multiple `DataSources` and, subsequently, multiple differently
configured `JdbcTemplate` instances.
@@ -531,11 +531,8 @@ Java::
}
public int countOfActorsByFirstName(String firstName) {
String sql = "select count(*) from T_ACTOR where first_name = :first_name";
String sql = "select count(*) from t_actor where first_name = :first_name";
SqlParameterSource namedParameters = new MapSqlParameterSource("first_name", firstName);
return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
}
----
@@ -547,7 +544,7 @@ Kotlin::
private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)
fun countOfActorsByFirstName(firstName: String): Int {
val sql = "select count(*) from T_ACTOR where first_name = :first_name"
val sql = "select count(*) from t_actor where first_name = :first_name"
val namedParameters = MapSqlParameterSource("first_name", firstName)
return namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Int::class.java)!!
}
@@ -579,12 +576,9 @@ Java::
}
public int countOfActorsByFirstName(String firstName) {
String sql = "select count(*) from T_ACTOR where first_name = :first_name";
String sql = "select count(*) from t_actor where first_name = :first_name";
Map<String, String> namedParameters = Collections.singletonMap("first_name", firstName);
return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
}
----
@@ -596,7 +590,7 @@ Kotlin::
private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)
fun countOfActorsByFirstName(firstName: String): Int {
val sql = "select count(*) from T_ACTOR where first_name = :first_name"
val sql = "select count(*) from t_actor where first_name = :first_name"
val namedParameters = mapOf("first_name" to firstName)
return namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Int::class.java)!!
}
@@ -644,7 +638,6 @@ Java::
}
// setters omitted...
}
----
@@ -673,12 +666,9 @@ Java::
}
public int countOfActors(Actor exampleActor) {
// notice how the named parameters match the properties of the above 'Actor' class
String sql = "select count(*) from T_ACTOR where first_name = :firstName and last_name = :lastName";
String sql = "select count(*) from t_actor where first_name = :firstName and last_name = :lastName";
SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(exampleActor);
return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
}
----
@@ -694,7 +684,7 @@ Kotlin::
fun countOfActors(exampleActor: Actor): Int {
// notice how the named parameters match the properties of the above 'Actor' class
val sql = "select count(*) from T_ACTOR where first_name = :firstName and last_name = :lastName"
val sql = "select count(*) from t_actor where first_name = :firstName and last_name = :lastName"
val namedParameters = BeanPropertySqlParameterSource(exampleActor)
return namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Int::class.java)!!
}
@@ -707,8 +697,8 @@ functionality that is present only in the `JdbcTemplate` class, you can use the
`getJdbcOperations()` method to access the wrapped `JdbcTemplate` through the
`JdbcOperations` interface.
See also xref:data-access/jdbc/core.adoc#jdbc-JdbcTemplate-idioms[`JdbcTemplate` Best Practices] for guidelines on using the
`NamedParameterJdbcTemplate` class in the context of an application.
See also xref:data-access/jdbc/core.adoc#jdbc-JdbcTemplate-idioms[`JdbcTemplate` Best Practices]
for guidelines on using the `NamedParameterJdbcTemplate` class in the context of an application.
[[jdbc-SQLExceptionTranslator]]
@@ -121,18 +121,17 @@ Kotlin::
<3> Using the method `setBlobAsBinaryStream` to pass in the contents of the BLOB.
======
[NOTE]
====
If you invoke the `setBlobAsBinaryStream`, `setClobAsAsciiStream`, or
`setClobAsCharacterStream` method on the `LobCreator` returned from
`DefaultLobHandler.getLobCreator()`, you can optionally specify a negative value for the
`contentLength` argument. If the specified content length is negative, the
`DefaultLobHandler.getLobCreator()`, you can optionally specify a negative value
for the `contentLength` argument. If the specified content length is negative, the
`DefaultLobHandler` uses the JDBC 4.0 variants of the set-stream methods without a
length parameter. Otherwise, it passes the specified length on to the driver.
See the documentation for the JDBC driver you use to verify that it supports streaming a
LOB without providing the content length.
See the documentation for the JDBC driver you use to verify that it supports streaming
a LOB without providing the content length.
====
Now it is time to read the LOB data from the database. Again, you use a `JdbcTemplate`
@@ -184,15 +183,15 @@ variable list of values. A typical example would be `select * from T_ACTOR where
JDBC standard. You cannot declare a variable number of placeholders. You need a number
of variations with the desired number of placeholders prepared, or you need to generate
the SQL string dynamically once you know how many placeholders are required. The named
parameter support provided in the `NamedParameterJdbcTemplate` and `JdbcTemplate` takes
the latter approach. You can pass in the values as a `java.util.List` of primitive objects. This
list is used to insert the required placeholders and pass in the values during
statement execution.
parameter support provided in the `NamedParameterJdbcTemplate` takes the latter approach.
You can pass in the values as a `java.util.List` (or any `Iterable`) of simple values.
This list is used to insert the required placeholders into the actual SQL statement
and pass in the values during statement execution.
NOTE: Be careful when passing in many values. The JDBC standard does not guarantee that you
can use more than 100 values for an `in` expression list. Various databases exceed this
number, but they usually have a hard limit for how many values are allowed. For example, Oracle's
limit is 1000.
NOTE: Be careful when passing in many values. The JDBC standard does not guarantee that
you can use more than 100 values for an `IN` expression list. Various databases exceed
this number, but they usually have a hard limit for how many values are allowed.
For example, Oracle's limit is 1000.
In addition to the primitive values in the value list, you can create a `java.util.List`
of object arrays. This list can support multiple expressions being defined for the `in`
@@ -88,12 +88,6 @@ You can use this option for full JPA capabilities in a Spring-based application
This includes web containers such as Tomcat, stand-alone applications, and
integration tests with sophisticated persistence requirements.
NOTE: If you want to specifically configure a Hibernate setup, an immediate alternative
is to set up a native Hibernate `LocalSessionFactoryBean` instead of a plain JPA
`LocalContainerEntityManagerFactoryBean`, letting it interact with JPA access code
as well as native Hibernate access code.
See xref:data-access/orm/jpa.adoc#orm-jpa-hibernate[Native Hibernate setup for JPA interaction] for details.
The `LocalContainerEntityManagerFactoryBean` gives full control over
`EntityManagerFactory` configuration and is appropriate for environments where
fine-grained customization is required. The `LocalContainerEntityManagerFactoryBean`
@@ -187,6 +181,7 @@ and automatic propagation of the weaver to all weaver-aware beans:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<context:load-time-weaver/>
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
...
</bean>
@@ -281,8 +276,8 @@ Spring Data JPA, make sure to set up deferred bootstrapping for its repositories
[[orm-jpa-dao]]
== Implementing DAOs Based on JPA: `EntityManagerFactory` and `EntityManager`
NOTE: Although `EntityManagerFactory` instances are thread-safe, `EntityManager` instances are
not. The injected JPA `EntityManager` behaves like an `EntityManager` fetched from an
NOTE: Although `EntityManagerFactory` instances are thread-safe, `EntityManager` instances
are not. The injected JPA `EntityManager` behaves like an `EntityManager` fetched from an
application server's JNDI environment, as defined by the JPA specification. It delegates
all calls to the current transactional `EntityManager`, if any. Otherwise, it falls back
to a newly created `EntityManager` per operation, in effect making its usage thread-safe.
@@ -290,8 +285,8 @@ to a newly created `EntityManager` per operation, in effect making its usage thr
It is possible to write code against the plain JPA without any Spring dependencies, by
using an injected `EntityManagerFactory` or `EntityManager`. Spring can understand the
`@PersistenceUnit` and `@PersistenceContext` annotations both at the field and the method level
if a `PersistenceAnnotationBeanPostProcessor` is enabled. The following example shows a plain JPA DAO implementation
that uses the `@PersistenceUnit` annotation:
if a `PersistenceAnnotationBeanPostProcessor` is enabled. The following example shows a plain
JPA DAO implementation that uses the `@PersistenceUnit` annotation:
[tabs]
======
@@ -384,9 +379,9 @@ Consider the following example:
----
The main problem with such a DAO is that it always creates a new `EntityManager` through
the factory. You can avoid this by requesting a transactional `EntityManager` (also
called a "`shared EntityManager`" because it is a shared, thread-safe proxy for the actual
transactional EntityManager) to be injected instead of the factory. The following example shows how to do so:
the factory. You can avoid this by requesting a transactional `EntityManager` (also called a
"`shared EntityManager`" because it is a shared, thread-safe proxy for the actual transactional
EntityManager) to be injected instead of the factory. The following example shows how to do so:
[tabs]
======
@@ -425,24 +420,24 @@ Kotlin::
----
======
The `@PersistenceContext` annotation has an optional attribute called `type`, which defaults to
`PersistenceContextType.TRANSACTION`. You can use this default to receive a shared
The `@PersistenceContext` annotation has an optional attribute called `type`, which defaults
to `PersistenceContextType.TRANSACTION`. You can use this default to receive a shared
`EntityManager` proxy. The alternative, `PersistenceContextType.EXTENDED`, is a completely
different affair. This results in a so-called extended `EntityManager`, which is not
thread-safe and, hence, must not be used in a concurrently accessed component, such as a
Spring-managed singleton bean. Extended `EntityManager` instances are only supposed to be used in
stateful components that, for example, reside in a session, with the lifecycle of the
Spring-managed singleton bean. Extended `EntityManager` instances are only supposed to be used
in stateful components that, for example, reside in a session, with the lifecycle of the
`EntityManager` not tied to a current transaction but rather being completely up to the
application.
.Method- and field-level Injection
****
You can apply annotations that indicate dependency injections (such as `@PersistenceUnit` and
`@PersistenceContext`) on field or methods inside a class -- hence the
expressions "`method-level injection`" and "`field-level injection`". Field-level
annotations are concise and easier to use while method-level annotations allow for further
processing of the injected dependency. In both cases, the member visibility (public,
protected, or private) does not matter.
You can apply annotations that indicate dependency injections (such as `@PersistenceUnit`
and `@PersistenceContext`) on field or methods inside a class -- hence the expressions
"`method-level injection`" and "`field-level injection`". Field-level annotations are
concise and easier to use while method-level annotations allow for further processing of the
injected dependency. In both cases, the member visibility (public, protected, or private)
does not matter.
What about class-level annotations?
@@ -451,21 +446,62 @@ injection.
****
The injected `EntityManager` is Spring-managed (aware of the ongoing transaction).
Even though the new DAO implementation uses method-level
injection of an `EntityManager` instead of an `EntityManagerFactory`, no change is
required in the application context XML, due to annotation usage.
Even though the new DAO implementation uses method-level injection of an `EntityManager`
instead of an `EntityManagerFactory`, no change is required in the bean definition
due to annotation usage.
The main advantage of this DAO style is that it depends only on the Java Persistence API.
No import of any Spring class is required. Moreover, as the JPA annotations are understood,
the injections are applied automatically by the Spring container. This is appealing from
a non-invasiveness perspective and can feel more natural to JPA developers.
[[orm-jpa-dao-autowired]]
=== Implementing DAOs Based on `@Autowired` (typically with constructor-based injection)
`@PersistenceUnit` and `@PersistenceContext` can only be declared on methods and fields.
What about providing JPA resources via constructors and other `@Autowired` injection points?
`EntityManagerFactory` can easily be injected via constructors and `@Autowired` fields/methods
as long as the target is defined as a bean, e.g. via `LocalContainerEntityManagerFactoryBean`.
The injection point matches the original `EntityManagerFactory` definition by type as-is.
However, an `@PersistenceContext`-style shared `EntityManager` reference is not available for
regular dependency injection out of the box. In order to make it available for type-based
matching as required by `@Autowired`, consider defining a `SharedEntityManagerBean` as a
companion for your `EntityManagerFactory` definition:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
...
</bean>
<bean id="em" class="org.springframework.orm.jpa.support.SharedEntityManagerBean">
<property name="entityManagerFactory" ref="emf"/>
</bean>
----
Alternatively, you may define an `@Bean` method based on `SharedEntityManagerCreator`:
[source,java,indent=0,subs="verbatim,quotes"]
----
@Bean("em")
public static EntityManager sharedEntityManager(EntityManagerFactory emf) {
return SharedEntityManagerCreator.createSharedEntityManager(emf);
}
----
In case of multiple persistence units, each `EntityManagerFactory` definition needs to be
accompanied by a corresponding `EntityManager` bean definition, ideally with qualifiers
that match with the distinct `EntityManagerFactory` definition in order to distinguish
the persistence units via `@Autowired @Qualifier("...")`.
[[orm-jpa-tx]]
== Spring-driven JPA transactions
== Spring-driven JPA Transactions
NOTE: We strongly encourage you to read xref:data-access/transaction/declarative.adoc[Declarative Transaction Management], if you have not
already done so, to get more detailed coverage of Spring's declarative transaction support.
NOTE: We strongly encourage you to read xref:data-access/transaction/declarative.adoc[Declarative Transaction Management],
if you have not already done so, to get more detailed coverage of Spring's declarative transaction support.
The recommended strategy for JPA is local transactions through JPA's native transaction
support. Spring's `JpaTransactionManager` provides many capabilities known from local
@@ -478,11 +514,6 @@ to JDBC access code that accesses the same `DataSource`, provided that the regis
Spring provides dialects for the EclipseLink and Hibernate JPA implementations.
See the xref:data-access/orm/jpa.adoc#orm-jpa-dialect[next section] for details on the `JpaDialect` mechanism.
NOTE: As an immediate alternative, Spring's native `HibernateTransactionManager` is capable
of interacting with JPA access code, adapting to several Hibernate specifics and providing
JDBC interaction. This makes particular sense in combination with `LocalSessionFactoryBean`
setup. See xref:data-access/orm/jpa.adoc#orm-jpa-hibernate[Native Hibernate Setup for JPA Interaction] for details.
[[orm-jpa-dialect]]
== Understanding `JpaDialect` and `JpaVendorAdapter`
@@ -11,11 +11,13 @@ specification effort to standardize access to SQL databases using reactive patte
The Spring Framework's R2DBC abstraction framework consists of two different packages:
* `core`: The `org.springframework.r2dbc.core` package contains the `DatabaseClient`
class plus a variety of related classes. See xref:data-access/r2dbc.adoc#r2dbc-core[Using the R2DBC Core Classes to Control Basic R2DBC Processing and Error Handling].
class plus a variety of related classes. See
xref:data-access/r2dbc.adoc#r2dbc-core[Using the R2DBC Core Classes to Control Basic R2DBC Processing and Error Handling].
* `connection`: The `org.springframework.r2dbc.connection` package contains a utility class
for easy `ConnectionFactory` access and various simple `ConnectionFactory` implementations
that you can use for testing and running unmodified R2DBC. See xref:data-access/r2dbc.adoc#r2dbc-connections[Controlling Database Connections].
that you can use for testing and running unmodified R2DBC. See
xref:data-access/r2dbc.adoc#r2dbc-connections[Controlling Database Connections].
[[r2dbc-core]]
@@ -31,6 +33,7 @@ including error handling. It includes the following topics:
* xref:data-access/r2dbc.adoc#r2dbc-DatabaseClient-filter[Statement Filters]
* xref:data-access/r2dbc.adoc#r2dbc-auto-generated-keys[Retrieving Auto-generated Keys]
[[r2dbc-DatabaseClient]]
=== Using `DatabaseClient`
@@ -43,8 +46,9 @@ SQL and extract results. The `DatabaseClient` class:
* Runs SQL queries
* Update statements and stored procedure calls
* Performs iteration over `Result` instances
* Catches R2DBC exceptions and translates them to the generic, more informative, exception
hierarchy defined in the `org.springframework.dao` package. (See xref:data-access/dao.adoc#dao-exceptions[Consistent Exception Hierarchy].)
* Catches R2DBC exceptions and translates them to the generic, more informative,
exception hierarchy defined in the `org.springframework.dao` package.
(See xref:data-access/dao.adoc#dao-exceptions[Consistent Exception Hierarchy].)
The client has a functional, fluent API using reactive types for declarative composition.
@@ -250,7 +254,6 @@ Kotlin::
----
======
[[r2dbc-DatabaseClient-mapping-null]]
.What about `null`?
****
@@ -315,10 +318,10 @@ The following example shows parameter binding for a query:
[source,java]
----
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
.bind("id", "joe")
.bind("name", "Joe")
.bind("age", 34);
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
.bind("id", "joe")
.bind("name", "Joe")
.bind("age", 34);
----
.R2DBC Native Bind Markers
@@ -327,7 +330,7 @@ R2DBC uses database-native bind markers that depend on the actual database vendo
As an example, Postgres uses indexed markers, such as `$1`, `$2`, `$n`.
Another example is SQL Server, which uses named bind markers prefixed with `@`.
This is different from JDBC, which requires `?` as bind markers.
This is different from JDBC which requires `?` as bind markers.
In JDBC, the actual drivers translate `?` bind markers to database-native
markers as part of their statement execution.
@@ -363,7 +366,7 @@ Java::
tuples.add(new Object[] {"Ann", 50});
client.sql("SELECT id, name, state FROM table WHERE (name, age) IN (:tuples)")
.bind("tuples", tuples);
.bind("tuples", tuples);
----
Kotlin::
@@ -375,7 +378,7 @@ Kotlin::
tuples.add(arrayOf("Ann", 50))
client.sql("SELECT id, name, state FROM table WHERE (name, age) IN (:tuples)")
.bind("tuples", tuples)
.bind("tuples", tuples)
----
======
@@ -390,7 +393,7 @@ Java::
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
client.sql("SELECT id, name, state FROM table WHERE age IN (:ages)")
.bind("ages", Arrays.asList(35, 50));
.bind("ages", Arrays.asList(35, 50));
----
Kotlin::
@@ -402,7 +405,7 @@ Kotlin::
tuples.add(arrayOf("Ann", 50))
client.sql("SELECT id, name, state FROM table WHERE age IN (:ages)")
.bind("tuples", arrayOf(35, 50))
.bind("tuples", arrayOf(35, 50))
----
======
@@ -429,9 +432,9 @@ Java::
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter((s, next) -> next.execute(s.returnGeneratedValues("id")))
.bind("name", …)
.bind("state", …);
.filter((s, next) -> next.execute(s.returnGeneratedValues("id")))
.bind("name", …)
.bind("state", …);
----
Kotlin::
@@ -439,9 +442,9 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter { s: Statement, next: ExecuteFunction -> next.execute(s.returnGeneratedValues("id")) }
.bind("name", …)
.bind("state", …)
.filter { s: Statement, next: ExecuteFunction -> next.execute(s.returnGeneratedValues("id")) }
.bind("name", …)
.bind("state", …)
----
======
@@ -455,10 +458,10 @@ Java::
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter(statement -> s.returnGeneratedValues("id"));
.filter(statement -> s.returnGeneratedValues("id"));
client.sql("SELECT id, name, state FROM table")
.filter(statement -> s.fetchSize(25));
.filter(statement -> s.fetchSize(25));
----
Kotlin::
@@ -466,10 +469,10 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter { statement -> s.returnGeneratedValues("id") }
.filter { statement -> s.returnGeneratedValues("id") }
client.sql("SELECT id, name, state FROM table")
.filter { statement -> s.fetchSize(25) }
.filter { statement -> s.fetchSize(25) }
----
======
@@ -593,7 +596,7 @@ Java::
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
Mono<Integer> generatedId = client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter(statement -> s.returnGeneratedValues("id"))
.filter(statement -> s.returnGeneratedValues("id"))
.map(row -> row.get("id", Integer.class))
.first();
@@ -605,7 +608,7 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val generatedId = client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter { statement -> s.returnGeneratedValues("id") }
.filter { statement -> s.returnGeneratedValues("id") }
.map { row -> row.get("id", Integer.class) }
.awaitOne()
@@ -672,7 +675,6 @@ Kotlin::
[[r2dbc-ConnectionFactoryUtils]]
=== Using `ConnectionFactoryUtils`
The `ConnectionFactoryUtils` class is a convenient and powerful helper class
that provides `static` methods to obtain connections from `ConnectionFactory`
and close connections (if necessary).
@@ -12,19 +12,16 @@ management that delivers the following benefits:
than complex transaction APIs, such as JTA.
* Excellent integration with Spring's data access abstractions.
The following sections describe the Spring Framework's transaction features and
technologies:
The following sections describe the Spring Framework's transaction features and technologies:
* xref:data-access/transaction/motivation.adoc[Advantages of the Spring Framework's transaction support model]
describes why you would use the Spring Framework's transaction abstraction
instead of EJB Container-Managed Transactions (CMT) or choosing to drive local
transactions through a proprietary API, such as Hibernate.
* xref:data-access/transaction/motivation.adoc[Advantages of the Spring Framework's transaction support model]
describes why you would use the Spring Framework's transaction abstraction instead of EJB
Container-Managed Transactions (CMT) or choosing to drive transactions through a proprietary API.
* xref:data-access/transaction/strategies.adoc[Understanding the Spring Framework transaction abstraction]
outlines the core classes and describes how to configure and obtain `DataSource`
instances from a variety of sources.
* xref:data-access/transaction/tx-resource-synchronization.adoc[Synchronizing resources with transactions] describes
how the application code ensures that resources are created, reused, and cleaned up
properly.
outlines the core classes and describes how to configure and obtain `DataSource` instances
from a variety of sources.
* xref:data-access/transaction/tx-resource-synchronization.adoc[Synchronizing resources with transactions]
describes how the application code ensures that resources are created, reused, and cleaned up properly.
* xref:data-access/transaction/declarative.adoc[Declarative transaction management] describes support for
declarative transaction management.
* xref:data-access/transaction/programmatic.adoc[Programmatic transaction management] covers support for
@@ -13,39 +13,7 @@ javadoc for details.
Spring's `JtaTransactionManager` is the standard choice to run on Jakarta EE application
servers and is known to work on all common servers. Advanced functionality, such as
transaction suspension, works on many servers as well (including GlassFish, JBoss and
Geronimo) without any special configuration required. However, for fully supported
transaction suspension and further advanced integration, Spring includes special adapters
for WebLogic Server and WebSphere. These adapters are discussed in the following
sections.
For standard scenarios, including WebLogic Server and WebSphere, consider using the
convenient `<tx:jta-transaction-manager/>` configuration element. When configured,
this element automatically detects the underlying server and chooses the best
transaction manager available for the platform. This means that you need not explicitly
configure server-specific adapter classes (as discussed in the following sections).
Rather, they are chosen automatically, with the standard
`JtaTransactionManager` as the default fallback.
[[transaction-application-server-integration-websphere]]
== IBM WebSphere
On WebSphere 6.1.0.9 and above, the recommended Spring JTA transaction manager to use is
`WebSphereUowTransactionManager`. This special adapter uses IBM's `UOWManager` API,
which is available in WebSphere Application Server 6.1.0.9 and later. With this adapter,
Spring-driven transaction suspension (suspend and resume as initiated by
`PROPAGATION_REQUIRES_NEW`) is officially supported by IBM.
[[transaction-application-server-integration-weblogic]]
== Oracle WebLogic Server
On WebLogic Server 9.0 or above, you would typically use the
`WebLogicJtaTransactionManager` instead of the stock `JtaTransactionManager` class. This
special WebLogic-specific subclass of the normal `JtaTransactionManager` supports the
full power of Spring's transaction definitions in a WebLogic-managed transaction
environment, beyond standard JTA semantics. Features include transaction names,
per-transaction isolation levels, and proper resuming of transactions in all cases.
Geronimo) without any special configuration required.
@@ -193,47 +193,39 @@ Kotlin::
======
Note that there are special considerations for the returned `Publisher` with regards to
Reactive Streams cancellation signals. See the xref:data-access/transaction/programmatic.adoc#tx-prog-operator-cancel[Cancel Signals] section under
"Using the TransactionalOperator" for more details.
Reactive Streams cancellation signals. See the
xref:data-access/transaction/programmatic.adoc#tx-prog-operator-cancel[Cancel Signals]
section under "Using the TransactionalOperator" for more details.
[[transaction-declarative-annotations-method-visibility]]
.Method visibility and `@Transactional`
.Method visibility and `@Transactional` in proxy mode
[NOTE]
====
When you use transactional proxies with Spring's standard configuration, you should apply
the `@Transactional` annotation only to methods with `public` visibility. If you do
annotate `protected`, `private`, or package-visible methods with the `@Transactional`
annotation, no error is raised, but the annotated method does not exhibit the configured
transactional settings. If you need to annotate non-public methods, consider the tip in
the following paragraph for class-based proxies or consider using AspectJ compile-time or
load-time weaving (described later).
The `@Transactional` annotation is typically used on methods with `public` visibility.
As of 6.0, `protected` or package-visible methods can also be made transactional for
class-based proxies by default. Note that transactional methods in interface-based
proxies must always be `public` and defined in the proxied interface. For both kinds
of proxies, only external method calls coming in through the proxy are intercepted.
When using `@EnableTransactionManagement` in a `@Configuration` class, `protected` or
package-visible methods can also be made transactional for class-based proxies by
registering a custom `transactionAttributeSource` bean like in the following example.
Note, however, that transactional methods in interface-based proxies must always be
`public` and defined in the proxied interface.
If you prefer consistent treatment of method visibility across the different kinds of
proxies (which was the default up until 5.3), consider specifying `publicMethodsOnly`:
[source,java,indent=0,subs="verbatim,quotes"]
----
/**
* Register a custom AnnotationTransactionAttributeSource with the
* publicMethodsOnly flag set to false to enable support for
* protected and package-private @Transactional methods in
* class-based proxies.
*
* publicMethodsOnly flag set to true to consistently ignore non-public methods.
* @see ProxyTransactionManagementConfiguration#transactionAttributeSource()
*/
@Bean
TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource(false);
return new AnnotationTransactionAttributeSource(true);
}
----
The _Spring TestContext Framework_ supports non-private `@Transactional` test methods by
default. See xref:testing/testcontext-framework/tx.adoc[Transaction Management] in the testing
chapter for examples.
The _Spring TestContext Framework_ supports non-private `@Transactional` test methods
by default as well. See xref:testing/testcontext-framework/tx.adoc[Transaction Management]
in the testing chapter for examples.
====
You can apply the `@Transactional` annotation to an interface definition, a method
@@ -374,7 +366,6 @@ Kotlin::
----
======
[[transaction-declarative-attransactional-settings]]
== `@Transactional` Settings
@@ -453,10 +444,9 @@ on rollback rule semantics, patterns, and warnings regarding possible unintentio
matches for pattern-based rollback rules.
Currently, you cannot have explicit control over the name of a transaction, where 'name'
means the transaction name that appears in a transaction monitor, if applicable
(for example, WebLogic's transaction monitor), and in logging output. For declarative
transactions, the transaction name is always the fully-qualified class name + `.`
+ the method name of the transactionally advised class. For example, if the
means the transaction name that appears in a transaction monitor and in logging output.
For declarative transactions, the transaction name is always the fully-qualified class
name + `.` + the method name of the transactionally advised class. For example, if the
`handlePayment(..)` method of the `BusinessService` class started a transaction, the
name of the transaction would be: `com.example.BusinessService.handlePayment`.
@@ -59,6 +59,14 @@ status and with an inner transaction's locks released immediately after its comp
Such an independent inner transaction can also declare its own isolation level, timeout,
and read-only settings and not inherit an outer transaction's characteristics.
NOTE: The resources attached to the outer transaction will remain bound there while
the inner transaction acquires its own resources such as a new database connection.
This may lead to exhaustion of the connection pool and potentially to a deadlock if
several threads have an active outer transaction and wait to acquire a new connection
for their inner transaction, with the pool not being able to hand out any such inner
connection anymore. Do not use `PROPAGATION_REQUIRES_NEW` unless your connection pool
is appropriately sized, exceeding the number of concurrent threads by at least 1.
[[tx-propagation-nested]]
== Understanding `PROPAGATION_NESTED`
@@ -98,9 +98,9 @@ through its `key` attribute. You can use xref:core/expressions.adoc[SpEL] to pic
arguments of interest (or their nested properties), perform operations, or even
invoke arbitrary methods without having to write any code or implement any interface.
This is the recommended approach over the
xref:integration/cache/annotations.adoc#cache-annotations-cacheable-default-key[default generator], since methods tend to be
quite different in signatures as the code base grows. While the default strategy might
work for some methods, it rarely works for all methods.
xref:integration/cache/annotations.adoc#cache-annotations-cacheable-default-key[default generator],
since methods tend to be quite different in signatures as the code base grows. While the
default strategy might work for some methods, it rarely works for all methods.
The following examples use various SpEL declarations (if you are not familiar with SpEL,
do yourself a favor and read xref:core/expressions.adoc[Spring Expression Language]):
@@ -137,9 +137,8 @@ that specifies both results in an exception.
[[cache-annotations-cacheable-default-cache-resolver]]
=== Default Cache Resolution
The caching abstraction uses a simple `CacheResolver` that
retrieves the caches defined at the operation level by using the configured
`CacheManager`.
The caching abstraction uses a simple `CacheResolver` that retrieves the caches
defined at the operation level by using the configured `CacheManager`.
To provide a different default cache resolver, you need to implement the
`org.springframework.cache.interceptor.CacheResolver` interface.
@@ -160,12 +159,11 @@ For applications that work with several cache managers, you can set the
----
<1> Specifying `anotherCacheManager`.
You can also replace the `CacheResolver` entirely in a fashion similar to that of
replacing xref:integration/cache/annotations.adoc#cache-annotations-cacheable-key[key generation]. The resolution is
requested for every cache operation, letting the implementation actually resolve
the caches to use based on runtime arguments. The following example shows how to
specify a `CacheResolver`:
replacing xref:integration/cache/annotations.adoc#cache-annotations-cacheable-key[key generation].
The resolution is requested for every cache operation, letting the implementation
actually resolve the caches to use based on runtime arguments. The following example
shows how to specify a `CacheResolver`:
[source,java,indent=0,subs="verbatim,quotes"]
----
@@ -174,7 +172,6 @@ specify a `CacheResolver`:
----
<1> Specifying the `CacheResolver`.
[NOTE]
====
Since Spring 4.1, the `value` attribute of the cache annotations are no longer
@@ -229,7 +226,6 @@ argument `name` has a length shorter than 32:
----
<1> Setting a condition on `@Cacheable`.
In addition to the `condition` parameter, you can use the `unless` parameter to veto the
adding of a value to the cache. Unlike `condition`, `unless` expressions are evaluated
after the method has been invoked. To expand on the previous example, perhaps we only
@@ -242,7 +238,6 @@ want to cache paperback books, as the following example does:
----
<1> Using the `unless` attribute to block hardbacks.
The cache abstraction supports `java.util.Optional` return types. If an `Optional` value
is _present_, it will be stored in the associated cache. If an `Optional` value is not
present, `null` will be stored in the associated cache. `#result` always refers to the
@@ -344,7 +339,7 @@ confirm the exclusion.
[[cache-annotations-evict]]
== The `@CacheEvict` annotation
== The `@CacheEvict` Annotation
The cache abstraction allows not just population of a cache store but also eviction.
This process is useful for removing stale or unused data from the cache. As opposed to
@@ -402,7 +397,7 @@ The following example uses two `@CacheEvict` annotations:
[[cache-annotations-config]]
== The `@CacheConfig` annotation
== The `@CacheConfig` Annotation
So far, we have seen that caching operations offer many customization options and that
you can set these options for each operation. However, some of the customization options
@@ -429,10 +424,13 @@ Placing this annotation on the class does not turn on any caching operation.
An operation-level customization always overrides a customization set on `@CacheConfig`.
Therefore, this gives three levels of customizations for each cache operation:
* Globally configured, available for `CacheManager`, `KeyGenerator`.
* Globally configured, e.g. through `CachingConfigurer`: see next section.
* At the class level, using `@CacheConfig`.
* At the operation level.
NOTE: Provider-specific settings are typically available on the `CacheManager` bean,
e.g. on `CaffeineCacheManager`. These are effectively also global.
[[cache-annotation-enable]]
== Enabling Caching Annotations
@@ -451,6 +449,13 @@ To enable caching annotations add the annotation `@EnableCaching` to one of your
@Configuration
@EnableCaching
public class AppConfig {
@Bean
CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCacheSpecification(...);
return cacheManager;
}
}
----
@@ -465,7 +470,11 @@ Alternatively, for XML configuration you can use the `cache:annotation-driven` e
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/cache https://www.springframework.org/schema/cache/spring-cache.xsd">
<cache:annotation-driven/>
<cache:annotation-driven/>
<bean id="cacheManager" class="org.springframework.cache.caffeine.CaffeineCacheManager">
<property name="cacheSpecification" value="..."/>
</bean>
</beans>
----
@@ -56,14 +56,13 @@ locally, as the following example shows:
----
The specified `WorkManager` can also point to an environment-specific thread pool --
typically through a `SimpleTaskWorkManager` instance's `asyncTaskExecutor` property. Consider
defining a shared thread pool for all your `ResourceAdapter` instances if you happen to
use multiple adapters.
typically through a `SimpleTaskWorkManager` instance's `asyncTaskExecutor` property.
Consider defining a shared thread pool for all your `ResourceAdapter` instances
if you happen to use multiple adapters.
In some environments (such as WebLogic 9 or above), you can instead obtain the entire `ResourceAdapter` object
from JNDI (by using `<jee:jndi-lookup>`). The Spring-based message
listeners can then interact with the server-hosted `ResourceAdapter`, which also use the
server's built-in `WorkManager`.
In some environments, you can instead obtain the entire `ResourceAdapter` object from JNDI
(by using `<jee:jndi-lookup>`). The Spring-based message listeners can then interact with
the server-hosted `ResourceAdapter`, which also use the server's built-in `WorkManager`.
See the javadoc for {api-spring-framework}/jms/listener/endpoint/JmsMessageEndpointManager.html[`JmsMessageEndpointManager`],
{api-spring-framework}/jms/listener/endpoint/JmsActivationSpecConfig.html[`JmsActivationSpecConfig`],
@@ -183,18 +183,21 @@ as the following example shows:
If you configure a bean with an `MBeanExporter` that is also configured for lazy
initialization, the `MBeanExporter` does not break this contract and avoids
instantiating the bean. Instead, it registers a proxy with the `MBeanServer` and
defers obtaining the bean from the container until the first invocation on the proxy
occurs.
instantiating the bean. Instead, it registers a proxy with the `MBeanServer` and defers
obtaining the bean from the container until the first invocation on the proxy occurs.
This also affects `FactoryBean` resolution where `MBeanExporter` will regularly
introspect the produced object, effectively triggering `FactoryBean.getObject()`.
In order to avoid this, mark the corresponding bean definition as lazy-init.
[[jmx-exporting-auto]]
== Automatic Registration of MBeans
Any beans that are exported through the `MBeanExporter` and are already valid MBeans are
registered as-is with the `MBeanServer` without further intervention from Spring. You can cause MBeans
to be automatically detected by the `MBeanExporter` by setting the `autodetect`
property to `true`, as the following example shows:
Any beans that are exported through the `MBeanExporter` and are already valid MBeans
are registered as-is with the `MBeanServer` without further intervention from Spring.
You can cause MBeans to be automatically detected by the `MBeanExporter` by setting
the `autodetect` property to `true`, as the following example shows:
[source,xml,indent=0,subs="verbatim,quotes"]
----
@@ -5,16 +5,11 @@ The Spring Framework provides abstractions for the asynchronous execution and sc
tasks with the `TaskExecutor` and `TaskScheduler` interfaces, respectively. Spring also
features implementations of those interfaces that support thread pools or delegation to
CommonJ within an application server environment. Ultimately, the use of these
implementations behind the common interfaces abstracts away the differences between Java
SE 5, Java SE 6, and Jakarta EE environments.
implementations behind the common interfaces abstracts away the differences between
Java SE and Jakarta EE environments.
Spring also features integration classes to support scheduling with the `Timer`
(part of the JDK since 1.3) and the https://www.quartz-scheduler.org/[Quartz Scheduler].
You can set up both of those schedulers by using a `FactoryBean` with optional references to
`Timer` or `Trigger` instances, respectively. Furthermore, a convenience class for both
the Quartz Scheduler and the `Timer` is available that lets you invoke a method of
an existing target object (analogous to the normal `MethodInvokingFactoryBean`
operation).
Spring also features integration classes to support scheduling with the
https://www.quartz-scheduler.org/[Quartz Scheduler].
@@ -62,10 +57,10 @@ The variants that Spring provides are as follows:
`ConcurrentTaskExecutor` directly. However, if the `ThreadPoolTaskExecutor` is not
flexible enough for your needs, `ConcurrentTaskExecutor` is an alternative.
* `ThreadPoolTaskExecutor`:
This implementation is most commonly used. It exposes bean properties for
configuring a `java.util.concurrent.ThreadPoolExecutor` and wraps it in a `TaskExecutor`.
If you need to adapt to a different kind of `java.util.concurrent.Executor`, we
recommend that you use a `ConcurrentTaskExecutor` instead.
This implementation is most commonly used. It exposes bean properties for configuring
a `java.util.concurrent.ThreadPoolExecutor` and wraps it in a `TaskExecutor`.
If you need to adapt to a different kind of `java.util.concurrent.Executor`,
we recommend that you use a `ConcurrentTaskExecutor` instead.
* `DefaultManagedTaskExecutor`:
This implementation uses a JNDI-obtained `ManagedExecutorService` in a JSR-236
compatible runtime environment (such as a Jakarta EE application server),
@@ -75,9 +70,9 @@ The variants that Spring provides are as follows:
[[scheduling-task-executor-usage]]
=== Using a `TaskExecutor`
Spring's `TaskExecutor` implementations are used as simple JavaBeans. In the following example,
we define a bean that uses the `ThreadPoolTaskExecutor` to asynchronously print
out a set of messages:
Spring's `TaskExecutor` implementations are commonly used with dependency injection.
In the following example, we define a bean that uses the `ThreadPoolTaskExecutor`
to asynchronously print out a set of messages:
[source,java,indent=0,subs="verbatim,quotes"]
----
@@ -227,8 +222,8 @@ fixed delay, those methods should be used directly whenever possible. The value
`PeriodicTrigger` implementation is that you can use it within components that rely on
the `Trigger` abstraction. For example, it may be convenient to allow periodic triggers,
cron-based triggers, and even custom trigger implementations to be used interchangeably.
Such a component could take advantage of dependency injection so that you can configure such `Triggers`
externally and, therefore, easily modify or extend them.
Such a component could take advantage of dependency injection so that you can configure
such `Triggers` externally and, therefore, easily modify or extend them.
[[scheduling-task-scheduler-implementations]]
@@ -238,10 +233,8 @@ As with Spring's `TaskExecutor` abstraction, the primary benefit of the `TaskSch
arrangement is that an application's scheduling needs are decoupled from the deployment
environment. This abstraction level is particularly relevant when deploying to an
application server environment where threads should not be created directly by the
application itself. For such scenarios, Spring provides a `TimerManagerTaskScheduler`
that delegates to a CommonJ `TimerManager` on WebLogic or WebSphere as well as a more recent
`DefaultManagedTaskScheduler` that delegates to a JSR-236 `ManagedScheduledExecutorService`
in a Jakarta EE environment. Both are typically configured with a JNDI lookup.
application itself. For such scenarios, Spring provides a `DefaultManagedTaskScheduler`
that delegates to a JSR-236 `ManagedScheduledExecutorService` in a Jakarta EE environment.
Whenever external thread management is not a requirement, a simpler alternative is
a local `ScheduledExecutorService` setup within the application, which can be adapted
@@ -263,8 +256,8 @@ execution.
[[scheduling-enable-annotation-support]]
=== Enable Scheduling Annotations
To enable support for `@Scheduled` and `@Async` annotations, you can add `@EnableScheduling` and
`@EnableAsync` to one of your `@Configuration` classes, as the following example shows:
To enable support for `@Scheduled` and `@Async` annotations, you can add `@EnableScheduling`
and `@EnableAsync` to one of your `@Configuration` classes, as the following example shows:
[source,java,indent=0,subs="verbatim,quotes"]
----
@@ -338,7 +331,7 @@ For example, the previous example can also be written as follows.
If you need a fixed-rate execution, you can use the `fixedRate` attribute within the
annotation. The following method is invoked every five seconds (measured between the
successive start times of each invocation).
successive start times of each invocation):
[source,java,indent=0,subs="verbatim,quotes"]
----
@@ -348,9 +341,9 @@ successive start times of each invocation).
}
----
For fixed-delay and fixed-rate tasks, you can specify an initial delay by indicating the
amount of time to wait before the first execution of the method, as the following
`fixedRate` example shows.
For fixed-delay and fixed-rate tasks, you can specify an initial delay by indicating
the amount of time to wait before the first execution of the method, as the following
`fixedRate` example shows:
[source,java,indent=0,subs="verbatim,quotes"]
----
@@ -380,6 +373,12 @@ Notice that the methods to be scheduled must have void returns and must not acce
arguments. If the method needs to interact with other objects from the application
context, those would typically have been provided through dependency injection.
`@Scheduled` can be used as a repeatable 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.
Please make sure that your specified cron expressions etc do not accidentally overlap.
[NOTE]
====
As of Spring Framework 4.3, `@Scheduled` methods are supported on beans of any scope.
@@ -413,8 +412,8 @@ to a method that returns `void`, as the following example shows:
Unlike the methods annotated with the `@Scheduled` annotation, these methods can expect
arguments, because they are invoked in the "`normal`" way by callers at runtime rather
than from a scheduled task being managed by the container. For example, the following code is
a legitimate application of the `@Async` annotation:
than from a scheduled task being managed by the container. For example, the following
code is a legitimate application of the `@Async` annotation:
[source,java,indent=0,subs="verbatim,quotes"]
----
@@ -438,15 +437,15 @@ that returns a value:
}
----
TIP: `@Async` methods may not only declare a regular `java.util.concurrent.Future` return type
but also Spring's `org.springframework.util.concurrent.ListenableFuture` or, as of Spring
4.2, JDK 8's `java.util.concurrent.CompletableFuture`, for richer interaction with the
asynchronous task and for immediate composition with further processing steps.
TIP: `@Async` methods may not only declare a regular `java.util.concurrent.Future` return
type but also Spring's `org.springframework.util.concurrent.ListenableFuture` or, as of
Spring 4.2, JDK 8's `java.util.concurrent.CompletableFuture`, for richer interaction with
the asynchronous task and for immediate composition with further processing steps.
You can not use `@Async` in conjunction with lifecycle callbacks such as
`@PostConstruct`. To asynchronously initialize Spring beans, you currently have to use
a separate initializing Spring bean that then invokes the `@Async` annotated method on the
target, as the following example shows:
You can not use `@Async` in conjunction with lifecycle callbacks such as `@PostConstruct`.
To asynchronously initialize Spring beans, you currently have to use a separate
initializing Spring bean that then invokes the `@Async` annotated method on the target,
as the following example shows:
[source,java,indent=0,subs="verbatim,quotes"]
----
@@ -500,8 +499,8 @@ used when executing a given method. The following example shows how to do so:
----
In this case, `"otherExecutor"` can be the name of any `Executor` bean in the Spring
container, or it may be the name of a qualifier associated with any `Executor` (for example, as
specified with the `<qualifier>` element or Spring's `@Qualifier` annotation).
container, or it may be the name of a qualifier associated with any `Executor` (for example,
as specified with the `<qualifier>` element or Spring's `@Qualifier` annotation).
[[scheduling-annotation-support-exception]]
@@ -669,14 +668,15 @@ invoked on that object. The following listing shows a simple example:
----
The scheduler is referenced by the outer element, and each individual
task includes the configuration of its trigger metadata. In the preceding example, that
metadata defines a periodic trigger with a fixed delay indicating the number of
task includes the configuration of its trigger metadata. In the preceding example,
that metadata defines a periodic trigger with a fixed delay indicating the number of
milliseconds to wait after each task execution has completed. Another option is
`fixed-rate`, indicating how often the method should be run regardless of how long
any previous execution takes. Additionally, for both `fixed-delay` and `fixed-rate` tasks, you can specify an
'initial-delay' parameter, indicating the number of milliseconds to wait
before the first execution of the method. For more control, you can instead provide a `cron` attribute
to provide a xref:integration/scheduling.adoc#scheduling-cron-expression[cron expression].
any previous execution takes. Additionally, for both `fixed-delay` and `fixed-rate`
tasks, you can specify an 'initial-delay' parameter, indicating the number of
milliseconds to wait before the first execution of the method. For more control,
you can instead provide a `cron` attribute to provide a
xref:integration/scheduling.adoc#scheduling-cron-expression[cron expression].
The following example shows these other options:
[source,xml,indent=0]
@@ -699,9 +699,8 @@ The following example shows these other options:
All Spring cron expressions have to conform to the same format, whether you are using them in
xref:integration/scheduling.adoc#scheduling-annotation-support-scheduled[`@Scheduled` annotations],
xref:integration/scheduling.adoc#scheduling-task-namespace-scheduled-tasks[`task:scheduled-tasks` elements],
or someplace else.
A well-formed cron expression, such as `* * * * * *`, consists of six space-separated time and date
fields, each with its own range of valid values:
or someplace else. A well-formed cron expression, such as `* * * * * *`, consists of six
space-separated time and date fields, each with its own range of valid values:
....
@@ -766,9 +765,10 @@ Here are some examples:
[[macros]]
=== Macros
Expressions such as `0 0 * * * *` are hard for humans to parse and are, therefore, hard to fix in case of bugs.
To improve readability, Spring supports the following macros, which represent commonly used sequences.
You can use these macros instead of the six-digit value, thus: `@Scheduled(cron = "@hourly")`.
Expressions such as `0 0 * * * *` are hard for humans to parse and are, therefore,
hard to fix in case of bugs. To improve readability, Spring supports the following
macros, which represent commonly used sequences. You can use these macros instead
of the six-digit value, thus: `@Scheduled(cron = "@hourly")`.
|===
|Macro | Meaning
@@ -785,8 +785,8 @@ You can use these macros instead of the six-digit value, thus: `@Scheduled(cron
[[scheduling-quartz]]
== Using the Quartz Scheduler
Quartz uses `Trigger`, `Job`, and `JobDetail` objects to realize scheduling of all kinds
of jobs. For the basic concepts behind Quartz, see the
Quartz uses `Trigger`, `Job`, and `JobDetail` objects to realize scheduling of all
kinds of jobs. For the basic concepts behind Quartz, see the
https://www.quartz-scheduler.org/[Quartz Web site]. For convenience purposes, Spring
offers a couple of classes that simplify using Quartz within Spring-based applications.
@@ -794,9 +794,9 @@ offers a couple of classes that simplify using Quartz within Spring-based applic
[[scheduling-quartz-jobdetail]]
=== Using the `JobDetailFactoryBean`
Quartz `JobDetail` objects contain all the information needed to run a job. Spring provides a
`JobDetailFactoryBean`, which provides bean-style properties for XML configuration purposes.
Consider the following example:
Quartz `JobDetail` objects contain all the information needed to run a job. Spring
provides a `JobDetailFactoryBean`, which provides bean-style properties for XML
configuration purposes. Consider the following example:
[source,xml,indent=0,subs="verbatim,quotes"]
----
@@ -813,9 +813,9 @@ Consider the following example:
The job detail configuration has all the information it needs to run the job (`ExampleJob`).
The timeout is specified in the job data map. The job data map is available through the
`JobExecutionContext` (passed to you at execution time), but the `JobDetail` also gets
its properties from the job data mapped to properties of the job instance. So, in the following example,
the `ExampleJob` contains a bean property named `timeout`, and the `JobDetail`
has it applied automatically:
its properties from the job data mapped to properties of the job instance. So, in the
following example, the `ExampleJob` contains a bean property named `timeout`, and the
`JobDetail` has it applied automatically:
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
----
@@ -908,8 +908,8 @@ NOTE: By default, jobs will run in a concurrent fashion.
[[scheduling-quartz-cron]]
=== Wiring up Jobs by Using Triggers and `SchedulerFactoryBean`
We have created job details and jobs. We have also reviewed the convenience bean that lets
you invoke a method on a specific object. Of course, we still need to schedule the
We have created job details and jobs. We have also reviewed the convenience bean that
lets you invoke a method on a specific object. Of course, we still need to schedule the
jobs themselves. This is done by using triggers and a `SchedulerFactoryBean`. Several
triggers are available within Quartz, and Spring offers two Quartz `FactoryBean`
implementations with convenient defaults: `CronTriggerFactoryBean` and
@@ -940,9 +940,9 @@ The following listing uses both a `SimpleTriggerFactoryBean` and a `CronTriggerF
</bean>
----
The preceding example sets up two triggers, one running every 50 seconds with a starting delay of 10
seconds and one running every morning at 6 AM. To finalize everything, we need to set up the
`SchedulerFactoryBean`, as the following example shows:
The preceding example sets up two triggers, one running every 50 seconds with a starting
delay of 10 seconds and one running every morning at 6 AM. To finalize everything,
we need to set up the `SchedulerFactoryBean`, as the following example shows:
[source,xml,indent=0,subs="verbatim,quotes"]
----
@@ -1023,7 +1023,7 @@ Two, create a proxy that will perform the declared RSocket exchanges:
RSocketRequester requester = ... ;
RSocketServiceProxyFactory factory = RSocketServiceProxyFactory.builder(requester).build();
RepositoryService service = factory.createClient(RadarService.class);
RadarService service = factory.createClient(RadarService.class);
----
@@ -3,13 +3,4 @@
:page-section-summary-toc: 1
This section covers annotations that you can use when you test Spring applications.
It includes the following topics:
* xref:testing/annotations/integration-standard.adoc[Standard Annotation Support]
* xref:testing/annotations/integration-spring.adoc[Spring Testing Annotations]
* xref:testing/annotations/integration-junit4.adoc[Spring JUnit 4 Testing Annotations]
* xref:testing/annotations/integration-junit-jupiter.adoc[Spring JUnit Jupiter Testing Annotations]
* xref:testing/annotations/integration-meta.adoc[Meta-Annotation Support for Testing]
@@ -14,7 +14,7 @@ following features.
testing annotations -- as long as the tests are run using a JUnit Platform
`TestEngine` that is registered for the current project.
* Build-time AOT processing: each unique test `ApplicationContext` in the current project
will be xref:core/aot.adoc#refresh[refreshed for AOT processing].
will be xref:core/aot.adoc#aot.refresh[refreshed for AOT processing].
* Runtime AOT support: when executing in AOT runtime mode, a Spring integration test will
use an AOT-optimized `ApplicationContext` that participates transparently with the
xref:testing/testcontext-framework/ctx-management/caching.adoc[context cache].
@@ -35,7 +35,7 @@ the following options.
via {api-spring-framework}/context/annotation/ImportRuntimeHints.html[`@ImportRuntimeHints`].
* Annotate a test class with {api-spring-framework}/aot/hint/annotation/Reflective.html[`@Reflective`] or
{api-spring-framework}/aot/hint/annotation/RegisterReflectionForBinding.html[`@RegisterReflectionForBinding`].
* See xref:core/aot.adoc#hints[Runtime Hints] for details on Spring's core runtime hints
* See xref:core/aot.adoc#aot.hints[Runtime Hints] for details on Spring's core runtime hints
and annotation support.
[TIP]
@@ -35,8 +35,8 @@ in which the test class is defined. A path starting with a slash is treated as a
absolute classpath resource (for example: `"/org/example/test.xml"`). A path that
references a URL (for example, a path prefixed with `classpath:`, `file:`, or `http:`) is
loaded by using the specified resource protocol. Resource location wildcards (such as
`**/*.properties`) are not permitted: Each location must evaluate to exactly one
`.properties` or `.xml` resource.
`{asterisk}{asterisk}/{asterisk}.properties`) are not permitted: Each location must
evaluate to exactly one `.properties` or `.xml` resource.
The following example uses a test properties file:
@@ -75,6 +75,35 @@ To learn more from the source or to make advanced customizations, see:
[[webflux-cors-credentialed-requests]]
== Credentialed Requests
[.small]#xref:web/webmvc-cors.adoc#mvc-cors-credentialed-requests[See equivalent in the Servlet stack]#
Using CORS with credentialed requests requires enabling `allowedCredentials`. Be aware that
this option establishes a high level of trust with the configured domains and also increases
the surface of attack of the web application by exposing sensitive user-specific information
such as cookies and CSRF tokens.
Enabling credentials also impacts how the configured `"*"` CORS wildcards are processed:
* Wildcards are not authorized in `allowOrigins`, but alternatively
the `allowOriginPatterns` property may be used to match to a dynamic set of origins.
* When set on `allowedHeaders` or `allowedMethods`, the `Access-Control-Allow-Headers`
and `Access-Control-Allow-Methods` response headers are handled by copying the related
headers and method specified in the CORS preflight request.
* When set on `exposedHeaders`, `Access-Control-Expose-Headers` response header is set
either to the configured list of headers or to the wildcard character. While the CORS spec
does not allow the wildcard character when `Access-Control-Allow-Credentials` is set to
`true`, most browsers support it and the response headers are not all available during the
CORS processing, so as a consequence the wildcard character is the header value used when
specified regardless of the value of the `allowCredentials` property.
WARNING: While such wildcard configuration can be handy, it is recommended when possible to configure
a finite set of values instead to provide a higher level of security.
[[webflux-cors-controller]]
== `@CrossOrigin`
[.small]#xref:web/webmvc-cors.adoc#mvc-cors-controller[See equivalent in the Servlet stack]#
@@ -25,6 +25,35 @@ powerful workarounds based on IFRAME or JSONP.
[[mvc-cors-credentialed-requests]]
== Credentialed Requests
[.small]#xref:web/webflux-cors.adoc#webflux-cors-credentialed-requests[See equivalent in the Reactive stack]#
Using CORS with credentialed requests requires enabling `allowedCredentials`. Be aware that
this option establishes a high level of trust with the configured domains and also increases
the surface of attack of the web application by exposing sensitive user-specific information
such as cookies and CSRF tokens.
Enabling credentials also impacts how the configured `"*"` CORS wildcards are processed:
* Wildcards are not authorized in `allowOrigins`, but alternatively
the `allowOriginPatterns` property may be used to match to a dynamic set of origins.
* When set on `allowedHeaders` or `allowedMethods`, the `Access-Control-Allow-Headers`
and `Access-Control-Allow-Methods` response headers are handled by copying the related
headers and method specified in the CORS preflight request.
* When set on `exposedHeaders`, `Access-Control-Expose-Headers` response header is set
either to the configured list of headers or to the wildcard character. While the CORS spec
does not allow the wildcard character when `Access-Control-Allow-Credentials` is set to
`true`, most browsers support it and the response headers are not all available during the
CORS processing, so as a consequence the wildcard character is the header value used when
specified regardless of the value of the `allowCredentials` property.
WARNING: While such wildcard configuration can be handy, it is recommended when possible to configure
a finite set of values instead to provide a higher level of security.
[[mvc-cors-processing]]
== Processing
[.small]#xref:web/webflux-cors.adoc#webflux-cors-processing[See equivalent in the Reactive stack]#
@@ -52,7 +52,7 @@ The following example shows how to achieve the same configuration in XML:
</mvc:interceptors>
----
NOTE: Mapped interceptors are not ideally suited as a security layer due to the potential
NOTE: Interceptors are not ideally suited as a security layer due to the potential
for a mismatch with annotated controller path matching, which can also match trailing
slashes and path extensions transparently, along with other path matching options. Many
of these options have been deprecated but the potential for a mismatch remains.
@@ -61,6 +61,10 @@ https://docs.spring.io/spring-security/reference/servlet/integrations/mvc.html#m
to align with Spring MVC path matching and also has a security firewall that blocks many
unwanted characters in URL paths.
NOTE: The XML config declares interceptors as `MappedInterceptor` beans, and those are in
turn detected by any `HandlerMapping` bean, including those from other frameworks.
By contrast, the Java config passes interceptors only to the `HandlerMapping` beans it manages.
To re-use the same interceptors across Spring MVC and other framework `HandlerMapping`
beans with the MVC Java config, either declare `MappedInterceptor` beans (and don't
manually add them in the Java config), or configure the same interceptors in both
the Java config and in other `HandlerMapping` beans.
+20 -21
View File
@@ -8,30 +8,30 @@ javaPlatform {
dependencies {
api(platform("com.fasterxml.jackson:jackson-bom:2.14.3"))
api(platform("io.micrometer:micrometer-bom:1.10.9"))
api(platform("io.netty:netty-bom:4.1.94.Final"))
api(platform("io.micrometer:micrometer-bom:1.10.11"))
api(platform("io.netty:netty-bom:4.1.97.Final"))
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
api(platform("io.projectreactor:reactor-bom:2022.0.9"))
api(platform("io.projectreactor:reactor-bom:2022.0.11"))
api(platform("io.rsocket:rsocket-bom:1.1.3"))
api(platform("org.apache.groovy:groovy-bom:4.0.13"))
api(platform("org.apache.groovy:groovy-bom:4.0.14"))
api(platform("org.apache.logging.log4j:log4j-bom:2.20.0"))
api(platform("org.eclipse.jetty:jetty-bom:11.0.15"))
api(platform("org.eclipse.jetty:jetty-bom:11.0.16"))
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.4"))
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.4.0"))
api(platform("org.junit:junit-bom:5.9.3"))
api(platform("org.mockito:mockito-bom:5.4.0"))
api(platform("org.mockito:mockito-bom:5.5.0"))
constraints {
api("com.fasterxml:aalto-xml:1.3.2")
api("com.fasterxml.woodstox:woodstox-core:6.5.1")
api("com.github.ben-manes.caffeine:caffeine:3.1.6")
api("com.github.ben-manes.caffeine:caffeine:3.1.8")
api("com.github.librepdf:openpdf:1.3.30")
api("com.google.code.findbugs:findbugs:3.0.1")
api("com.google.code.findbugs:jsr305:3.0.2")
api("com.google.code.gson:gson:2.10.1")
api("com.google.protobuf:protobuf-java-util:3.23.2")
api("com.googlecode.protobuf-java-format:protobuf-java-format:1.4")
api("com.h2database:h2:2.1.214")
api("com.h2database:h2:2.2.220")
api("com.jayway.jsonpath:json-path:2.8.0")
api("com.rometools:rome:1.19.0")
api("com.squareup.okhttp3:mockwebserver:3.14.9")
@@ -45,8 +45,7 @@ dependencies {
api("com.thoughtworks.xstream:xstream:1.4.20")
api("commons-io:commons-io:2.11.0")
api("de.bechte.junit:junit-hierarchicalcontextrunner:4.12.2")
api("info.picocli:picocli:4.7.4")
api("io.micrometer:context-propagation:1.0.4")
api("io.micrometer:context-propagation:1.0.5")
api("io.mockk:mockk:1.13.4")
api("io.projectreactor.netty:reactor-netty5-http:2.0.0-M3")
api("io.projectreactor.tools:blockhound:1.0.8.RELEASE")
@@ -55,9 +54,9 @@ dependencies {
api("io.r2dbc:r2dbc-spi:1.0.0.RELEASE")
api("io.reactivex.rxjava3:rxjava:3.1.6")
api("io.smallrye.reactive:mutiny:1.9.0")
api("io.undertow:undertow-core:2.3.7.Final")
api("io.undertow:undertow-servlet:2.3.7.Final")
api("io.undertow:undertow-websockets-jsr:2.3.7.Final")
api("io.undertow:undertow-core:2.3.8.Final")
api("io.undertow:undertow-servlet:2.3.8.Final")
api("io.undertow:undertow-websockets-jsr:2.3.8.Final")
api("io.vavr:vavr:0.10.4")
api("jakarta.activation:jakarta.activation-api:2.0.1")
api("jakarta.annotation:jakarta.annotation-api:2.0.0")
@@ -98,15 +97,15 @@ dependencies {
api("org.apache.derby:derbyclient:10.16.1.1")
api("org.apache.derby:derbytools:10.16.1.1")
api("org.apache.httpcomponents.client5:httpclient5:5.2.1")
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.2.1")
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.2.2")
api("org.apache.poi:poi-ooxml:5.2.3")
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.10")
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.10")
api("org.apache.tomcat:tomcat-util:10.1.10")
api("org.apache.tomcat:tomcat-websocket:10.1.10")
api("org.aspectj:aspectjrt:1.9.19")
api("org.aspectj:aspectjtools:1.9.19")
api("org.aspectj:aspectjweaver:1.9.19")
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.13")
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.13")
api("org.apache.tomcat:tomcat-util:10.1.13")
api("org.apache.tomcat:tomcat-websocket:10.1.13")
api("org.aspectj:aspectjrt:1.9.20")
api("org.aspectj:aspectjtools:1.9.20")
api("org.aspectj:aspectjweaver:1.9.20")
api("org.assertj:assertj-core:3.24.2")
api("org.awaitility:awaitility:4.2.0")
api("org.bouncycastle:bcpkix-jdk18on:1.72")
+1 -1
View File
@@ -1,4 +1,4 @@
version=6.0.11-SNAPSHOT
version=6.0.12
org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+2 -1
View File
@@ -83,7 +83,8 @@ done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -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();
+4 -4
View File
@@ -3,12 +3,12 @@ description = "Spring AOP"
dependencies {
api(project(":spring-beans"))
api(project(":spring-core"))
optional("org.aspectj:aspectjweaver")
optional("org.apache.commons:commons-pool2")
testImplementation(project(":spring-core-test"))
testImplementation(testFixtures(project(":spring-beans")))
testImplementation(testFixtures(project(":spring-core")))
optional("org.aspectj:aspectjweaver")
testFixturesImplementation(testFixtures(project(":spring-beans")))
testFixturesImplementation(testFixtures(project(":spring-core")))
testFixturesImplementation("com.google.code.findbugs:jsr305")
testImplementation(project(":spring-core-test"))
testImplementation(testFixtures(project(":spring-beans")))
testImplementation(testFixtures(project(":spring-core")))
}
@@ -714,13 +714,8 @@ public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedence
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof AdviceExcludingMethodMatcher otherMm)) {
return false;
}
return this.adviceMethod.equals(otherMm.adviceMethod);
return (this == other || (other instanceof AdviceExcludingMethodMatcher that &&
this.adviceMethod.equals(that.adviceMethod)));
}
@Override
@@ -243,8 +243,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) {
@@ -516,21 +516,16 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof AspectJExpressionPointcut otherPc)) {
return false;
}
return ObjectUtils.nullSafeEquals(this.getExpression(), otherPc.getExpression()) &&
ObjectUtils.nullSafeEquals(this.pointcutDeclarationScope, otherPc.pointcutDeclarationScope) &&
ObjectUtils.nullSafeEquals(this.pointcutParameterNames, otherPc.pointcutParameterNames) &&
ObjectUtils.nullSafeEquals(this.pointcutParameterTypes, otherPc.pointcutParameterTypes);
return (this == other || (other instanceof AspectJExpressionPointcut that &&
ObjectUtils.nullSafeEquals(getExpression(), that.getExpression()) &&
ObjectUtils.nullSafeEquals(this.pointcutDeclarationScope, that.pointcutDeclarationScope) &&
ObjectUtils.nullSafeEquals(this.pointcutParameterNames, that.pointcutParameterNames) &&
ObjectUtils.nullSafeEquals(this.pointcutParameterTypes, that.pointcutParameterTypes)));
}
@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);
@@ -89,13 +89,8 @@ public class AspectJPointcutAdvisor implements PointcutAdvisor, Ordered {
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof AspectJPointcutAdvisor otherAdvisor)) {
return false;
}
return this.advice.equals(otherAdvisor.advice);
return (this == other || (other instanceof AspectJPointcutAdvisor that &&
this.advice.equals(that.advice)));
}
@Override
@@ -117,8 +117,8 @@ public class TypePatternClassFilter implements ClassFilter {
}
@Override
public boolean equals(@Nullable Object obj) {
return (this == obj || (obj instanceof TypePatternClassFilter that &&
public boolean equals(@Nullable Object other) {
return (this == other || (other instanceof TypePatternClassFilter that &&
ObjectUtils.nullSafeEquals(this.typePattern, that.typePattern)));
}
@@ -370,8 +370,8 @@ class CglibAopProxy implements AopProxy, Serializable {
@Override
public boolean equals(@Nullable Object other) {
return (this == other || (other instanceof CglibAopProxy cglibAopProxy &&
AopProxyUtils.equalsInProxy(this.advised, cglibAopProxy.advised)));
return (this == other || (other instanceof CglibAopProxy that &&
AopProxyUtils.equalsInProxy(this.advised, that.advised)));
}
@Override
@@ -597,15 +597,10 @@ class CglibAopProxy implements AopProxy, Serializable {
}
if (other instanceof Factory factory) {
Callback callback = factory.getCallback(INVOKE_EQUALS);
if (!(callback instanceof EqualsInterceptor equalsInterceptor)) {
return false;
}
AdvisedSupport otherAdvised = equalsInterceptor.advised;
return AopProxyUtils.equalsInProxy(this.advised, otherAdvised);
}
else {
return false;
return (callback instanceof EqualsInterceptor that &&
AopProxyUtils.equalsInProxy(this.advised, that.advised));
}
return false;
}
}
@@ -920,20 +915,14 @@ class CglibAopProxy implements AopProxy, Serializable {
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof ProxyCallbackFilter otherCallbackFilter)) {
return false;
}
AdvisedSupport otherAdvised = otherCallbackFilter.advised;
return (this.advised.getAdvisorKey().equals(otherAdvised.getAdvisorKey()) &&
AopProxyUtils.equalsProxiedInterfaces(this.advised, otherAdvised) &&
ObjectUtils.nullSafeEquals(this.advised.getTargetClass(), otherAdvised.getTargetClass()) &&
this.advised.getTargetSource().isStatic() == otherAdvised.getTargetSource().isStatic() &&
this.advised.isFrozen() == otherAdvised.isFrozen() &&
this.advised.isExposeProxy() == otherAdvised.isExposeProxy() &&
this.advised.isOpaque() == otherAdvised.isOpaque());
return (this == other || (other instanceof ProxyCallbackFilter that &&
this.advised.getAdvisorKey().equals(that.advised.getAdvisorKey()) &&
AopProxyUtils.equalsProxiedInterfaces(this.advised, that.advised) &&
ObjectUtils.nullSafeEquals(this.advised.getTargetClass(), that.advised.getTargetClass()) &&
this.advised.getTargetSource().isStatic() == that.advised.getTargetSource().isStatic() &&
this.advised.isFrozen() == that.advised.isFrozen() &&
this.advised.isExposeProxy() == that.advised.isExposeProxy() &&
this.advised.isOpaque() == that.advised.isOpaque()));
}
@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.
@@ -109,8 +109,8 @@ public abstract class ClassFilters {
}
@Override
public boolean equals(@Nullable Object obj) {
return (this == obj || (obj instanceof UnionClassFilter that &&
public boolean equals(@Nullable Object other) {
return (this == other || (other instanceof UnionClassFilter that &&
ObjectUtils.nullSafeEquals(this.filters, that.filters)));
}
@@ -150,8 +150,8 @@ public abstract class ClassFilters {
}
@Override
public boolean equals(@Nullable Object obj) {
return (this == obj || (obj instanceof IntersectionClassFilter that &&
public boolean equals(@Nullable Object other) {
return (this == other || (other instanceof IntersectionClassFilter that &&
ObjectUtils.nullSafeEquals(this.filters, that.filters)));
}
@@ -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.
@@ -100,8 +100,8 @@ public class NameMatchMethodPointcut extends StaticMethodMatcherPointcut impleme
@Override
public boolean equals(@Nullable Object obj) {
return (this == obj || (obj instanceof NameMatchMethodPointcut that &&
public boolean equals(@Nullable Object other) {
return (this == other || (other instanceof NameMatchMethodPointcut that &&
this.mappedNames.equals(that.mappedNames)));
}
@@ -46,8 +46,8 @@ public class RootClassFilter implements ClassFilter, Serializable {
}
@Override
public boolean equals(@Nullable Object obj) {
return (this == obj || (obj instanceof RootClassFilter that &&
public boolean equals(@Nullable Object other) {
return (this == other || (other instanceof RootClassFilter that &&
this.clazz.equals(that.clazz)));
}
@@ -133,13 +133,9 @@ public final class EmptyTargetSource implements TargetSource, Serializable {
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof EmptyTargetSource otherTs)) {
return false;
}
return (ObjectUtils.nullSafeEquals(this.targetClass, otherTs.targetClass) && this.isStatic == otherTs.isStatic);
return (this == other || (other instanceof EmptyTargetSource that &&
ObjectUtils.nullSafeEquals(this.targetClass, that.targetClass) &&
this.isStatic == that.isStatic));
}
@Override
@@ -127,7 +127,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
}
@Test
void multiplePerTargetAspects() throws Exception {
void multiplePerTargetAspects() {
TestBean target = new TestBean();
int realAge = 65;
target.setAge(realAge);
@@ -153,7 +153,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
}
@Test
void multiplePerTargetAspectsWithOrderAnnotation() throws Exception {
void multiplePerTargetAspectsWithOrderAnnotation() {
TestBean target = new TestBean();
int realAge = 65;
target.setAge(realAge);
@@ -283,7 +283,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
int realAge = 65;
target.setAge(realAge);
ITestBean itb = createProxy(target, ITestBean.class,
getAdvisorFactory().getAdvisors(aspectInstanceFactory(aspectInstance, "someBean")));
getAdvisorFactory().getAdvisors(aspectInstanceFactory(aspectInstance, "someBean")));
assertThat(itb.getAge()).as("Around advice must apply").isEqualTo(-1);
assertThat(target.getAge()).isEqualTo(realAge);
}
@@ -292,7 +292,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
void bindingWithSingleArg() {
TestBean target = new TestBean();
ITestBean itb = createProxy(target, ITestBean.class,
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new BindingAspectWithSingleArg(), "someBean")));
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new BindingAspectWithSingleArg(), "someBean")));
itb.setAge(10);
assertThat(itb.getAge()).as("Around advice must apply").isEqualTo(20);
assertThat(target.getAge()).isEqualTo(20);
@@ -483,7 +483,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
}
@Test
void nonAbstractParentAspect() throws Exception {
void nonAbstractParentAspect() {
IncrementingAspect aspect = new IncrementingAspect();
// Precondition:
@@ -689,7 +689,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
@Aspect
static class Library {
@Pointcut("execution(!void get*())")
@Pointcut("execution(int get*())") // before AspectJ 1.9.20, "!void" used to work instead of "int"
void propertyAccess() {}
@Pointcut("execution(* *(..)) && args(i)")
@@ -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();
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();
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);
}
@@ -47,14 +47,8 @@ public class NopInterceptor implements MethodInterceptor {
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof NopInterceptor that)) {
return false;
}
return this.count == that.count;
public boolean equals(@Nullable Object other) {
return (this == other || (other instanceof NopInterceptor that && this.count == that.count));
}
@Override
+1 -1
View File
@@ -32,8 +32,8 @@ dependencies {
optional(project(":spring-context-support")) // for JavaMail and JSR-107 support
optional(project(":spring-orm")) // for JPA exception translation support
optional(project(":spring-tx")) // for JPA, @Transactional support
optional("javax.cache:cache-api") // for JCache aspect
optional("jakarta.transaction:jakarta.transaction-api") // for @jakarta.transaction.Transactional support
optional("javax.cache:cache-api") // for JCache aspect
testImplementation(project(":spring-core")) // for CodeStyleAspect
testImplementation(project(":spring-test"))
testImplementation(testFixtures(project(":spring-context")))
+6 -6
View File
@@ -5,14 +5,14 @@ apply plugin: "kotlin"
dependencies {
api(project(":spring-core"))
optional("jakarta.inject:jakarta.inject-api")
optional("org.yaml:snakeyaml")
optional("org.apache.groovy:groovy-xml")
optional("org.jetbrains.kotlin:kotlin-reflect")
optional("org.jetbrains.kotlin:kotlin-stdlib")
testImplementation(testFixtures(project(":spring-core")))
testImplementation(project(":spring-core-test"))
testImplementation("jakarta.annotation:jakarta.annotation-api")
optional("org.yaml:snakeyaml")
testFixturesApi("org.junit.jupiter:junit-jupiter-api")
testFixturesImplementation("org.assertj:assertj-core")
testFixturesImplementation("com.google.code.findbugs:jsr305")
}
testFixturesImplementation("org.assertj:assertj-core")
testImplementation(project(":spring-core-test"))
testImplementation(testFixtures(project(":spring-core")))
testImplementation("jakarta.annotation:jakarta.annotation-api")
}
@@ -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.
@@ -280,7 +280,7 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
}
}
@SuppressWarnings({"unchecked", "rawtypes"})
@SuppressWarnings({"rawtypes", "unchecked"})
private void processKeyedProperty(PropertyTokenHolder tokens, PropertyValue pv) {
Object propValue = getPropertyHoldingValue(tokens);
PropertyHandler ph = getLocalPropertyHandler(tokens.actualName);
@@ -616,7 +616,7 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
return nestedPa.getPropertyValue(tokens);
}
@SuppressWarnings({"unchecked", "rawtypes"})
@SuppressWarnings({"rawtypes", "unchecked"})
@Nullable
protected Object getPropertyValue(PropertyTokenHolder tokens) throws BeansException {
String propertyName = tokens.canonicalName;
@@ -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.
@@ -82,15 +82,10 @@ public class BeanMetadataAttribute implements BeanMetadataElement {
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof BeanMetadataAttribute otherMa)) {
return false;
}
return (this.name.equals(otherMa.name) &&
ObjectUtils.nullSafeEquals(this.value, otherMa.value) &&
ObjectUtils.nullSafeEquals(this.source, otherMa.source));
return (this == other ||(other instanceof BeanMetadataAttribute that &&
this.name.equals(that.name) &&
ObjectUtils.nullSafeEquals(this.value, that.value) &&
ObjectUtils.nullSafeEquals(this.source, that.source)));
}
@Override
@@ -338,8 +338,8 @@ class ExtendedBeanInfo implements BeanInfo {
}
@Override
public boolean equals(@Nullable Object obj) {
return (this == obj || (obj instanceof PropertyDescriptor that &&
public boolean equals(@Nullable Object other) {
return (this == other || (other instanceof PropertyDescriptor that &&
PropertyDescriptorUtils.equals(this, that)));
}
@@ -491,16 +491,11 @@ class ExtendedBeanInfo implements BeanInfo {
*/
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof IndexedPropertyDescriptor otherPd)) {
return false;
}
return (ObjectUtils.nullSafeEquals(getIndexedReadMethod(), otherPd.getIndexedReadMethod()) &&
ObjectUtils.nullSafeEquals(getIndexedWriteMethod(), otherPd.getIndexedWriteMethod()) &&
ObjectUtils.nullSafeEquals(getIndexedPropertyType(), otherPd.getIndexedPropertyType()) &&
PropertyDescriptorUtils.equals(this, otherPd));
return (this == other || (other instanceof IndexedPropertyDescriptor that &&
ObjectUtils.nullSafeEquals(getIndexedReadMethod(), that.getIndexedReadMethod()) &&
ObjectUtils.nullSafeEquals(getIndexedWriteMethod(), that.getIndexedWriteMethod()) &&
ObjectUtils.nullSafeEquals(getIndexedPropertyType(), that.getIndexedPropertyType()) &&
PropertyDescriptorUtils.equals(this, that)));
}
@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.
@@ -165,13 +165,9 @@ final class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor {
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof GenericTypeAwarePropertyDescriptor otherPd)) {
return false;
}
return (getBeanClass().equals(otherPd.getBeanClass()) && PropertyDescriptorUtils.equals(this, otherPd));
return (this == other || (other instanceof GenericTypeAwarePropertyDescriptor that &&
getBeanClass().equals(that.getBeanClass()) &&
PropertyDescriptorUtils.equals(this, that)));
}
@Override
@@ -366,8 +366,8 @@ public class MutablePropertyValues implements PropertyValues, Serializable {
@Override
public boolean equals(@Nullable Object obj) {
return (this == obj || (obj instanceof MutablePropertyValues that &&
public boolean equals(@Nullable Object other) {
return (this == other || (other instanceof MutablePropertyValues that &&
this.propertyValueList.equals(that.propertyValueList)));
}
@@ -189,15 +189,10 @@ public class PropertyValue extends BeanMetadataAttributeAccessor implements Seri
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof PropertyValue otherPv)) {
return false;
}
return (this.name.equals(otherPv.name) &&
ObjectUtils.nullSafeEquals(this.value, otherPv.value) &&
ObjectUtils.nullSafeEquals(getSource(), otherPv.getSource()));
return (this == other || (other instanceof PropertyValue that &&
this.name.equals(that.name) &&
ObjectUtils.nullSafeEquals(this.value, that.value) &&
ObjectUtils.nullSafeEquals(getSource(), that.getSource())));
}
@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.
@@ -68,9 +68,21 @@ public class StandardBeanInfoFactory implements BeanInfoFactory, Ordered {
@Override
@NonNull
public BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionException {
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;
}
@Override
@@ -166,7 +166,8 @@ class TypeConverterDelegate {
}
else if (requiredType.isArray()) {
// Array required -> apply appropriate conversion of elements.
if (convertedValue instanceof String text && Enum.class.isAssignableFrom(requiredType.getComponentType())) {
if (convertedValue instanceof String text &&
Enum.class.isAssignableFrom(requiredType.getComponentType())) {
convertedValue = StringUtils.commaDelimitedListToStringArray(text);
}
return (T) convertToTypedArray(convertedValue, propertyName, requiredType.getComponentType());
@@ -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.
@@ -16,6 +16,7 @@
package org.springframework.beans.factory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -61,7 +62,7 @@ public class NoUniqueBeanDefinitionException extends NoSuchBeanDefinitionExcepti
super(type, "expected single matching bean but found " + beanNamesFound.size() + ": " +
StringUtils.collectionToCommaDelimitedString(beanNamesFound));
this.numberOfBeansFound = beanNamesFound.size();
this.beanNamesFound = beanNamesFound;
this.beanNamesFound = new ArrayList<>(beanNamesFound);
}
/**
@@ -83,7 +84,7 @@ public class NoUniqueBeanDefinitionException extends NoSuchBeanDefinitionExcepti
super(type, "expected single matching bean but found " + beanNamesFound.size() + ": " +
StringUtils.collectionToCommaDelimitedString(beanNamesFound));
this.numberOfBeansFound = beanNamesFound.size();
this.beanNamesFound = beanNamesFound;
this.beanNamesFound = new ArrayList<>(beanNamesFound);
}
/**
@@ -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;
@@ -79,6 +80,10 @@ import org.springframework.core.PriorityOrdered;
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.javapoet.ClassName;
import org.springframework.javapoet.CodeBlock;
import org.springframework.lang.Nullable;
@@ -167,6 +172,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);
@@ -271,6 +279,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
"AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory: " + beanFactory);
}
this.beanFactory = clbf;
this.metadataReaderFactory = new SimpleMetadataReaderFactory(clbf.getBeanClassLoader());
}
@@ -539,12 +548,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) {
@@ -555,10 +563,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)) {
@@ -580,11 +589,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);
@@ -617,6 +627,47 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
this.requiredParameterValue == ann.getBoolean(this.requiredParameterName));
}
/**
* 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.
*/
@@ -705,7 +756,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;
@@ -724,8 +775,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
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;
@@ -805,7 +855,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++) {
@@ -814,7 +864,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;
@@ -829,16 +879,16 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
if (!this.cached) {
if (arguments != null) {
DependencyDescriptor[] cachedMethodArguments = Arrays.copyOf(descriptors, argumentCount);
registerDependentBeans(beanName, autowiredBeans);
if (autowiredBeans.size() == argumentCount) {
Iterator<String> it = autowiredBeans.iterator();
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 (arguments[i] != null && beanFactory.containsBean(autowiredBeanName) &&
beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) {
cachedMethodArguments[i] = new ShortcutDependencyDescriptor(
descriptors[i], autowiredBeanName, paramTypes[i]);
descriptors[i], autowiredBeanName);
}
}
}
@@ -864,17 +914,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());
}
}
@@ -340,13 +340,8 @@ public class InjectionMetadata {
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof InjectedElement otherElement)) {
return false;
}
return this.member.equals(otherElement.member);
return (this == other || (other instanceof InjectedElement that &&
this.member.equals(that.member)));
}
@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.
@@ -62,20 +62,14 @@ abstract class AutowiredElementResolver {
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.
@@ -179,8 +179,7 @@ public final class AutowiredFieldValueResolver extends AutowiredElementResolver
DependencyDescriptor descriptor = new DependencyDescriptor(field, this.required);
descriptor.setContainingClass(beanClass);
if (this.shortcut != null) {
descriptor = new ShortcutDependencyDescriptor(descriptor, this.shortcut,
field.getType());
descriptor = new ShortcutDependencyDescriptor(descriptor, this.shortcut);
}
Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
TypeConverter typeConverter = beanFactory.getTypeConverter();
@@ -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.
@@ -76,6 +76,7 @@ public final class AutowiredMethodArgumentsResolver extends AutowiredElementReso
this.shortcuts = shortcuts;
}
/**
* Create a new {@link AutowiredMethodArgumentsResolver} for the specified
* method where injection is optional.
@@ -83,11 +84,8 @@ public final class AutowiredMethodArgumentsResolver extends AutowiredElementReso
* @param parameterTypes the factory method parameter types
* @return a new {@link AutowiredFieldValueResolver} instance
*/
public static AutowiredMethodArgumentsResolver forMethod(String methodName,
Class<?>... parameterTypes) {
return new AutowiredMethodArgumentsResolver(methodName, parameterTypes, false,
null);
public static AutowiredMethodArgumentsResolver forMethod(String methodName, Class<?>... parameterTypes) {
return new AutowiredMethodArgumentsResolver(methodName, parameterTypes, false, null);
}
/**
@@ -97,11 +95,8 @@ public final class AutowiredMethodArgumentsResolver extends AutowiredElementReso
* @param parameterTypes the factory method parameter types
* @return a new {@link AutowiredFieldValueResolver} instance
*/
public static AutowiredMethodArgumentsResolver forRequiredMethod(String methodName,
Class<?>... parameterTypes) {
return new AutowiredMethodArgumentsResolver(methodName, parameterTypes, true,
null);
public static AutowiredMethodArgumentsResolver forRequiredMethod(String methodName, Class<?>... parameterTypes) {
return new AutowiredMethodArgumentsResolver(methodName, parameterTypes, true, null);
}
/**
@@ -113,8 +108,7 @@ public final class AutowiredMethodArgumentsResolver extends AutowiredElementReso
* the shortcuts
*/
public AutowiredMethodArgumentsResolver withShortcut(String... beanNames) {
return new AutowiredMethodArgumentsResolver(this.methodName, this.parameterTypes,
this.required, beanNames);
return new AutowiredMethodArgumentsResolver(this.methodName, this.parameterTypes, this.required, beanNames);
}
/**
@@ -123,9 +117,7 @@ public final class AutowiredMethodArgumentsResolver extends AutowiredElementReso
* @param registeredBean the registered bean
* @param action the action to execute with the resolved method arguments
*/
public void resolve(RegisteredBean registeredBean,
ThrowingConsumer<AutowiredArguments> action) {
public void resolve(RegisteredBean registeredBean, ThrowingConsumer<AutowiredArguments> action) {
Assert.notNull(registeredBean, "'registeredBean' must not be null");
Assert.notNull(action, "'action' must not be null");
AutowiredArguments resolved = resolve(registeredBean);
@@ -177,25 +169,22 @@ public final class AutowiredMethodArgumentsResolver extends AutowiredElementReso
TypeConverter typeConverter = beanFactory.getTypeConverter();
for (int i = 0; i < argumentCount; i++) {
MethodParameter parameter = new MethodParameter(method, i);
DependencyDescriptor descriptor = new DependencyDescriptor(parameter,
this.required);
DependencyDescriptor descriptor = new DependencyDescriptor(parameter, this.required);
descriptor.setContainingClass(beanClass);
String shortcut = (this.shortcuts != null) ? this.shortcuts[i] : null;
String shortcut = (this.shortcuts != null ? this.shortcuts[i] : null);
if (shortcut != null) {
descriptor = new ShortcutDependencyDescriptor(descriptor, shortcut,
parameter.getParameterType());
descriptor = new ShortcutDependencyDescriptor(descriptor, shortcut);
}
try {
Object argument = autowireCapableBeanFactory.resolveDependency(descriptor,
beanName, autowiredBeanNames, typeConverter);
Object argument = autowireCapableBeanFactory.resolveDependency(
descriptor, beanName, autowiredBeanNames, typeConverter);
if (argument == null && !this.required) {
return null;
}
arguments[i] = argument;
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, beanName,
new InjectionPoint(parameter), ex);
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(parameter), ex);
}
}
registerDependentBeans(beanFactory, beanName, autowiredBeanNames);
@@ -90,8 +90,7 @@ class BeanDefinitionMethodGenerator {
/**
* Generate the method that returns the {@link BeanDefinition} to be
* registered.
* Generate the method that returns the {@link BeanDefinition} to be registered.
* @param generationContext the generation context
* @param beanRegistrationsCode the bean registrations code
* @return a reference to the generated method.
@@ -100,8 +99,7 @@ class BeanDefinitionMethodGenerator {
BeanRegistrationsCode beanRegistrationsCode) {
registerRuntimeHintsIfNecessary(generationContext.getRuntimeHints());
BeanRegistrationCodeFragments codeFragments = getCodeFragments(generationContext,
beanRegistrationsCode);
BeanRegistrationCodeFragments codeFragments = getCodeFragments(generationContext, beanRegistrationsCode);
ClassName target = codeFragments.getTarget(this.registeredBean, this.constructorOrFactoryMethod);
if (isWritablePackageName(target)) {
GeneratedClass generatedClass = lookupGeneratedClass(generationContext, target);
@@ -187,7 +185,7 @@ class BeanDefinitionMethodGenerator {
return generatedMethods.add("getBeanDefinition", method -> {
method.addJavadoc("Get the $L definition for '$L'.",
(!this.registeredBean.isInnerBean()) ? "bean" : "inner-bean",
(this.registeredBean.isInnerBean() ? "inner-bean" : "bean"),
getName());
method.addModifiers(modifier, Modifier.STATIC);
method.returns(BeanDefinition.class);
@@ -214,9 +212,9 @@ class BeanDefinitionMethodGenerator {
private String getSimpleBeanName(String beanName) {
int lastDot = beanName.lastIndexOf('.');
beanName = (lastDot != -1) ? beanName.substring(lastDot + 1) : beanName;
beanName = (lastDot != -1 ? beanName.substring(lastDot + 1) : beanName);
int lastDollar = beanName.lastIndexOf('$');
beanName = (lastDollar != -1) ? beanName.substring(lastDollar + 1) : beanName;
beanName = (lastDollar != -1 ? beanName.substring(lastDollar + 1) : beanName);
return StringUtils.uncapitalize(beanName);
}
@@ -248,18 +248,18 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
() -> "'shortcuts' must contain " + resolved.length + " elements");
ConstructorArgumentValues argumentValues = resolveArgumentValues(registeredBean);
Set<String> autowiredBeans = new LinkedHashSet<>(resolved.length);
Set<String> autowiredBeanNames = new LinkedHashSet<>(resolved.length * 2);
for (int i = startIndex; i < parameterCount; i++) {
MethodParameter parameter = getMethodParameter(executable, i);
DependencyDescriptor descriptor = new DependencyDescriptor(parameter, true);
String shortcut = (this.shortcuts != null ? this.shortcuts[i - startIndex] : null);
if (shortcut != null) {
descriptor = new ShortcutDependencyDescriptor(descriptor, shortcut, registeredBean.getBeanClass());
descriptor = new ShortcutDependencyDescriptor(descriptor, shortcut);
}
ValueHolder argumentValue = argumentValues.getIndexedArgumentValue(i, null);
resolved[i - startIndex] = resolveArgument(registeredBean, descriptor, argumentValue, autowiredBeans);
resolved[i - startIndex] = resolveArgument(registeredBean, descriptor, argumentValue, autowiredBeanNames);
}
registerDependentBeans(registeredBean.getBeanFactory(), registeredBean.getBeanName(), autowiredBeans);
registerDependentBeans(registeredBean.getBeanFactory(), registeredBean.getBeanName(), autowiredBeanNames);
return AutowiredArguments.of(resolved);
}
@@ -302,7 +302,7 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
@Nullable
private Object resolveArgument(RegisteredBean registeredBean, DependencyDescriptor descriptor,
@Nullable ValueHolder argumentValue, Set<String> autowiredBeans) {
@Nullable ValueHolder argumentValue, Set<String> autowiredBeanNames) {
TypeConverter typeConverter = registeredBean.getBeanFactory().getTypeConverter();
if (argumentValue != null) {
@@ -311,7 +311,7 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
descriptor.getDependencyType(), descriptor.getMethodParameter()));
}
try {
return registeredBean.resolveAutowiredArgument(descriptor, typeConverter, autowiredBeans);
return registeredBean.resolveAutowiredArgument(descriptor, typeConverter, autowiredBeanNames);
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, registeredBean.getBeanName(), descriptor, ex);
@@ -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.
@@ -52,8 +52,8 @@ public final class AutowiredPropertyMarker implements Serializable {
@Override
public boolean equals(@Nullable Object obj) {
return (this == obj);
public boolean equals(@Nullable Object other) {
return (this == other);
}
@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.
@@ -165,15 +165,10 @@ public class BeanDefinitionHolder implements BeanMetadataElement {
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof BeanDefinitionHolder otherHolder)) {
return false;
}
return this.beanDefinition.equals(otherHolder.beanDefinition) &&
this.beanName.equals(otherHolder.beanName) &&
ObjectUtils.nullSafeEquals(this.aliases, otherHolder.aliases);
return (this == other || (other instanceof BeanDefinitionHolder that &&
this.beanDefinition.equals(that.beanDefinition) &&
this.beanName.equals(that.beanName) &&
ObjectUtils.nullSafeEquals(this.aliases, that.aliases)));
}
@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.
@@ -70,13 +70,8 @@ public class BeanExpressionContext {
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof BeanExpressionContext otherContext)) {
return false;
}
return (this.beanFactory == otherContext.beanFactory && this.scope == otherContext.scope);
return (this == other || (other instanceof BeanExpressionContext that &&
this.beanFactory == that.beanFactory && this.scope == that.scope));
}
@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.
@@ -68,13 +68,8 @@ public class RuntimeBeanNameReference implements BeanReference {
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof RuntimeBeanNameReference that)) {
return false;
}
return this.beanName.equals(that.beanName);
return (this == other || (other instanceof RuntimeBeanNameReference that &&
this.beanName.equals(that.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.
@@ -132,14 +132,9 @@ public class RuntimeBeanReference implements BeanReference {
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof RuntimeBeanReference that)) {
return false;
}
return (this.beanName.equals(that.beanName) && this.beanType == that.beanType &&
this.toParent == that.toParent);
return (this == other || (other instanceof RuntimeBeanReference that &&
this.beanName.equals(that.beanName) && this.beanType == that.beanType &&
this.toParent == that.toParent));
}
@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.
@@ -216,14 +216,9 @@ public class TypedStringValue implements BeanMetadataElement {
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof TypedStringValue otherValue)) {
return false;
}
return (ObjectUtils.nullSafeEquals(this.value, otherValue.value) &&
ObjectUtils.nullSafeEquals(this.targetType, otherValue.targetType));
return (this == other || (other instanceof TypedStringValue that &&
ObjectUtils.nullSafeEquals(this.value, that.value) &&
ObjectUtils.nullSafeEquals(this.targetType, that.targetType)));
}
@Override
@@ -226,7 +226,7 @@ public abstract class YamlProcessor {
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@SuppressWarnings({"rawtypes", "unchecked"})
private Map<String, Object> asMap(Object object) {
// YAML can have numbers as keys
Map<String, Object> result = new LinkedHashMap<>();
@@ -305,7 +305,7 @@ public abstract class YamlProcessor {
return result;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@SuppressWarnings({"rawtypes", "unchecked"})
private void buildFlattenedMap(Map<String, Object> result, Map<String, Object> source, @Nullable String path) {
source.forEach((key, value) -> {
if (StringUtils.hasText(path)) {
@@ -837,16 +837,13 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
return result;
}
ResolvableType beanType =
(mbd.hasBeanClass() ? ResolvableType.forClass(mbd.getBeanClass()) : ResolvableType.NONE);
// For instance supplied beans try the target type and bean class
// For instance supplied beans, try the target type and bean class immediately
if (mbd.getInstanceSupplier() != null) {
result = getFactoryBeanGeneric(mbd.targetType);
if (result.resolve() != null) {
return result;
}
result = getFactoryBeanGeneric(beanType);
result = getFactoryBeanGeneric(mbd.hasBeanClass() ? ResolvableType.forClass(mbd.getBeanClass()) : null);
if (result.resolve() != null) {
return result;
}
@@ -908,18 +905,21 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
// static factory method signature or from class inheritance hierarchy...
return getTypeForFactoryBeanFromMethod(mbd.getBeanClass(), factoryMethodName);
}
result = getFactoryBeanGeneric(beanType);
if (result.resolve() != null) {
return result;
}
return ResolvableType.NONE;
}
private ResolvableType getFactoryBeanGeneric(@Nullable ResolvableType type) {
if (type == null) {
return ResolvableType.NONE;
// For regular beans, try the target type and bean class as fallback
if (mbd.getInstanceSupplier() == null) {
result = getFactoryBeanGeneric(mbd.targetType);
if (result.resolve() != null) {
return result;
}
result = getFactoryBeanGeneric(mbd.hasBeanClass() ? ResolvableType.forClass(mbd.getBeanClass()) : null);
if (result.resolve() != null) {
return result;
}
}
return type.as(FactoryBean.class).getGeneric();
// FactoryBean type not resolvable
return ResolvableType.NONE;
}
/**
@@ -1483,8 +1483,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);
@@ -1211,13 +1211,8 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof AbstractBeanDefinition that)) {
return false;
}
return (ObjectUtils.nullSafeEquals(getBeanClassName(), that.getBeanClassName()) &&
return (this == other || (other instanceof AbstractBeanDefinition that &&
ObjectUtils.nullSafeEquals(getBeanClassName(), that.getBeanClassName()) &&
ObjectUtils.nullSafeEquals(this.scope, that.scope) &&
this.abstractFlag == that.abstractFlag &&
this.lazyInit == that.lazyInit &&
@@ -1240,7 +1235,7 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
this.enforceDestroyMethod == that.enforceDestroyMethod &&
this.synthetic == that.synthetic &&
this.role == that.role &&
super.equals(other));
super.equals(other)));
}
private boolean equalsConstructorArgumentValues(AbstractBeanDefinition other) {
@@ -64,7 +64,6 @@ import org.springframework.beans.factory.config.DestructionAwareBeanPostProcesso
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
import org.springframework.beans.factory.config.Scope;
import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor;
import org.springframework.core.AttributeAccessor;
import org.springframework.core.DecoratingClassLoader;
import org.springframework.core.NamedThreadLocal;
import org.springframework.core.ResolvableType;
@@ -583,7 +582,6 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
Class<?>[] typesToMatch = (FactoryBean.class == classToMatch ?
new Class<?>[] {classToMatch} : new Class<?>[] {FactoryBean.class, classToMatch});
// Attempt to predict the bean type
Class<?> predictedType = null;
@@ -1685,25 +1683,8 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
onSuppressedException(ex);
}
}
return ResolvableType.NONE;
}
/**
* Determine the bean type for a FactoryBean by inspecting its attributes for a
* {@link FactoryBean#OBJECT_TYPE_ATTRIBUTE} value.
* @param attributes the attributes to inspect
* @return a {@link ResolvableType} extracted from the attributes or
* {@code ResolvableType.NONE}
* @since 5.2
*/
ResolvableType getTypeForFactoryBeanFromAttributes(AttributeAccessor attributes) {
Object attribute = attributes.getAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE);
if (attribute instanceof ResolvableType resolvableType) {
return resolvableType;
}
if (attribute instanceof Class<?> clazz) {
return ResolvableType.forClass(clazz);
}
// FactoryBean type not resolvable
return ResolvableType.NONE;
}
@@ -81,8 +81,8 @@ class BeanDefinitionResource extends AbstractResource {
* This implementation compares the underlying BeanDefinition.
*/
@Override
public boolean equals(@Nullable Object obj) {
return (this == obj || (obj instanceof BeanDefinitionResource that &&
public boolean equals(@Nullable Object other) {
return (this == other || (other instanceof BeanDefinitionResource that &&
this.beanDefinition.equals(that.beanDefinition)));
}
@@ -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.
@@ -153,7 +153,7 @@ public class BeanDefinitionValueResolver {
(name, mbd) -> resolveInnerBeanValue(argName, name, mbd));
}
else if (value instanceof DependencyDescriptor dependencyDescriptor) {
Set<String> autowiredBeanNames = new LinkedHashSet<>(4);
Set<String> autowiredBeanNames = new LinkedHashSet<>(2);
Object result = this.beanFactory.resolveDependency(
dependencyDescriptor, this.beanName, autowiredBeanNames, this.typeConverter);
for (String autowiredBeanName : autowiredBeanNames) {
@@ -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.
@@ -157,13 +157,8 @@ public class ChildBeanDefinition extends AbstractBeanDefinition {
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof ChildBeanDefinition that)) {
return false;
}
return (ObjectUtils.nullSafeEquals(this.parentName, that.parentName) && super.equals(other));
return (this == other || (other instanceof ChildBeanDefinition that &&
ObjectUtils.nullSafeEquals(this.parentName, that.parentName) && super.equals(other)));
}
@Override
@@ -48,6 +48,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.FactoryBean;
import org.springframework.beans.factory.InjectionPoint;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
@@ -85,7 +86,7 @@ import org.springframework.util.StringUtils;
* @author Sebastien Deleuze
* @author Sam Brannen
* @author Stephane Nicoll
* @author Phil Webb
* @author Phillip Webb
* @since 2.0
* @see #autowireConstructor
* @see #instantiateUsingFactoryMethod
@@ -96,12 +97,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 constructorValueHolder) {
@@ -788,11 +783,17 @@ class ConstructorResolver {
"] - did you specify the correct bean references as arguments?");
}
try {
Object autowiredArgument = resolveAutowiredArgument(new DependencyDescriptor(methodParam, true),
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,32 +823,60 @@ 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(new DependencyDescriptor(methodParam, true),
beanName, null, converter, true);
Class<?> paramType = paramTypes[argIndex];
boolean convertNecessary = false;
if (argValue instanceof ConstructorDependencyDescriptor descriptor) {
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 text) {
argValue = this.beanFactory.evaluateBeanDefinitionString(text, 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) {
@@ -870,13 +892,12 @@ 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(DependencyDescriptor descriptor, String beanName,
Object resolveAutowiredArgument(DependencyDescriptor descriptor, Class<?> paramType, String beanName,
@Nullable Set<String> autowiredBeanNames, TypeConverter typeConverter, boolean fallback) {
Class<?> paramType = descriptor.getMethodParameter().getParameterType();
if (InjectionPoint.class.isAssignableFrom(paramType)) {
InjectionPoint injectionPoint = currentInjectionPoint.get();
if (injectionPoint == null) {
@@ -884,9 +905,9 @@ class ConstructorResolver {
}
return injectionPoint;
}
try {
return this.beanFactory.resolveDependency(
descriptor, beanName, autowiredBeanNames, typeConverter);
return this.beanFactory.resolveDependency(descriptor, beanName, autowiredBeanNames, typeConverter);
}
catch (NoUniqueBeanDefinitionException ex) {
throw ex;
@@ -909,6 +930,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 + "'");
}
}
}
// AOT-oriented pre-resolution
@@ -929,19 +975,19 @@ class ConstructorResolver {
Assert.state(isCompatible, () -> String.format(
"Incompatible target type '%s' for factory bean '%s'",
resolvableType.toClass().getName(), factoryBeanClass.getName()));
Executable executable = resolveConstructor(beanName, mbd,
Constructor<?> constructor = resolveConstructor(beanName, mbd,
() -> ResolvableType.forClass(factoryBeanClass), valueTypes);
if (executable != null) {
return executable;
if (constructor != null) {
return constructor;
}
throw new IllegalStateException("No suitable FactoryBean constructor found for " +
mbd + " and argument types " + valueTypes);
}
Executable resolvedConstructor = resolveConstructor(beanName, mbd, beanType, valueTypes);
if (resolvedConstructor != null) {
return resolvedConstructor;
Constructor<?> constructor = resolveConstructor(beanName, mbd, beanType, valueTypes);
if (constructor != null) {
return constructor;
}
throw new IllegalStateException("No constructor or factory method candidate found for " +
@@ -984,7 +1030,7 @@ class ConstructorResolver {
}
@Nullable
private Executable resolveConstructor(String beanName, RootBeanDefinition mbd,
private Constructor<?> resolveConstructor(String beanName, RootBeanDefinition mbd,
Supplier<ResolvableType> beanType, List<ResolvableType> valueTypes) {
Class<?> type = ClassUtils.getUserClass(beanType.get().toClass());
@@ -1008,14 +1054,14 @@ class ConstructorResolver {
}
return types;
};
List<? extends Executable> matches = Arrays.stream(ctors)
List<Constructor<?>> matches = Arrays.stream(ctors)
.filter(executable -> match(parameterTypesFactory.apply(executable),
valueTypes, FallbackMode.NONE))
.toList();
if (matches.size() == 1) {
return matches.get(0);
}
List<? extends Executable> assignableElementFallbackMatches = Arrays
List<Constructor<?>> assignableElementFallbackMatches = Arrays
.stream(ctors)
.filter(executable -> match(parameterTypesFactory.apply(executable),
valueTypes, FallbackMode.ASSIGNABLE_ELEMENT))
@@ -1023,7 +1069,7 @@ class ConstructorResolver {
if (assignableElementFallbackMatches.size() == 1) {
return assignableElementFallbackMatches.get(0);
}
List<? extends Executable> typeConversionFallbackMatches = Arrays
List<Constructor<?>> typeConversionFallbackMatches = Arrays
.stream(ctors)
.filter(executable -> match(parameterTypesFactory.apply(executable),
valueTypes, FallbackMode.TYPE_CONVERSION))
@@ -1075,7 +1121,7 @@ class ConstructorResolver {
}
return types;
};
result = (Method) resolveFactoryMethod(candidates, parameterTypesFactory, valueTypes);
result = resolveFactoryMethod(candidates, parameterTypesFactory, valueTypes);
}
if (result == null) {
@@ -1092,24 +1138,24 @@ class ConstructorResolver {
}
@Nullable
private Executable resolveFactoryMethod(List<Method> executables,
private Method resolveFactoryMethod(List<Method> executables,
Function<Method, List<ResolvableType>> parameterTypesFactory,
List<ResolvableType> valueTypes) {
List<? extends Executable> matches = executables.stream()
List<Method> matches = executables.stream()
.filter(executable -> match(parameterTypesFactory.apply(executable), valueTypes, FallbackMode.NONE))
.toList();
if (matches.size() == 1) {
return matches.get(0);
}
List<? extends Executable> assignableElementFallbackMatches = executables.stream()
List<Method> assignableElementFallbackMatches = executables.stream()
.filter(executable -> match(parameterTypesFactory.apply(executable),
valueTypes, FallbackMode.ASSIGNABLE_ELEMENT))
.toList();
if (assignableElementFallbackMatches.size() == 1) {
return assignableElementFallbackMatches.get(0);
}
List<? extends Executable> typeConversionFallbackMatches = executables.stream()
List<Method> typeConversionFallbackMatches = executables.stream()
.filter(executable -> match(parameterTypesFactory.apply(executable),
valueTypes, FallbackMode.TYPE_CONVERSION))
.toList();
@@ -1361,6 +1407,37 @@ 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);
}
}
private enum FallbackMode {
NONE,
@@ -1472,7 +1472,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
}
TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
Object result = converter.convertIfNecessary(matchingBeans.values(), resolvedArrayType);
if (result instanceof Object[] array) {
if (result instanceof Object[] array && array.length > 1) {
Comparator<Object> comparator = adaptDependencyComparator(matchingBeans);
if (comparator != null) {
Arrays.sort(array, comparator);
@@ -1664,7 +1664,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
if (priorityCandidate != null) {
return priorityCandidate;
}
// Fallback
// Fallback: pick directly registered dependency or qualified bean name match
for (Map.Entry<String, Object> entry : candidates.entrySet()) {
String candidateName = entry.getKey();
Object beanInstance = entry.getValue();
@@ -2083,7 +2083,6 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
public boolean isRequired() {
return false;
}
@Override
@Nullable
public Object resolveNotUnique(ResolvableType type, Map<String, Object> matchingBeans) {
@@ -2137,7 +2136,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
return resolveStream(true);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@SuppressWarnings({"rawtypes", "unchecked"})
private Stream<Object> resolveStream(boolean ordered) {
DependencyDescriptor descriptorToUse = new StreamDependencyDescriptor(this.descriptor, ordered);
Object result = doResolveDependency(descriptorToUse, this.beanName, null, null);
@@ -24,6 +24,8 @@ import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanCurrentlyInCreationException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.FactoryBeanNotInitializedException;
import org.springframework.core.AttributeAccessor;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
/**
@@ -61,6 +63,34 @@ public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanReg
}
}
/**
* Determine the bean type for a FactoryBean by inspecting its attributes for a
* {@link FactoryBean#OBJECT_TYPE_ATTRIBUTE} value.
* @param attributes the attributes to inspect
* @return a {@link ResolvableType} extracted from the attributes or
* {@code ResolvableType.NONE}
* @since 5.2
*/
ResolvableType getTypeForFactoryBeanFromAttributes(AttributeAccessor attributes) {
Object attribute = attributes.getAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE);
if (attribute instanceof ResolvableType resolvableType) {
return resolvableType;
}
if (attribute instanceof Class<?> clazz) {
return ResolvableType.forClass(clazz);
}
return ResolvableType.NONE;
}
/**
* Determine the FactoryBean object type from the given generic declaration.
* @param type the FactoryBean type
* @return the nested object type, or {@code NONE} if not resolvable
*/
ResolvableType getFactoryBeanGeneric(@Nullable ResolvableType type) {
return (type != null ? type.as(FactoryBean.class).getGeneric() : ResolvableType.NONE);
}
/**
* Obtain an object to expose from the given FactoryBean, if available
* in cached form. Quick check for minimal synchronization.
@@ -87,13 +87,8 @@ public class GenericBeanDefinition extends AbstractBeanDefinition {
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof GenericBeanDefinition that)) {
return false;
}
return (ObjectUtils.nullSafeEquals(this.parentName, that.parentName) && super.equals(other));
return (this == other || (other instanceof GenericBeanDefinition that &&
ObjectUtils.nullSafeEquals(this.parentName, that.parentName) && super.equals(other)));
}
@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.
@@ -102,16 +102,14 @@ public class LookupOverride extends MethodOverride {
@Override
public boolean equals(@Nullable Object other) {
if (!(other instanceof LookupOverride that) || !super.equals(other)) {
return false;
}
return (ObjectUtils.nullSafeEquals(this.method, that.method) &&
return (other instanceof LookupOverride that && super.equals(other) &&
ObjectUtils.nullSafeEquals(this.method, that.method) &&
ObjectUtils.nullSafeEquals(this.beanName, that.beanName));
}
@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-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.
@@ -106,14 +106,9 @@ public abstract class MethodOverride implements BeanMetadataElement {
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof MethodOverride that)) {
return false;
}
return (ObjectUtils.nullSafeEquals(this.methodName, that.methodName) &&
ObjectUtils.nullSafeEquals(this.source, that.source));
return (this == other || (other instanceof MethodOverride that &&
ObjectUtils.nullSafeEquals(this.methodName, that.methodName) &&
ObjectUtils.nullSafeEquals(this.source, that.source)));
}
@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.
@@ -104,13 +104,8 @@ public class MethodOverrides {
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof MethodOverrides that)) {
return false;
}
return this.overrides.equals(that.overrides);
return (this == other || (other instanceof MethodOverrides that &&
this.overrides.equals(that.overrides)));
}
@Override
@@ -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.
@@ -40,8 +40,8 @@ final class NullBean {
@Override
public boolean equals(@Nullable Object obj) {
return (this == obj || obj == null);
public boolean equals(@Nullable Object other) {
return (this == other || other == null);
}
@Override
@@ -216,16 +216,18 @@ public final class RegisteredBean {
* Resolve an autowired argument.
* @param descriptor the descriptor for the dependency (field/method/constructor)
* @param typeConverter the TypeConverter to use for populating arrays and collections
* @param autowiredBeans a Set that all names of autowired beans (used for
* @param autowiredBeanNames a Set that all names of autowired beans (used for
* resolving the given dependency) are supposed to be added to
* @return the resolved object, or {@code null} if none found
* @since 6.0.9
*/
@Nullable
public Object resolveAutowiredArgument(DependencyDescriptor descriptor, TypeConverter typeConverter,
Set<String> autowiredBeans) {
public Object resolveAutowiredArgument(
DependencyDescriptor descriptor, TypeConverter typeConverter, Set<String> autowiredBeanNames) {
return new ConstructorResolver((AbstractAutowireCapableBeanFactory) getBeanFactory())
.resolveAutowiredArgument(descriptor, getBeanName(), autowiredBeans, typeConverter, true);
.resolveAutowiredArgument(descriptor, descriptor.getDependencyType(),
getBeanName(), autowiredBeanNames, typeConverter, true);
}
@@ -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.
@@ -97,10 +97,8 @@ public class ReplaceOverride extends MethodOverride {
@Override
public boolean equals(@Nullable Object other) {
if (!(other instanceof ReplaceOverride that) || !super.equals(other)) {
return false;
}
return (ObjectUtils.nullSafeEquals(this.methodReplacerBeanName, that.methodReplacerBeanName) &&
return (other instanceof ReplaceOverride that && super.equals(other) &&
ObjectUtils.nullSafeEquals(this.methodReplacerBeanName, that.methodReplacerBeanName) &&
ObjectUtils.nullSafeEquals(this.typeIdentifiers, that.typeIdentifiers));
}
@@ -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.
@@ -156,15 +156,10 @@ public class MutableSortDefinition implements SortDefinition, Serializable {
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof SortDefinition otherSd)) {
return false;
}
return (getProperty().equals(otherSd.getProperty()) &&
isAscending() == otherSd.isAscending() &&
isIgnoreCase() == otherSd.isIgnoreCase());
return (this == other || (other instanceof SortDefinition that &&
getProperty().equals(that.getProperty()) &&
isAscending() == that.isAscending() &&
isIgnoreCase() == that.isIgnoreCase()));
}
@Override
@@ -1370,10 +1370,10 @@ class DefaultListableBeanFactoryTests {
lbf.registerBeanDefinition("rod2", bd2);
lbf.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());
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
@@ -1441,11 +1441,11 @@ class DefaultListableBeanFactoryTests {
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
@@ -1460,11 +1460,11 @@ class DefaultListableBeanFactoryTests {
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
@@ -1607,10 +1607,10 @@ class DefaultListableBeanFactoryTests {
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
@@ -1815,9 +1815,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
@@ -1980,6 +1980,16 @@ class DefaultListableBeanFactoryTests {
assertBeanNamesForType(FactoryBean.class, false, false);
}
@Test // gh-30987
void getBeanNamesForTypeWithFactoryBeanDefinedAsTargetType() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(TestRepositoryFactoryBean.class);
beanDefinition.setTargetType(ResolvableType.forClassWithGenerics(TestRepositoryFactoryBean.class,
CityRepository.class, Object.class, Object.class));
lbf.registerBeanDefinition("factoryBean", beanDefinition);
assertBeanNamesForType(TestRepositoryFactoryBean.class, true, false, "&factoryBean");
assertBeanNamesForType(CityRepository.class, true, false, "factoryBean");
}
/**
* Verifies that a dependency on a {@link FactoryBean} can <strong>not</strong>
* be autowired <em>by name</em>, as &amp; is an illegal character in
@@ -2004,10 +2014,10 @@ class DefaultListableBeanFactoryTests {
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
@@ -2071,10 +2081,10 @@ class DefaultListableBeanFactoryTests {
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
@@ -2337,20 +2347,20 @@ class DefaultListableBeanFactoryTests {
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
@@ -3068,6 +3078,25 @@ class DefaultListableBeanFactoryTests {
}
public static class TestRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extends Serializable>
extends RepositoryFactoryBeanSupport<T, S, ID> {
@Override
public T getObject() throws Exception {
throw new IllegalArgumentException("Should not be called");
}
@Override
public Class<?> getObjectType() {
throw new IllegalArgumentException("Should not be called");
}
}
public record City(String name) {}
public static class CityRepository implements Repository<City, Long> {}
public static class LazyInitFactory implements FactoryBean<Object> {
public boolean initialized = false;
@@ -71,6 +71,9 @@ import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.annotation.Order;
import org.springframework.core.testfixture.io.SerializationTestUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -128,6 +131,8 @@ public class AutowiredAnnotationBeanPostProcessorTests {
bean = bf.getBean("annotatedBean", ResourceInjectionBean.class);
assertThat(bean.getTestBean()).isSameAs(tb);
assertThat(bean.getTestBean2()).isSameAs(tb);
assertThat(bf.getDependenciesForBean("annotatedBean")).isEqualTo(new String[] {"testBean"});
}
@Test
@@ -150,10 +155,12 @@ public class AutowiredAnnotationBeanPostProcessorTests {
assertThat(bean.getTestBean()).isNull();
assertThat(bean.getTestBean2()).isNull();
assertThat(bean.getTestBean3()).isNull();
assertThat(bf.getDependenciesForBean("annotatedBean")).isEqualTo(new String[] {"testBean"});
}
@Test
void resourceInjectionWithSometimesNullBean() {
void resourceInjectionWithSometimesNullBeanEarly() {
RootBeanDefinition bd = new RootBeanDefinition(OptionalResourceInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
@@ -168,6 +175,55 @@ public class AutowiredAnnotationBeanPostProcessorTests {
assertThat(bean.getTestBean2()).isNull();
assertThat(bean.getTestBean3()).isNull();
SometimesNullFactoryMethods.active = false;
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isNull();
assertThat(bean.getTestBean2()).isNull();
assertThat(bean.getTestBean3()).isNull();
SometimesNullFactoryMethods.active = true;
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isNotNull();
assertThat(bean.getTestBean2()).isNotNull();
assertThat(bean.getTestBean3()).isNotNull();
SometimesNullFactoryMethods.active = true;
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isNotNull();
assertThat(bean.getTestBean2()).isNotNull();
assertThat(bean.getTestBean3()).isNotNull();
SometimesNullFactoryMethods.active = false;
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isNull();
assertThat(bean.getTestBean2()).isNull();
assertThat(bean.getTestBean3()).isNull();
SometimesNullFactoryMethods.active = false;
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isNull();
assertThat(bean.getTestBean2()).isNull();
assertThat(bean.getTestBean3()).isNull();
assertThat(bf.getDependenciesForBean("annotatedBean")).isEqualTo(new String[] {"testBean"});
}
@Test
void resourceInjectionWithSometimesNullBeanLate() {
RootBeanDefinition bd = new RootBeanDefinition(OptionalResourceInjectionBean.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
RootBeanDefinition tb = new RootBeanDefinition(SometimesNullFactoryMethods.class);
tb.setFactoryMethodName("createTestBean");
tb.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("testBean", tb);
SometimesNullFactoryMethods.active = true;
OptionalResourceInjectionBean bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isNotNull();
assertThat(bean.getTestBean2()).isNotNull();
assertThat(bean.getTestBean3()).isNotNull();
SometimesNullFactoryMethods.active = true;
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isNotNull();
@@ -192,17 +248,13 @@ public class AutowiredAnnotationBeanPostProcessorTests {
assertThat(bean.getTestBean2()).isNotNull();
assertThat(bean.getTestBean3()).isNotNull();
SometimesNullFactoryMethods.active = true;
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isNotNull();
assertThat(bean.getTestBean2()).isNotNull();
assertThat(bean.getTestBean3()).isNotNull();
SometimesNullFactoryMethods.active = false;
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
assertThat(bean.getTestBean()).isNull();
assertThat(bean.getTestBean2()).isNull();
assertThat(bean.getTestBean3()).isNull();
assertThat(bf.getDependenciesForBean("annotatedBean")).isEqualTo(new String[] {"testBean"});
}
@Test
@@ -231,10 +283,7 @@ public class AutowiredAnnotationBeanPostProcessorTests {
assertThat(bean.getNestedTestBean()).isSameAs(ntb);
assertThat(bean.getBeanFactory()).isSameAs(bf);
String[] depBeans = bf.getDependenciesForBean("annotatedBean");
assertThat(depBeans).hasSize(2);
assertThat(depBeans[0]).isEqualTo("testBean");
assertThat(depBeans[1]).isEqualTo("nestedTestBean");
assertThat(bf.getDependenciesForBean("annotatedBean")).isEqualTo(new String[] {"testBean", "nestedTestBean"});
}
@Test
@@ -375,6 +424,22 @@ public class AutowiredAnnotationBeanPostProcessorTests {
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
bf.destroySingleton("testBean");
bf.registerSingleton("testBeanX", tb);
bean = bf.getBean("annotatedBean", OptionalResourceInjectionBean.class);
assertThat(bean.getTestBean()).isSameAs(tb);
assertThat(bean.getTestBean2()).isSameAs(tb);
assertThat(bean.getTestBean3()).isSameAs(tb);
assertThat(bean.getTestBean4()).isSameAs(tb);
assertThat(bean.getIndexedTestBean()).isSameAs(itb);
assertThat(bean.getNestedTestBeans()).hasSize(2);
assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1);
assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2);
assertThat(bean.nestedTestBeansField).hasSize(2);
assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1);
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
bf.destroySingleton("testBeanX");
bean = bf.getBean("annotatedBean", OptionalResourceInjectionBean.class);
assertThat(bean.getTestBean()).isNull();
@@ -432,6 +497,22 @@ public class AutowiredAnnotationBeanPostProcessorTests {
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
bf.removeBeanDefinition("testBean");
bf.registerBeanDefinition("testBeanX", new RootBeanDefinition(TestBean.class));
bean = bf.getBean("annotatedBean", OptionalResourceInjectionBean.class);
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBeanX"));
assertThat(bean.getTestBean2()).isSameAs(bf.getBean("testBeanX"));
assertThat(bean.getTestBean3()).isSameAs(bf.getBean("testBeanX"));
assertThat(bean.getTestBean4()).isSameAs(bf.getBean("testBeanX"));
assertThat(bean.getIndexedTestBean()).isSameAs(itb);
assertThat(bean.getNestedTestBeans()).hasSize(2);
assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1);
assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2);
assertThat(bean.nestedTestBeansField).hasSize(2);
assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1);
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
bf.removeBeanDefinition("testBeanX");
bean = bf.getBean("annotatedBean", OptionalResourceInjectionBean.class);
assertThat(bean.getTestBean()).isNull();
@@ -696,6 +777,9 @@ public class AutowiredAnnotationBeanPostProcessorTests {
assertThat(bean.getTestBean4()).isSameAs(tb);
assertThat(bean.getNestedTestBean()).isSameAs(ntb);
assertThat(bean.getBeanFactory()).isSameAs(bf);
assertThat(bf.getDependenciesForBean("annotatedBean")).isEqualTo(
new String[] {"testBean", "nestedTestBean", ObjectUtils.identityToString(bf)});
}
@Test
@@ -717,6 +801,17 @@ public class AutowiredAnnotationBeanPostProcessorTests {
assertThat(bean.getBeanFactory()).isSameAs(bf);
bf.destroySingleton("nestedTestBean");
bf.registerSingleton("nestedTestBeanX", ntb);
bean = bf.getBean("annotatedBean", ConstructorResourceInjectionBean.class);
assertThat(bean.getTestBean()).isSameAs(tb);
assertThat(bean.getTestBean2()).isSameAs(tb);
assertThat(bean.getTestBean3()).isSameAs(tb);
assertThat(bean.getTestBean4()).isSameAs(tb);
assertThat(bean.getNestedTestBean()).isSameAs(ntb);
assertThat(bean.getBeanFactory()).isSameAs(bf);
bf.destroySingleton("nestedTestBeanX");
bean = bf.getBean("annotatedBean", ConstructorResourceInjectionBean.class);
assertThat(bean.getTestBean()).isSameAs(tb);
@@ -755,6 +850,17 @@ public class AutowiredAnnotationBeanPostProcessorTests {
assertThat(bean.getBeanFactory()).isSameAs(bf);
bf.removeBeanDefinition("nestedTestBean");
bf.registerBeanDefinition("nestedTestBeanX", new RootBeanDefinition(NestedTestBean.class));
bean = bf.getBean("annotatedBean", ConstructorResourceInjectionBean.class);
assertThat(bean.getTestBean()).isSameAs(tb);
assertThat(bean.getTestBean2()).isSameAs(tb);
assertThat(bean.getTestBean3()).isSameAs(tb);
assertThat(bean.getTestBean4()).isSameAs(tb);
assertThat(bean.getNestedTestBean()).isSameAs(bf.getBean("nestedTestBeanX"));
assertThat(bean.getBeanFactory()).isSameAs(bf);
bf.removeBeanDefinition("nestedTestBeanX");
bean = bf.getBean("annotatedBean", ConstructorResourceInjectionBean.class);
assertThat(bean.getTestBean()).isSameAs(tb);
@@ -858,6 +964,80 @@ public class AutowiredAnnotationBeanPostProcessorTests {
.satisfies(methodParameterDeclaredOn(ConstructorWithoutFallbackBean.class));
}
@Test
void constructorResourceInjectionWithSometimesNullBeanEarly() {
RootBeanDefinition bd = new RootBeanDefinition(ConstructorWithNullableArgument.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
RootBeanDefinition tb = new RootBeanDefinition(SometimesNullFactoryMethods.class);
tb.setFactoryMethodName("createTestBean");
tb.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("testBean", tb);
SometimesNullFactoryMethods.active = false;
ConstructorWithNullableArgument bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
assertThat(bean.getTestBean3()).isNull();
SometimesNullFactoryMethods.active = false;
bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
assertThat(bean.getTestBean3()).isNull();
SometimesNullFactoryMethods.active = true;
bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
assertThat(bean.getTestBean3()).isNotNull();
SometimesNullFactoryMethods.active = true;
bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
assertThat(bean.getTestBean3()).isNotNull();
SometimesNullFactoryMethods.active = false;
bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
assertThat(bean.getTestBean3()).isNull();
SometimesNullFactoryMethods.active = false;
bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
assertThat(bean.getTestBean3()).isNull();
assertThat(bf.getDependenciesForBean("annotatedBean")).isEqualTo(new String[] {"testBean"});
}
@Test
void constructorResourceInjectionWithSometimesNullBeanLate() {
RootBeanDefinition bd = new RootBeanDefinition(ConstructorWithNullableArgument.class);
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
RootBeanDefinition tb = new RootBeanDefinition(SometimesNullFactoryMethods.class);
tb.setFactoryMethodName("createTestBean");
tb.setScope(BeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("testBean", tb);
SometimesNullFactoryMethods.active = true;
ConstructorWithNullableArgument bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
assertThat(bean.getTestBean3()).isNotNull();
SometimesNullFactoryMethods.active = true;
bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
assertThat(bean.getTestBean3()).isNotNull();
SometimesNullFactoryMethods.active = false;
bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
assertThat(bean.getTestBean3()).isNull();
SometimesNullFactoryMethods.active = false;
bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
assertThat(bean.getTestBean3()).isNull();
SometimesNullFactoryMethods.active = true;
bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
assertThat(bean.getTestBean3()).isNotNull();
SometimesNullFactoryMethods.active = false;
bean = (ConstructorWithNullableArgument) bf.getBean("annotatedBean");
assertThat(bean.getTestBean3()).isNull();
assertThat(bf.getDependenciesForBean("annotatedBean")).isEqualTo(new String[] {"testBean"});
}
@Test
void constructorResourceInjectionWithCollectionAndNullFromFactoryBean() {
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(
@@ -2426,13 +2606,12 @@ public class AutowiredAnnotationBeanPostProcessorTests {
@Autowired(required = false)
private TestBean testBean;
private TestBean testBean2;
TestBean testBean2;
@Autowired
public void setTestBean2(TestBean testBean2) {
if (this.testBean2 != null) {
throw new IllegalStateException("Already called");
}
Assert.state(this.testBean != null, "Wrong initialization order");
Assert.state(this.testBean2 == null, "Already called");
this.testBean2 = testBean2;
}
@@ -2464,9 +2643,8 @@ public class AutowiredAnnotationBeanPostProcessorTests {
@Override
@Autowired
@SuppressWarnings("deprecation")
public void setTestBean2(TestBean testBean2) {
super.setTestBean2(testBean2);
this.testBean2 = testBean2;
}
@Autowired
@@ -2482,6 +2660,7 @@ public class AutowiredAnnotationBeanPostProcessorTests {
@Autowired
protected void initBeanFactory(BeanFactory beanFactory) {
Assert.state(this.baseInjected, "Wrong initialization order");
this.beanFactory = beanFactory;
}
@@ -2788,6 +2967,21 @@ public class AutowiredAnnotationBeanPostProcessorTests {
}
public static class ConstructorWithNullableArgument {
protected ITestBean testBean3;
@Autowired(required = false)
public ConstructorWithNullableArgument(@Nullable ITestBean testBean3) {
this.testBean3 = testBean3;
}
public ITestBean getTestBean3() {
return this.testBean3;
}
}
public static class ConstructorsCollectionResourceInjectionBean {
protected ITestBean testBean3;
@@ -3890,9 +4084,7 @@ public class AutowiredAnnotationBeanPostProcessorTests {
private RT obj;
protected void setObj(RT obj) {
if (this.obj != null) {
throw new IllegalStateException("Already called");
}
Assert.state(this.obj == null, "Already called");
this.obj = obj;
}
}
@@ -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.
@@ -53,7 +53,6 @@ public class LookupAnnotationTests {
@Test
public void testWithoutConstructorArg() {
AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean");
assertThat(bean).isNotNull();
Object expected = bean.get();
assertThat(expected.getClass()).isEqualTo(TestBean.class);
assertThat(beanFactory.getBean(BeanConsumer.class).abstractBean).isSameAs(bean);
@@ -62,7 +61,6 @@ public class LookupAnnotationTests {
@Test
public void testWithOverloadedArg() {
AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean");
assertThat(bean).isNotNull();
TestBean expected = bean.get("haha");
assertThat(expected.getClass()).isEqualTo(TestBean.class);
assertThat(expected.getName()).isEqualTo("haha");
@@ -72,7 +70,6 @@ public class LookupAnnotationTests {
@Test
public void testWithOneConstructorArg() {
AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean");
assertThat(bean).isNotNull();
TestBean expected = bean.getOneArgument("haha");
assertThat(expected.getClass()).isEqualTo(TestBean.class);
assertThat(expected.getName()).isEqualTo("haha");
@@ -82,7 +79,6 @@ public class LookupAnnotationTests {
@Test
public void testWithTwoConstructorArg() {
AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean");
assertThat(bean).isNotNull();
TestBean expected = bean.getTwoArguments("haha", 72);
assertThat(expected.getClass()).isEqualTo(TestBean.class);
assertThat(expected.getName()).isEqualTo("haha");
@@ -93,7 +89,6 @@ public class LookupAnnotationTests {
@Test
public void testWithThreeArgsShouldFail() {
AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean");
assertThat(bean).isNotNull();
assertThatExceptionOfType(AbstractMethodError.class).as("TestBean has no three arg constructor").isThrownBy(() ->
bean.getThreeArguments("name", 1, 2));
assertThat(beanFactory.getBean(BeanConsumer.class).abstractBean).isSameAs(bean);
@@ -102,7 +97,6 @@ public class LookupAnnotationTests {
@Test
public void testWithEarlyInjection() {
AbstractBean bean = beanFactory.getBean("beanConsumer", BeanConsumer.class).abstractBean;
assertThat(bean).isNotNull();
Object expected = bean.get();
assertThat(expected.getClass()).isEqualTo(TestBean.class);
assertThat(beanFactory.getBean(BeanConsumer.class).abstractBean).isSameAs(bean);
@@ -115,7 +109,6 @@ public class LookupAnnotationTests {
beanFactory.registerBeanDefinition("testBean", tbd);
AbstractBean bean = beanFactory.getBean("beanConsumer", BeanConsumer.class).abstractBean;
assertThat(bean).isNotNull();
Object expected = bean.get();
assertThat(expected).isNull();
assertThat(beanFactory.getBean(BeanConsumer.class).abstractBean).isSameAs(bean);
@@ -128,7 +121,6 @@ public class LookupAnnotationTests {
beanFactory.registerBeanDefinition("floatStore", new RootBeanDefinition(FloatStore.class));
NumberBean bean = (NumberBean) beanFactory.getBean("numberBean");
assertThat(bean).isNotNull();
assertThat(beanFactory.getBean(DoubleStore.class)).isSameAs(bean.getDoubleStore());
assertThat(beanFactory.getBean(FloatStore.class)).isSameAs(bean.getFloatStore());
}
@@ -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.

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