Compare commits

...

147 Commits

Author SHA1 Message Date
Spring Builds 8404537474 Release v6.0.11 2023-07-13 08:28:02 +00:00
Juergen Hoeller c873a597c7 Polishing 2023-07-12 19:21:44 +02:00
Stephane Nicoll 4dc93bc485 Avoid ambiguous call with BeanInstanceSupplier#withGenerator
Previously, BeanInstanceSupplier had three variants of the
`withGenerator` callback, one with a bi function, one with a function,
and with a supplier. This could lead to compilation failure when the
target type has a method with the same name and a number of arguments
that match another variant.

It turns out the supplier-based variant is only used a shortcut. This
commit deprecates it and update ghe code generation to use the function
instead.

Closes gh-29278
2023-07-12 17:23:08 +02:00
Sam Brannen 68f2b0ca59 Rely on auto-boxing in tests 2023-07-12 11:49:02 +02:00
Sam Brannen 1edc0d8002 Remove outdated Javadoc cross references 2023-07-12 10:36:02 +02:00
Sam Brannen a6dc020dc4 Remove since tag 2023-07-12 10:33:24 +02:00
Sam Brannen 07422d709e Remove getAutodetectMode() in MBeanExporter
After further consideration, the team has decided to remove the
getAutodetectMode() method since its return type conflicts with the
parameter type in setAutodetectMode(int), making it an invalid bean
property.

See gh-30855
2023-07-12 10:30:38 +02:00
Brian Clozel 768fc7e341 Upgrade to Reactor 2022.0.9
Closes gh-30871
2023-07-12 08:41:46 +02:00
Johnny Lim 8ecedb81b3 Add missing @Nullable annotations in ContentDisposition.Builder
Closes gh-30820
2023-07-11 19:58:29 +02:00
Juergen Hoeller f19433f2d8 Polishing 2023-07-11 18:01:07 +02:00
Juergen Hoeller 0b02a5e073 Avoid illegal reflective access in ContextOverridingClassLoader
Closes gh-22791
2023-07-11 17:51:55 +02:00
Juergen Hoeller e2b24f3c12 Improve diagnostics for LinkageError in case of ClassLoader mismatch
Closes gh-25940
2023-07-11 17:50:44 +02:00
Juergen Hoeller c375fb1f70 Deprecate setAllowResultAccessAfterCompletion and document it as broken
Closes gh-26557
2023-07-11 17:50:31 +02:00
Sam Brannen ad3e5425d4 Reduce WARN level log output during test AOT processing
Closes gh-30867
2023-07-11 16:35:07 +02:00
Sam Brannen c418118683 Polishing 2023-07-11 15:37:55 +02:00
Sam Brannen fb3f30832c Delete obsolete constant in AnnotationConfigUtils
See gh-30695
2023-07-11 15:33:28 +02:00
Sébastien Deleuze a275d942d2 Update STOMP documentation with the new guidelines
This commit changes the documentation to advise using
https://github.com/stomp-js/stompjs library on client
side and to configure by default WebSocket without
SockJS as browsers and proxies now have a pretty good
WebSocket support.

Closes gh-30857
2023-07-11 14:07:15 +02:00
rstoyanchev 20afa3265a Encapsulate full path initialization 2023-07-11 11:10:20 +01:00
Sam Brannen df50c8db3e Upgrade to micrometer-bom 1.10.9 and context-propagation 1.0.4
Closes gh-30860
2023-07-11 10:58:11 +02:00
Sam Brannen 679b668bbb Avoid need for reflection hints for MBeanExporter in native image
Prior to this commit, MBeanExporter used
org.springframework.core.Constants which used reflection to find
constant fields in the MBeanExporter class. Consequently, one had to
register reflection hints in order to use MBeanExporter in a GraalVM
native image.

This commit addresses this by replacing the use of the `Constants`
class with a simple java.util.Map which maps constant names to constant
values for the autodetect constants defined in MBeanExporter.

See gh-30851
Closes gh-30846
2023-07-10 19:01:44 +02:00
Sam Brannen 676daa990b Reorganize methods in MBeanExporter 2023-07-10 18:28:45 +02:00
Sam Brannen 7c7fa69558 Introduce getAutodetectMode() in MBeanExporter
Closes gh-30855
2023-07-10 18:26:33 +02:00
Sam Brannen 9ad92b16b0 Polish MBeanExporterTests 2023-07-10 18:20:39 +02:00
Juergen Hoeller 3b899fe7e2 Handle JDBC warnings in case of a statement exception as well
Closes gh-23106
2023-07-10 17:17:30 +02:00
Sébastien Deleuze c91041b675 Improve RSocketClientToServer*IntegrationTests reliability
Those new delays seems to make tests more reliable.

Closes gh-30844
2023-07-09 18:39:50 +02:00
Juergen Hoeller a17cf742b2 Polishing 2023-07-09 16:55:21 +02:00
Juergen Hoeller a102cd5f32 Tolerate isCandidateClass call with null as annotation type
Closes gh-30842
2023-07-09 16:52:54 +02:00
Juergen Hoeller d03b6aa1d6 Reinstate support for legacy JSR-250 Resource annotation
This merges the existing support for the legacy JSR-250 PostConstruct/PreDestroy annotations into CommonAnnotationBeanPostProcessor itself, opening up the InitDestroyAnnotationBeanPostProcessor base class for multiple init/destroy methods in a single post-processor. This removes the need for a separate JSR-250 InitDestroyAnnotationBeanPostProcessor in AnnotationConfigUtils.

Closes gh-30695
2023-07-09 16:52:17 +02:00
Sam Brannen b32b4f3a59 Polish DefaultSingletonBeanRegistryTests 2023-07-09 15:32:20 +02:00
Sam Brannen 502997d8e9 Further simplify DefaultSingletonBeanRegistry.isDependent()
See gh-30839
2023-07-09 15:32:20 +02:00
bnbakp0582 fb4ad2f3ba Simplify DefaultSingletonBeanRegistry.isDependent()
Move `alreadySeen` handling out of for-loop.

Closes gh-30839
2023-07-09 15:22:35 +02:00
Sébastien Deleuze b3de1b8e95 Use consistently *KotlinTests naming for Kotlin tests
Closes gh-30837
2023-07-08 11:02:20 +02:00
Sébastien Deleuze fb17e283d1 Replace @link by proper KDoc class reference in tests
Closes gh-30836
2023-07-08 10:44:40 +02:00
Juergen Hoeller 0b7a24fc14 Polishing 2023-07-08 00:58:20 +02:00
Sam Brannen 75b540f25c Update copyright headers 2023-07-07 15:03:09 +02:00
Sam Brannen 8bf79cc9c4 Polish contribution
Closes gh-30593
2023-07-07 15:02:38 +02:00
Heo YounHaeng 7ff80bc09d Fix example in Javadoc for MultipartBodyBuilder
See gh-30593
2023-07-07 15:00:47 +02:00
Valery Yatsynovich 8d6b0eb191 Fix typo in UriUtils Javadoc
Closes gh-30598
2023-07-07 14:50:46 +02:00
Juergen Hoeller 35c7e3960e Polishing 2023-07-07 13:46:57 +02:00
Juergen Hoeller 8e8c3f5a7c Polishing 2023-07-07 13:14:40 +02:00
Sam Brannen f50b230fb3 Emphasize that a ContextCustomizer must implement equals() and hashCode() 2023-07-06 17:28:24 +02:00
Sam Brannen 02ba06953f Polish grammar and formatting 2023-07-06 17:21:30 +02:00
Brian Clozel df22ba39f8 Warn against direct usage of Servlet API in WebFlux apps
Closes gh-28872
2023-07-06 17:11:24 +02:00
Sam Brannen 826776f321 Improve assertions in DefaultConversionServiceTests
Specifically, we now check the actual type of a converted collection in
various assertions to ensure that converters adhere to their contracts.
2023-07-06 14:01:26 +02:00
Sam Brannen 29f92c8a2b Polish observability docs 2023-07-06 13:27:20 +02:00
Brian Clozel 64e04b7bc2 Document limitations of Servlet Filter observations
This commit documents the fact that the Servlet Filter based
observations for MVC applications is limited by the Servlet Filter
contract in the first place. All processing and logging that happens
outside of the scope of the filter is not observed.
Log statements from the catalina engine (in the case of Tomcat), or any
container-specific infrastructure, is not covered by the
instrumentation.

Closes gh-29398
2023-07-06 13:09:41 +02:00
Sam Brannen ad05b02ff5 Update Javadoc for ObjectUtils.nullSafeConciseToString()
See gh-30810
2023-07-06 12:26:47 +02:00
Brian Clozel 430a24e6bc Further document ShallowEtagHeaderFilter limitations
This commit improves the documentation for the
`ShallowEtagHeaderFilter`, stating that it is only meant to support a
subset of conditional HTTP requests: GET requests with "If-None-Match"
headers. Other headers and state changing HTTP methods are not supported
here, as the filter only operates on the content of the response and has
no knowledge of the resource being served.

Closes gh-30517
2023-07-06 09:39:57 +02:00
Juergen Hoeller b7b9f2cb6b Expand tests for array to Collection/Set/List interface
See gh-28048
2023-07-05 20:15:10 +02:00
Sam Brannen b76664e757 Support arrays, collections, & maps in ObjectUtils.nullSafeConciseToString()
Prior to this commit, there was no explicit support for arrays,
collections, and maps in nullSafeConciseToString(). This lead to string
representations such as the following, regardless of whether the array,
collection, or map was empty.

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

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

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

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

Closes gh-30810
2023-07-05 17:01:01 +02:00
Sam Brannen ae13823851 Polishing 2023-07-05 15:33:15 +02:00
Sam Brannen 58b4286216 Polish MockHttpServletRequest 2023-07-05 13:59:47 +02:00
Sam Brannen df079feea9 Update copyright header 2023-07-05 13:48:13 +02:00
Patrick Strawderman 372282457f Use Collections.emptyEnumeration() where appropriate
Closes gh-30803
2023-07-05 13:47:11 +02:00
Juergen Hoeller 79df1da792 Clarify ReactiveTransactionManager exception declarations
Avoid misleading "throws TransactionException" declarations but preserve javadoc "@throws" notes for specific exceptions (with reactive propagation semantics).

Closes gh-30817
2023-07-05 12:51:45 +02:00
Juergen Hoeller c5771bc7c8 Discuss JdbcTransactionManager vs DataSourceTransactionManager
Includes doc update for 6.0 default exception translation etc.

Closes gh-30802
2023-07-05 12:51:10 +02:00
Juergen Hoeller 2365581265 Upgrade to HSQLDB 2.7.2 and WebJars Locator 0.53 2023-07-04 21:35:37 +02:00
Juergen Hoeller c1a8b9a14d Polishing 2023-07-04 21:24:35 +02:00
rstoyanchev e945e7426e Update AspectJ Javadoc link after permanent redirect 2023-07-04 18:09:55 +01:00
Juergen Hoeller f07b9fd217 Polishing 2023-07-04 16:52:44 +02:00
Juergen Hoeller 80a20488fd Make File/Path tests pass on Windows
See gh-30806
2023-07-04 16:52:39 +02:00
Juergen Hoeller 1dc9dffc70 Restore full representation of rejected value in FieldError.toString()
We would preferably use ObjectUtils.nullSafeConciseToString(rejectedValue) here but revert to the full nullSafeToString representation for strict backwards compatibility (programmatic toString calls as well as exception messages).

Closes gh-30799
2023-07-04 15:58:46 +02:00
Juergen Hoeller 0226580773 Refresh cached value after unexpected mismatch (e.g. null vs non-null)
In addition to the previously addressed removal of bean definitions, this is able to deal with prototype factory methods returning non-null after null or also null after non-null. Stale cached values are getting refreshed rather than bypassed.

Closes gh-30794
2023-07-04 15:58:30 +02:00
Sam Brannen 3ef1b7d83c Extend supported types in ObjectUtils.nullSafeConciseToString()
This commit extends the list of explicitly supported types in
ObjectUtils.nullSafeConciseToString() with the following.

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

Closes gh-30805
2023-07-04 13:27:47 +02:00
Sam Brannen 08bce69d3d Add tests for status quo in ObjectUtils.nullSafeConciseToString() 2023-07-04 13:15:56 +02:00
Yanming Zhou 56b60120fe Stop using deprecated AbstractArchiveTask.archivePath in Gradle build
This commit addresses the following warning in the build.

The AbstractArchiveTask.archivePath property has been deprecated. This
is scheduled to be removed in Gradle 9.0. Please use the archiveFile
property instead. See
https://docs.gradle.org/8.1.1/dsl/org.gradle.api.tasks.bundling.AbstractArchiveTask.html#org.gradle.api.tasks.bundling.AbstractArchiveTask:archivePath
for more details.

Closes gh-30697
2023-07-03 17:09:48 +02:00
Sam Brannen 1364a179a9 Upgrade to Gradle 8.2
Closes gh-30791
2023-07-03 16:24:34 +02:00
Sam Brannen 2161e865d7 Update copyright header 2023-07-03 15:53:43 +02:00
Vladyslav Baidak 0b2c2d04b2 Fix typo in Javadoc for BeanDefinitionDsl.kt
Closes gh-30798
2023-07-03 15:49:28 +02:00
Sam Brannen 07fe8eea83 Improve error message for wrong version of micrometer-observation
This commit also reverts to using ReflectionUtils.findMethod in order
to make the check more robust in case the Micrometer team refactors the
code base and declares the `getObservationRegistry()` method in a super
type.
2023-07-03 14:48:53 +02:00
Sam Brannen 5aac35b99e Check deps only once in MicrometerObservationRegistryTestExecutionListener
Prior to this commit, the required runtime dependencies were checked
via reflection each time an attempt was made to instantiate
MicrometerObservationRegistryTestExecutionListener.

Since it's sufficient to check for the presence of required runtime
dependencies only once, this commit caches the results of the
dependency checks in a static field.

This commit also introduces automated tests for the runtime dependency
checks in MicrometerObservationRegistryTestExecutionListener.

See gh-30747
2023-07-02 16:40:41 +02:00
Sam Brannen 040ea0a97c Remove @Aspect for classes containing only @Pointcut declarations in ref docs
Closes gh-30790
2023-07-01 17:41:55 +02:00
Sam Brannen 3c05679a97 Polishing
See gh-30762
2023-06-30 14:04:37 +02:00
Yanming Zhou d8729a7c67 Polish variable name in ReschedulingRunnable
As a variable name, `initDelay` is applicable for `scheduleAtFixedRate`
and `scheduleWithFixedDelay` but not for `schedule`.

Closes gh-30762
2023-06-30 13:57:46 +02:00
Sam Brannen c95426a616 Polishing 2023-06-30 13:55:34 +02:00
Juergen Hoeller 1e403d1606 Upgrade to Groovy 4.0.13 and Checkstyle 10.12.1 2023-06-30 12:58:28 +02:00
Juergen Hoeller f1567fb21a Raise beforeCompletion/afterCompletion exception log level to error
Closes gh-30776
2023-06-30 12:47:30 +02:00
Juergen Hoeller 60865eae4b Align ConcurrentMapCacheManager locking behavior with CaffeineCacheManager
Closes gh-30780
2023-06-30 10:39:53 +02:00
Juergen Hoeller 0c39fff831 Polishing 2023-06-29 18:04:08 +02:00
Juergen Hoeller e902f9551a Improve condition assertions 2023-06-29 12:05:07 +02:00
Juergen Hoeller 0a20c8a44a Restore defensive access to volatile field in getBeanClassName()
Closes gh-30773
2023-06-29 12:04:53 +02:00
Juergen Hoeller 3cb746c358 Consistently handle invocation exceptions in TypeProxyInvocationHandler
Closes gh-30764
2023-06-28 15:45:40 +02:00
Juergen Hoeller 6526e79eea Polishing 2023-06-26 19:28:38 +02:00
Juergen Hoeller b77d4d01c5 Adapt no-arg value from interface-based InvocationHandler callback
Closes gh-30756
2023-06-26 19:28:19 +02:00
Juergen Hoeller 599ac58baa Avoid arithmetic overflow for large delay/period values
Closes gh-30754
2023-06-26 19:28:08 +02:00
Juergen Hoeller 449174c7d4 Polishing 2023-06-26 12:35:09 +02:00
Juergen Hoeller 9266e6d29e Remove outdated javadoc notes on getMessage and printStackTrace
Closes gh-30748
2023-06-26 12:34:59 +02:00
Juergen Hoeller 062d701ae1 Consistently use mutable ArrayList for modulesToInstall vs modules
Closes gh-30751
2023-06-26 12:34:54 +02:00
Sébastien Deleuze 7137b22e6b Fix test compilation warnings
Closes gh-30753
2023-06-26 12:03:27 +02:00
Sam Brannen acb786d359 Improve logging in MicrometerObservationRegistryTestExecutionListener
Prior to this commit, dependency checks in the static initialization
block for MicrometerObservationRegistryTestExecutionListener resulted
in an ExceptionInInitializerError which led to verbose logging in
TestContextFailureHandler.

This commit improves the logging for missing dependencies in
MicrometerObservationRegistryTestExecutionListener by moving the
dependency checks to the constructor and by throwing a
NoClassDefFoundError instead of an IllegalStateException. This allows
TestContextFailureHandler to log a concise DEBUG message denoting that
the listener is being skipped due to missing dependencies.

This commit also now checks for the presence of
io.micrometer.context.ThreadLocalAccessor in addition to
io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor.

Furthermore, this commit now explicitly mentions the need for
io.micrometer:context-propagation in the error message.

The following demonstrate the generated DEBUB message when
ObservationThreadLocalAccessor and ThreadLocalAccessor are missing,
respectively.

Skipping candidate TestExecutionListener [org.springframework.test.context.observation.MicrometerObservationRegistryTestExecutionListener] due to a missing dependency. Specify custom TestExecutionListener classes or make the default TestExecutionListener classes and their required dependencies available. Offending class: io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor. MicrometerObservationRegistryTestExecutionListener requires io.micrometer:micrometer-observation:1.10.8 or higher and io.micrometer:context-propagation:1.0.3 or higher.

Skipping candidate TestExecutionListener [org.springframework.test.context.observation.MicrometerObservationRegistryTestExecutionListener] due to a missing dependency. Specify custom TestExecutionListener classes or make the default TestExecutionListener classes and their required dependencies available. Offending class: io.micrometer.context.ThreadLocalAccessor. MicrometerObservationRegistryTestExecutionListener requires io.micrometer:micrometer-observation:1.10.8 or higher and io.micrometer:context-propagation:1.0.3 or higher.

Closes gh-30747
2023-06-25 18:49:57 +02:00
Sam Brannen fa7300c1de Remove unused test code and polish 2023-06-25 15:31:15 +02:00
Sam Brannen db17a97ce8 Polishing 2023-06-25 15:18:59 +02:00
Johnny Lim 55f946c5a0 Fix incomplete AssertJ assertions
Closes gh-30743
2023-06-25 15:18:52 +02:00
Sam Brannen 3181dca5ef Support [package-]private init/destroy methods in AOT mode
Prior to this commit, private (and non-visible package-private)
init/destroy methods were not supported in AOT mode. The reason is that
such methods are tracked using their fully-qualified method names, and
the AOT support for init/destroy methods previously did not take
fully-qualified method names into account. In addition, the invocation
order of init/destroy methods differed vastly between standard JVM mode
and AOT mode.

This commit addresses these issues in the following ways.

- AbstractAutowireCapableBeanFactory.invokeCustomInitMethod(),
  DisposableBeanAdapter.determineDestroyMethod(), and
  BeanDefinitionPropertiesCodeGenerator.addInitDestroyHint() now parse
  fully-qualified method names to locate the correct init/destroy
  methods.

- AbstractAutowireCapableBeanFactory and DisposableBeanAdapter delegate
  to a new MethodDescriptor record which encapsulates the parsing of
  fully-qualified method names; however,
  BeanDefinitionPropertiesCodeGenerator duplicates this logic since it
  resides in a different package, and we do not currently want to make
  MethodDescriptor public.

- Init/destroy methods detected via annotations (such as @PostConstruct
  and @PreDestroy) are now invoked prior to init/destroy methods that
  are explicitly configured by name or convention. This aligns with the
  invocation order in standard JVM mode; however,
  InitializingBean#afterPropertiesSet() and DisposableBean#destroy()
  are still invoked before annotated init/destroy methods in AOT mode
  which differs from standard JVM mode.

- Unit and integration tests have been updated to test the revised
  behavior.

Closes gh-30692
2023-06-24 14:22:17 +02:00
Sam Brannen f86a69ebfb Update copyright headers and polish 2023-06-24 14:14:02 +02:00
Sam Brannen 6d63890c56 Use Assumptions.abort() where appropriate 2023-06-24 14:13:22 +02:00
Sam Brannen 489c89b912 Remove unused test code 2023-06-24 14:12:58 +02:00
Sam Brannen 39bc7566df Stop printing to System.out in tests 2023-06-24 14:10:12 +02:00
Sébastien Deleuze d3a249e34d Reduce the delay used for Coroutines in tests
Closes gh-30731
2023-06-23 14:17:56 +02:00
Sébastien Deleuze 81f1edbaf2 Change InvocableHandlerMethod#invokeSuspendingFunction return type
This commits changes the return type from Publisher<?> to
Object in order to avoid potential compatibility issues when
the Reactive Streams dependency is not in the classpath.

Closes gh-30716
2023-06-23 14:17:56 +02:00
Sébastien Deleuze 23ecb50137 Optimize KotlinReflectionParameterNameDiscoverer
This commit removes the intermediate list allocation.

Closes gh-30725
2023-06-23 14:17:56 +02:00
Sam Brannen 3d33d2baa9 Polish contribution 2023-06-23 13:35:16 +02:00
Xiu Hong Kooi 3745224646 Remove redundant assertion in ReactorServerHttpResponse constructor
See gh-30686
Closes gh-30696
2023-06-23 13:34:31 +02:00
Sam Brannen 3bf78d6f8c Fix AbstractMessageListenerContainer Javadoc regarding error log level
This commit updates AbstractMessageListenerContainer's Javadoc
regarding the log level used in invokeErrorHandler() so that the
documentation aligns with the implementation, namely that errors will
logged at WARN level if no ErrorHandler has been registered.

Closes gh-30730
2023-06-23 12:45:52 +02:00
Sam Brannen 563b2a8505 Polish AbstractMessageListenerContainer Javadoc 2023-06-23 12:44:57 +02:00
Sam Brannen 9ccbeec947 Ignore null message when introspecting resource cleanup failure
This commit fixes a regression introduced in conjunction with gh-27572.

See gh-30597
Closes gh-30729
2023-06-23 12:01:16 +02:00
Sam Brannen 29248dff15 Use correct ClassLoader in MicrometerObservationRegistryTestExecutionListener
In the original implementation of
MicrometerObservationRegistryTestExecutionListener I accidentally
imported JUnit 5's org.junit.platform.launcher.TestExecutionListener
instead Spring's
org.springframework.test.context.TestExecutionListener. The code
therefore attempts to use the ClassLoader for the JUnit Platform's
TestExecutionListener which may fail to see the required types. In
addition, if the JUnit Platform's TestExecutionListener is not on the
classpath, the attempt to access its ClassLoader will fail.

This commit addresses this by properly using the ClassLoader for
Spring's TestExecutionListener to detect dependencies of the
MicrometerObservationRegistryTestExecutionListener.

Closes gh-30726
2023-06-22 15:55:46 +02:00
Sam Brannen 65d450ab6d Document that RowCallbackHandler can be used w/ NamedParameterJdbcTemplate
Closes gh-30705
2023-06-22 15:27:41 +02:00
Johnny Lim 271f2dc665 Polish
This commit polishes a bit.

Closes gh-30691
2023-06-22 15:06:05 +02:00
Sam Brannen da323d3335 Update copyright headers 2023-06-22 14:55:23 +02:00
Sam Brannen 32f061a3e0 Polishing 2023-06-22 13:48:07 +02:00
Anton Gubenko 9b5cbc1334 Fix link text from "null" to "Component Classes" in Testing chapter
Closes gh-30714
2023-06-22 11:35:23 +02:00
Johnny Lim 40e378a5a6 Upgrade to me.champeau.jmh 0.7.1
Closes gh-30690
2023-06-22 11:31:32 +02:00
Juergen Hoeller 8eb0a0b94e Upgrade to Tomcat 10.1.10, Netty 4.1.94, Mockito 5.4 2023-06-21 18:52:02 +02:00
Juergen Hoeller 09cf489c6b Test for supportsEventType mismatch with unrelated event type
See gh-30712
2023-06-21 18:24:38 +02:00
Sam Brannen 9a5290ea27 Apply project conventions to formatting 2023-06-21 17:50:42 +02:00
Sam Brannen b3176208e2 Remove invalid Javadoc link 2023-06-21 17:36:36 +02:00
Juergen Hoeller 1dfe737d0e Avoid ResolvableType creation for interface/superclass check
See gh-30713
2023-06-21 17:17:58 +02:00
Juergen Hoeller dc2f513619 Correct supportsEventType checks for payload events
See gh-30712
2023-06-21 17:17:03 +02:00
Sam Brannen 0eb33d09ac Ensure package-private init/destroy methods are always invoked
Prior to this commit, if an init/destroy method was package-private and
declared in a superclass in a package different from the package in
which the registered bean resided, a local init/destroy method with the
same name would effectively "shadow" the method from the different
package, resulting in only the local init/destroy method being invoked.

This commit addresses this issue by tracking package-private init
methods from different packages using their fully-qualified method
names, analogous to the existing support for private init/destroy
methods.

Closes gh-30718
2023-06-21 16:18:19 +02:00
Sam Brannen f67f98a1a7 Polishing 2023-06-21 15:56:50 +02:00
Stephane Nicoll 6c42f374c8 Consistently set target type in generated code
Previously, a bean definition that is optimized AOT could have
different metadata based on whether its resolved type had a generic or
not. This is due to RootBeanDefinition taking either a Class or a
ResolvableType doing fundamentally different things. While the former
sets the bean class which is to little use with an instance supplier,
the latter specifies the target type of the bean.

This commit sets the target type of the bean, using the existing
setter methods that take either a class or a ResolvableType and set the
same attribute consistently.

Closes gh-30689
2023-06-21 14:45:19 +02:00
Sam Brannen 4879b56bb9 Test status quo for private init/destroy methods in AOT mode
This commit only tests which init/destroy methods are "registered" in
AOT mode.

This commit does NOT test that the registered methods can actually be
invoked in AOT mode: that will be addressed in a separate commit.

See gh-30692
2023-06-21 14:19:13 +02:00
Sam Brannen 2fd83aa764 Polish InitDestroyAnnotationBeanPostProcessor internals, etc. 2023-06-21 14:19:00 +02:00
Juergen Hoeller 93218a06ba Cache hasUnresolvableGenerics result for repeated checks
Closes gh-30713
2023-06-21 13:16:04 +02:00
Juergen Hoeller 714c3c59eb Accept unresolvable generics as long as raw event class matches
Closes gh-30712
2023-06-21 13:15:35 +02:00
Juergen Hoeller adcdefce43 Upgrade to Jetty 12.0.0.beta2 2023-06-21 09:49:26 +02:00
Juergen Hoeller 049a024dea Deprecate RootBeanDefinition(ResolvableType) constructor
Closes gh-30704
2023-06-21 09:48:00 +02:00
Juergen Hoeller 20bbebb299 Ensure Spring LogFactory contains all public methods from Apache LogFactory
Closes gh-30668
2023-06-21 09:47:16 +02:00
Sébastien Deleuze 26f006509f Polish CoWebFilter javadoc 2023-06-20 20:56:47 +02:00
Stephane Nicoll 089503aab7 Fix typo 2023-06-20 17:32:24 +02:00
Stephane Nicoll 74155e3d88 Do not invoke AspectJ hints generation if AspectJ is not present
See gh-28711
2023-06-20 17:26:08 +02:00
Sam Brannen 564f33d5ef Polishing 2023-06-20 15:47:41 +02:00
Stephane Nicoll 83acd5b050 Document JPA configuration best practices with AOT
Closes gh-30498
2023-06-20 15:21:12 +02:00
Sam Brannen 67798a7b52 Fix CSS classes in Javadoc HTML tables due to upgrade to Java 17
Closes gh-30701
2023-06-20 13:24:03 +02:00
Stephane Nicoll 2b981651e1 Merge pull request #30601 from cwatzl
* pr/30601:
  Update copyright year of changed file
  Flag PathResourceResolver#resolve*Internal as @Nullable

Closes gh-30601
2023-06-19 15:47:12 +02:00
Stephane Nicoll 294cdba80c Update copyright year of changed file
See gh-30601
2023-06-19 15:44:04 +02:00
cwatzl 072a86149d Flag PathResourceResolver#resolve*Internal as @Nullable
See gh-30601
2023-06-19 15:37:16 +02:00
Sam Brannen 8bb4c167e4 Polishing
See gh-30280
2023-06-17 13:36:37 +02:00
Juergen Hoeller dff7aa4d4b Fall back to type-based creation if no bean of the given name exists
Closes gh-30683
2023-06-17 11:38:57 +02:00
Juergen Hoeller c634acd9ff Recognize error code 2628 as data integrity violation (MSSQL 2019)
Closes gh-30681
2023-06-17 11:38:41 +02:00
Sam Brannen ed5c19f53e Document that @Sql requires spring-jdbc & spring-tx on the classpath
Closes gh-30280
2023-06-16 13:39:35 +02:00
Sébastien Deleuze 03420f811b Add reflection hints for AspectJ advice methods
Closes gh-28711
2023-06-16 11:33:57 +02:00
Sam Brannen 4565bcd757 Update copyright headers 2023-06-15 16:19:58 +02:00
Sam Brannen f22f439a68 Suppress deprecation warning due to upgrade to context-propagation 1.0.3
See gh-30657
2023-06-15 16:18:38 +02:00
Sam Brannen 5672284f53 Remove code duplication in RootBeanDefinition 2023-06-15 16:11:40 +02:00
Spring Builds 367f381fea Next development version (v6.0.11-SNAPSHOT) 2023-06-15 08:31:33 +00:00
293 changed files with 4722 additions and 3246 deletions
+3 -3
View File
@@ -8,7 +8,7 @@ plugins {
id 'com.github.ben-manes.versions' version '0.46.0'
id 'com.github.johnrengelman.shadow' version '8.1.1' apply false
id 'de.undercouch.download' version '5.4.0'
id 'me.champeau.jmh' version '0.7.0' apply false
id 'me.champeau.jmh' version '0.7.1' apply false
}
ext {
@@ -75,7 +75,7 @@ configure([rootProject] + javaProjects) { project ->
}
checkstyle {
toolVersion = "10.12.0"
toolVersion = "10.12.1"
configDirectory.set(rootProject.file("src/checkstyle"))
}
@@ -116,7 +116,7 @@ configure([rootProject] + javaProjects) { project ->
"https://www.ibm.com/docs/api/v1/content/SSEQTP_8.5.5/com.ibm.websphere.javadoc.doc/web/apidocs/", // com.ibm.*
"https://docs.jboss.org/jbossas/javadoc/4.0.5/connector/", // org.jboss.resource.*
"https://docs.jboss.org/hibernate/orm/5.6/javadocs/",
"https://www.eclipse.org/aspectj/doc/released/aspectj5rt-api/",
"https://eclipse.dev/aspectj/doc/released/aspectj5rt-api",
"https://www.quartz-scheduler.org/api/2.3.0/",
"https://www.javadoc.io/doc/com.fasterxml.jackson.core/jackson-core/2.14.1/",
"https://www.javadoc.io/doc/com.fasterxml.jackson.core/jackson-databind/2.14.1/",
@@ -154,7 +154,6 @@ Java::
----
package com.xyz;
@Aspect
public class Pointcuts {
@Pointcut("execution(public * *(..))")
@@ -179,7 +178,6 @@ Kotlin::
----
package com.xyz
@Aspect
class Pointcuts {
@Pointcut("execution(public * *(..))")
@@ -211,9 +209,9 @@ pointcut matching.
When working with enterprise applications, developers often have the need to refer to
modules of the application and particular sets of operations from within several aspects.
We recommend defining a dedicated aspect that captures commonly used _named pointcut_
expressions for this purpose. Such an aspect typically resembles the following
`CommonPointcuts` example (though what you name the aspect is up to you):
We recommend defining a dedicated class that captures commonly used _named pointcut_
expressions for this purpose. Such a class typically resembles the following
`CommonPointcuts` example (though what you name the class is up to you):
[tabs]
======
@@ -223,10 +221,8 @@ Java::
----
package com.xyz;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class CommonPointcuts {
/**
@@ -287,10 +283,8 @@ Kotlin::
----
package com.xyz
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Pointcut
@Aspect
class CommonPointcuts {
/**
@@ -346,9 +340,9 @@ Kotlin::
----
======
You can refer to the pointcuts defined in such an aspect anywhere you need a pointcut
expression by referencing the fully-qualified name of the `@Aspect` class combined with
the `@Pointcut` method's name. For example, to make the service layer transactional, you
You can refer to the pointcuts defined in such a class anywhere you need a pointcut
expression by referencing the fully-qualified name of the class combined with the
`@Pointcut` method's name. For example, to make the service layer transactional, you
could write the following which references the
`com.xyz.CommonPointcuts.businessService()` _named pointcut_:
@@ -318,6 +318,51 @@ Java::
----
======
[[aot.bestpractices.jpa]]
=== JPA
The JPA persistence unit has to be known upfront for certain optimizations to apply. Consider the following basic example:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
@Bean
LocalContainerEntityManagerFactoryBean customDBEntityManagerFactory(DataSource dataSource) {
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setPackagesToScan("com.example.app");
return factoryBean;
}
----
======
To make sure the scanning occurs ahead of time, a `PersistenceManagedTypes` bean must be declared and used by the
factory bean definition, as shown by the following example:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
@Bean
PersistenceManagedTypes persistenceManagedTypes(ResourceLoader resourceLoader) {
return new PersistenceManagedTypesScanner(resourceLoader)
.scan("com.example.app");
}
@Bean
LocalContainerEntityManagerFactoryBean customDBEntityManagerFactory(DataSource dataSource, PersistenceManagedTypes managedTypes) {
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setManagedTypes(managedTypes);
return factoryBean;
}
----
======
[[aot.hints]]
== Runtime Hints
@@ -151,10 +151,10 @@ Kotlin::
----
======
The last example we show here is for typical JDBC support. You could have the
`DataSource` injected into an initialization method or a constructor, where you would create a
`JdbcTemplate` and other data access support classes (such as `SimpleJdbcCall` and others) by using
this `DataSource`. The following example autowires a `DataSource`:
The last example we show here is for typical JDBC support. You could have the `DataSource`
injected into an initialization method or a constructor, where you would create a `JdbcTemplate`
and other data access support classes (such as `SimpleJdbcCall` and others) by using this
`DataSource`. The following example autowires a `DataSource`:
[tabs]
======
@@ -9,13 +9,13 @@ to the database.
[[jdbc-batch-classic]]
== Basic Batch Operations with `JdbcTemplate`
You accomplish `JdbcTemplate` batch processing by implementing two methods of a special
interface, `BatchPreparedStatementSetter`, and passing that implementation in as the second parameter
You accomplish `JdbcTemplate` batch processing by implementing two methods of a special interface,
`BatchPreparedStatementSetter`, and passing that implementation in as the second parameter
in your `batchUpdate` method call. You can use the `getBatchSize` method to provide the size of
the current batch. You can use the `setValues` method to set the values for the parameters of
the prepared statement. This method is called the number of times that you
specified in the `getBatchSize` call. The following example updates the `t_actor` table
based on entries in a list, and the entire list is used as the batch:
the prepared statement. This method is called the number of times that you specified in the
`getBatchSize` call. The following example updates the `t_actor` table based on entries in a list,
and the entire list is used as the batch:
[tabs]
======
@@ -10,7 +10,7 @@ This section covers:
* xref:data-access/jdbc/connections.adoc#jdbc-SingleConnectionDataSource[Using `SingleConnectionDataSource`]
* xref:data-access/jdbc/connections.adoc#jdbc-DriverManagerDataSource[Using `DriverManagerDataSource`]
* xref:data-access/jdbc/connections.adoc#jdbc-TransactionAwareDataSourceProxy[Using `TransactionAwareDataSourceProxy`]
* xref:data-access/jdbc/connections.adoc#jdbc-DataSourceTransactionManager[Using `DataSourceTransactionManager`]
* xref:data-access/jdbc/connections.adoc#jdbc-DataSourceTransactionManager[Using `DataSourceTransactionManager` / `JdbcTransactionManager`]
[[jdbc-datasource]]
@@ -125,8 +125,12 @@ The following example shows C3P0 configuration:
== Using `DataSourceUtils`
The `DataSourceUtils` class is a convenient and powerful helper class that provides
`static` methods to obtain connections from JNDI and close connections if necessary. It
supports thread-bound connections with, for example, `DataSourceTransactionManager`.
`static` methods to obtain connections from JNDI and close connections if necessary.
It supports a thread-bound JDBC `Connection` with `DataSourceTransactionManager` but
also with `JtaTransactionManager` and `JpaTransactionManager`.
Note that `JdbcTemplate` implies `DataSourceUtils` connection access, using it
behind every JDBC operation, implicitly participating in an ongoing transaction.
[[jdbc-SmartDataSource]]
@@ -165,7 +169,6 @@ In contrast to `DriverManagerDataSource`, it reuses the same connection all the
avoiding excessive creation of physical connections.
[[jdbc-DriverManagerDataSource]]
== Using `DriverManagerDataSource`
@@ -201,29 +204,44 @@ javadoc for more details.
[[jdbc-DataSourceTransactionManager]]
== Using `DataSourceTransactionManager`
== Using `DataSourceTransactionManager` / `JdbcTransactionManager`
The `DataSourceTransactionManager` class is a `PlatformTransactionManager`
implementation for single JDBC data sources. It binds a JDBC connection from the
specified data source to the currently executing thread, potentially allowing for one
thread connection per data source.
implementation for a single JDBC `DataSource`. It binds a JDBC `Connection`
from the specified `DataSource` to the currently executing thread, potentially
allowing for one thread-bound `Connection` per `DataSource`.
Application code is required to retrieve the JDBC connection through
`DataSourceUtils.getConnection(DataSource)` instead of Jakarta EE's standard
Application code is required to retrieve the JDBC `Connection` through
`DataSourceUtils.getConnection(DataSource)` instead of Java EE's standard
`DataSource.getConnection`. It throws unchecked `org.springframework.dao` exceptions
instead of checked `SQLExceptions`. All framework classes (such as `JdbcTemplate`) use this
strategy implicitly. If not used with this transaction manager, the lookup strategy
behaves exactly like the common one. Thus, it can be used in any case.
instead of checked `SQLExceptions`. All framework classes (such as `JdbcTemplate`) use
this strategy implicitly. If not used with a transaction manager, the lookup strategy
behaves exactly like `DataSource.getConnection` and can therefore be used in any case.
The `DataSourceTransactionManager` class supports custom isolation levels and timeouts
that get applied as appropriate JDBC statement query timeouts. To support the latter,
application code must either use `JdbcTemplate` or call the
`DataSourceUtils.applyTransactionTimeout(..)` method for each created statement.
The `DataSourceTransactionManager` class supports savepoints (`PROPAGATION_NESTED`),
custom isolation levels, and timeouts that get applied as appropriate JDBC statement
query timeouts. To support the latter, application code must either use `JdbcTemplate` or
call the `DataSourceUtils.applyTransactionTimeout(..)` method for each created statement.
You can use this implementation instead of `JtaTransactionManager` in the single-resource
case, as it does not require the container to support JTA. Switching between
both is just a matter of configuration, provided you stick to the required connection lookup
pattern. JTA does not support custom isolation levels.
You can use `DataSourceTransactionManager` instead of `JtaTransactionManager` in the
single-resource case, as it does not require the container to support a JTA transaction
coordinator. Switching between these transaction managers is just a matter of configuration,
provided you stick to the required connection lookup pattern. Note that JTA does not support
savepoints or custom isolation levels and has a different timeout mechanism but otherwise
exposes similar behavior in terms of JDBC resources and JDBC commit/rollback management.
NOTE: As of 5.3, Spring provides an extended `JdbcTransactionManager` variant which adds
exception translation capabilities on commit/rollback (aligned with `JdbcTemplate`).
Where `DataSourceTransactionManager` will only ever throw `TransactionSystemException`
(analogous to JTA), `JdbcTransactionManager` translates database locking failures etc to
corresponding `DataAccessException` subclasses. Note that application code needs to be
prepared for such exceptions, not exclusively expecting `TransactionSystemException`.
In scenarios where that is the case, `JdbcTransactionManager` is the recommended choice.
In terms of exception behavior, `JdbcTransactionManager` is roughly equivalent to
`JpaTransactionManager` and also to `R2dbcTransactionManager`, serving as an immediate
companion/replacement for each other. `DataSourceTransactionManager` on the other hand
is equivalent to `JtaTransactionManager` and can serve as a direct replacement there.
@@ -718,12 +718,22 @@ See also xref:data-access/jdbc/core.adoc#jdbc-JdbcTemplate-idioms[`JdbcTemplate`
between ``SQLException``s and Spring's own `org.springframework.dao.DataAccessException`,
which is agnostic in regard to data access strategy. Implementations can be generic (for
example, using SQLState codes for JDBC) or proprietary (for example, using Oracle error
codes) for greater precision.
codes) for greater precision. This exception translation mechanism is used behind the
the common `JdbcTemplate` and `JdbcTransactionManager` entry points which do not
propagate `SQLException` but rather `DataAccessException`.
NOTE: As of 6.0, the default exception translator is `SQLExceptionSubclassTranslator`,
detecting JDBC 4 `SQLException` subclasses with a few extra checks, and with a fallback
to `SQLState` introspection through `SQLStateSQLExceptionTranslator`. This is usually
sufficient for common database access and does not require vendor-specific detection.
For backwards compatibility, consider using `SQLErrorCodeSQLExceptionTranslator` as
described below, potentially with custom error code mappings.
`SQLErrorCodeSQLExceptionTranslator` is the implementation of `SQLExceptionTranslator`
that is used by default. This implementation uses specific vendor codes. It is more
precise than the `SQLState` implementation. The error code translations are based on
codes held in a JavaBean type class called `SQLErrorCodes`. This class is created and
that is used by default when a file named `sql-error-codes.xml` is present in the root
of the classpath. This implementation uses specific vendor codes. It is more precise than
`SQLState` or `SQLException` subclass translation. The error code translations are based
on codes held in a JavaBean type class called `SQLErrorCodes`. This class is created and
populated by an `SQLErrorCodesFactory`, which (as the name suggests) is a factory for
creating `SQLErrorCodes` based on the contents of a configuration file named
`sql-error-codes.xml`. This file is populated with vendor codes and based on the
@@ -744,8 +754,8 @@ The `SQLErrorCodeSQLExceptionTranslator` applies matching rules in the following
translator. If this translation is not available, the next fallback translator is
the `SQLStateSQLExceptionTranslator`.
NOTE: The `SQLErrorCodesFactory` is used by default to define `Error` codes and custom exception
translations. They are looked up in a file named `sql-error-codes.xml` from the
NOTE: The `SQLErrorCodesFactory` is used by default to define error codes and custom
exception translations. They are looked up in a file named `sql-error-codes.xml` from the
classpath, and the matching `SQLErrorCodes` instance is located based on the database
name from the database metadata of the database in use.
@@ -784,12 +794,12 @@ Kotlin::
----
======
In the preceding example, the specific error code (`-12345`) is translated, while other errors are
left to be translated by the default translator implementation. To use this custom
translator, you must pass it to the `JdbcTemplate` through the method
`setExceptionTranslator`, and you must use this `JdbcTemplate` for all of the data access
processing where this translator is needed. The following example shows how you can use this custom
translator:
In the preceding example, the specific error code (`-12345`) is translated while
other errors are left to be translated by the default translator implementation.
To use this custom translator, you must pass it to the `JdbcTemplate` through the
method `setExceptionTranslator`, and you must use this `JdbcTemplate` for all of the
data access processing where this translator is needed. The following example shows
how you can use this custom translator:
[tabs]
======
@@ -800,7 +810,6 @@ Java::
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
// create a JdbcTemplate and set data source
this.jdbcTemplate = new JdbcTemplate();
this.jdbcTemplate.setDataSource(dataSource);
@@ -809,7 +818,6 @@ Java::
CustomSQLErrorCodesTranslator tr = new CustomSQLErrorCodesTranslator();
tr.setDataSource(dataSource);
this.jdbcTemplate.setExceptionTranslator(tr);
}
public void updateShippingCharge(long orderId, long pct) {
@@ -3,30 +3,30 @@
The Spring Framework's JDBC abstraction framework consists of four different packages:
* `core`: The `org.springframework.jdbc.core` package contains the `JdbcTemplate` class and its
various callback interfaces, plus a variety of related classes. A subpackage named
`org.springframework.jdbc.core.simple` contains the `SimpleJdbcInsert` and
* `core`: The `org.springframework.jdbc.core` package contains the `JdbcTemplate` class
and its various callback interfaces, plus a variety of related classes. A subpackage
named `org.springframework.jdbc.core.simple` contains the `SimpleJdbcInsert` and
`SimpleJdbcCall` classes. Another subpackage named
`org.springframework.jdbc.core.namedparam` contains the `NamedParameterJdbcTemplate`
class and the related support classes. See xref:data-access/jdbc/core.adoc[Using the JDBC Core Classes to Control Basic JDBC Processing and Error Handling], xref:data-access/jdbc/advanced.adoc[JDBC Batch Operations], and
xref:data-access/jdbc/simple.adoc[Simplifying JDBC Operations with the `SimpleJdbc` Classes].
* `datasource`: The `org.springframework.jdbc.datasource` package contains a utility class for easy
`DataSource` access and various simple `DataSource` implementations that you can use for
testing and running unmodified JDBC code outside of a Jakarta EE container. A subpackage
named `org.springfamework.jdbc.datasource.embedded` provides support for creating
* `datasource`: The `org.springframework.jdbc.datasource` package contains a utility class
for easy `DataSource` access and various simple `DataSource` implementations that you can
use for testing and running unmodified JDBC code outside of a Jakarta EE container. A subpackage
named `org.springframework.jdbc.datasource.embedded` provides support for creating
embedded databases by using Java database engines, such as HSQL, H2, and Derby. See
xref:data-access/jdbc/connections.adoc[Controlling Database Connections] and xref:data-access/jdbc/embedded-database-support.adoc[Embedded Database Support].
* `object`: The `org.springframework.jdbc.object` package contains classes that represent RDBMS
queries, updates, and stored procedures as thread-safe, reusable objects. See
* `object`: The `org.springframework.jdbc.object` package contains classes that represent
RDBMS queries, updates, and stored procedures as thread-safe, reusable objects. See
xref:data-access/jdbc/object.adoc[Modeling JDBC Operations as Java Objects]. This approach is modeled by JDO, although objects returned by queries
are naturally disconnected from the database. This higher-level of JDBC abstraction
depends on the lower-level abstraction in the `org.springframework.jdbc.core` package.
* `support`: The `org.springframework.jdbc.support` package provides `SQLException` translation
functionality and some utility classes. Exceptions thrown during JDBC processing are
translated to exceptions defined in the `org.springframework.dao` package. This means
* `support`: The `org.springframework.jdbc.support` package provides `SQLException`
translation functionality and some utility classes. Exceptions thrown during JDBC processing
are translated to exceptions defined in the `org.springframework.dao` package. This means
that code using the Spring JDBC abstraction layer does not need to implement JDBC or
RDBMS-specific error handling. All translated exceptions are unchecked, which gives you
the option of catching the exceptions from which you can recover while letting other
@@ -495,7 +495,7 @@ features supported by Spring, usually in a vendor-specific manner:
* Applying specific transaction semantics (such as custom isolation level or transaction
timeout)
* Retrieving the transactional JDBC `Connection` (for exposure to JDBC-based DAOs)
* Advanced translation of `PersistenceExceptions` to Spring `DataAccessExceptions`
* Advanced translation of `PersistenceException` to Spring's `DataAccessException`
This is particularly valuable for special transaction semantics and for advanced
translation of exception. The default implementation (`DefaultJpaDialect`) does
@@ -718,19 +718,15 @@ javadoc for more details.
=== Using `R2dbcTransactionManager`
The `R2dbcTransactionManager` class is a `ReactiveTransactionManager` implementation for
single R2DBC data sources. It binds an R2DBC connection from the specified connection factory
to the subscriber `Context`, potentially allowing for one subscriber connection for each
connection factory.
a single R2DBC `ConnectionFactory`. It binds an R2DBC `Connection` from the specified
`ConnectionFactory` to the subscriber `Context`, potentially allowing for one subscriber
`Connection` for each `ConnectionFactory`.
Application code is required to retrieve the R2DBC connection through
Application code is required to retrieve the R2DBC `Connection` through
`ConnectionFactoryUtils.getConnection(ConnectionFactory)`, instead of R2DBC's standard
`ConnectionFactory.create()`.
All framework classes (such as `DatabaseClient`) use this strategy implicitly.
If not used with this transaction manager, the lookup strategy behaves exactly like the common one.
Thus, it can be used in any case.
The `R2dbcTransactionManager` class supports custom isolation levels that get applied to the connection.
`ConnectionFactory.create()`. All framework classes (such as `DatabaseClient`) use this
strategy implicitly. If not used with a transaction manager, the lookup strategy behaves
exactly like `ConnectionFactory.create()` and can therefore be used in any case.
@@ -124,7 +124,6 @@ In XML configuration, the `<tx:annotation-driven/>` tag provides similar conveni
----
<1> The line that makes the bean instance transactional.
TIP: You can omit the `transaction-manager` attribute in the `<tx:annotation-driven/>`
tag if the bean name of the `TransactionManager` that you want to wire in has the name
`transactionManager`. If the `TransactionManager` bean that you want to dependency-inject
@@ -522,17 +521,17 @@ The following listing shows the bean declarations:
----
<tx:annotation-driven/>
<bean id="transactionManager1" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<bean id="transactionManager1" class="org.springframework.jdbc.support.JdbcTransactionManager">
...
<qualifier value="order"/>
</bean>
<bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<bean id="transactionManager2" class="org.springframework.jdbc.support.JdbcTransactionManager">
...
<qualifier value="account"/>
</bean>
<bean id="transactionManager3" class="org.springframework.data.r2dbc.connectionfactory.R2dbcTransactionManager">
<bean id="transactionManager3" class="org.springframework.data.r2dbc.connection.R2dbcTransactionManager">
...
<qualifier value="reactive-account"/>
</bean>
@@ -2,8 +2,8 @@
= Observability Support
Micrometer defines an https://micrometer.io/docs/observation[Observation concept that enables both Metrics and Traces] in applications.
Metrics support offers a way to create timers, gauges or counters for collecting statistics about the runtime behavior of your application.
Metrics can help you to track error rates, usage patterns, performance and more.
Metrics support offers a way to create timers, gauges, or counters for collecting statistics about the runtime behavior of your application.
Metrics can help you to track error rates, usage patterns, performance, and more.
Traces provide a holistic view of an entire system, crossing application boundaries; you can zoom in on particular user requests and follow their entire completion across applications.
Spring Framework instruments various parts of its own codebase to publish observations if an `ObservationRegistry` is configured.
@@ -36,16 +36,16 @@ https://micrometer.io/docs/concepts#_naming_meters[to the format preferred by th
[[observability.concepts]]
== Micrometer Observation concepts
If you are not familiar with Micrometer Observation, here's a quick summary of the new concepts you should know about.
If you are not familiar with Micrometer Observation, here's a quick summary of the concepts you should know about.
* `Observation` is the actual recording of something happening in your application. This is processed by `ObservationHandler` implementations to produce metrics or traces.
* Each observation has a corresponding `ObservationContext` implementation; this type holds all the relevant information for extracting metadata for it.
In the case of an HTTP server observation, the context implementation could hold the HTTP request, the HTTP response, any Exception thrown during processing...
* Each `Observation` holds `KeyValues` metadata. In the case of a server HTTP observation, this could be the HTTP request method, the HTTP response status...
In the case of an HTTP server observation, the context implementation could hold the HTTP request, the HTTP response, any exception thrown during processing, and so forth.
* Each `Observation` holds `KeyValues` metadata. In the case of an HTTP server observation, this could be the HTTP request method, the HTTP response status, and so forth.
This metadata is contributed by `ObservationConvention` implementations which should declare the type of `ObservationContext` they support.
* `KeyValues` are said to be "low cardinality" if there is a low, bounded number of possible values for the `KeyValue` tuple (HTTP method is a good example).
Low cardinality values are contributed to metrics only.
High cardinality values are on the other hand unbounded (for example, HTTP request URIs) and are only contributed to Traces.
Conversely, "high cardinality" values are unbounded (for example, HTTP request URIs) and are only contributed to traces.
* An `ObservationDocumentation` documents all observations in a particular domain, listing the expected key names and their meaning.
@@ -63,16 +63,16 @@ Each instrumented component will provide two extension points:
=== Using custom Observation conventions
Let's take the example of the Spring MVC "http.server.requests" metrics instrumentation with the `ServerHttpObservationFilter`.
This observation is using a `ServerRequestObservationConvention` with a `ServerRequestObservationContext`; custom conventions can be configured on the Servlet filter.
This observation uses a `ServerRequestObservationConvention` with a `ServerRequestObservationContext`; custom conventions can be configured on the Servlet filter.
If you would like to customize the metadata produced with the observation, you can extend the `DefaultServerRequestObservationConvention` for your requirements:
include-code::./ExtendedServerRequestObservationConvention[]
If you want full control, you can then implement the entire convention contract for the observation you're interested in:
If you want full control, you can implement the entire convention contract for the observation you're interested in:
include-code::./CustomServerRequestObservationConvention[]
You can also achieve similar goals using a custom `ObservationFilter` - adding or removing key values for an observation.
You can also achieve similar goals using a custom `ObservationFilter` adding or removing key values for an observation.
Filters do not replace the default convention and are used as a post-processing component.
include-code::./ServerRequestObservationFilter[]
@@ -83,20 +83,24 @@ You can configure `ObservationFilter` instances on the `ObservationRegistry`.
[[observability.http-server]]
== HTTP Server instrumentation
HTTP server exchanges observations are created with the name `"http.server.requests"` for Servlet and Reactive applications.
HTTP server exchange observations are created with the name `"http.server.requests"` for Servlet and Reactive applications.
[[observability.http-server.servlet]]
=== Servlet applications
Applications need to configure the `org.springframework.web.filter.ServerHttpObservationFilter` Servlet filter in their application.
It is using the `org.springframework.http.server.observation.DefaultServerRequestObservationConvention` by default, backed by the `ServerRequestObservationContext`.
It uses the `org.springframework.http.server.observation.DefaultServerRequestObservationConvention` by default, backed by the `ServerRequestObservationContext`.
This will only record an observation as an error if the `Exception` has not been handled by the web Framework and has bubbled up to the Servlet filter.
This will only record an observation as an error if the `Exception` has not been handled by the web framework and has bubbled up to the Servlet filter.
Typically, all exceptions handled by Spring MVC's `@ExceptionHandler` and xref:web/webmvc/mvc-ann-rest-exceptions.adoc[`ProblemDetail` support] will not be recorded with the observation.
You can, at any point during request processing, set the error field on the `ObservationContext` yourself:
include-code::./UserController[]
NOTE: Because the instrumentation is done at the Servlet Filter level, the observation scope only covers the filters ordered after this one as well as the handling of the request.
Typically, Servlet container error handling is performed at a lower level and won't have any active observation or span.
For this use case, a container-specific implementation is required, such as a `org.apache.catalina.Valve` for Tomcat; this is outside of the scope of this project.
By default, the following `KeyValues` are created:
.Low cardinality Keys
@@ -122,9 +126,9 @@ By default, the following `KeyValues` are created:
=== Reactive applications
Applications need to configure the `org.springframework.web.filter.reactive.ServerHttpObservationFilter` reactive `WebFilter` in their application.
It is using the `org.springframework.http.server.reactive.observation.DefaultServerRequestObservationConvention` by default, backed by the `ServerRequestObservationContext`.
It uses the `org.springframework.http.server.reactive.observation.DefaultServerRequestObservationConvention` by default, backed by the `ServerRequestObservationContext`.
This will only record an observation as an error if the `Exception` has not been handled by the web Framework and has bubbled up to the `WebFilter`.
This will only record an observation as an error if the `Exception` has not been handled by the web framework and has bubbled up to the `WebFilter`.
Typically, all exceptions handled by Spring WebFlux's `@ExceptionHandler` and xref:web/webflux/ann-rest-exceptions.adoc[`ProblemDetail` support] will not be recorded with the observation.
You can, at any point during request processing, set the error field on the `ObservationContext` yourself:
@@ -153,9 +157,9 @@ By default, the following `KeyValues` are created:
[[observability.http-client]]
== HTTP Client instrumentation
== HTTP Client Instrumentation
HTTP client exchanges observations are created with the name `"http.client.requests"` for blocking and reactive clients.
HTTP client exchange observations are created with the name `"http.client.requests"` for blocking and reactive clients.
Unlike their server counterparts, the instrumentation is implemented directly in the client so the only required step is to configure an `ObservationRegistry` on the client.
[[observability.http-client.resttemplate]]
@@ -164,7 +168,7 @@ Unlike their server counterparts, the instrumentation is implemented directly in
Applications must configure an `ObservationRegistry` on `RestTemplate` instances to enable the instrumentation; without that, observations are "no-ops".
Spring Boot will auto-configure `RestTemplateBuilder` beans with the observation registry already set.
Instrumentation is using the `org.springframework.http.client.observation.ClientRequestObservationConvention` by default, backed by the `ClientRequestObservationContext`.
Instrumentation uses the `org.springframework.http.client.observation.ClientRequestObservationConvention` by default, backed by the `ClientRequestObservationContext`.
.Low cardinality Keys
[cols="a,a"]
@@ -193,7 +197,7 @@ Instrumentation is using the `org.springframework.http.client.observation.Client
Applications must configure an `ObservationRegistry` on the `WebClient` builder to enable the instrumentation; without that, observations are "no-ops".
Spring Boot will auto-configure `WebClient.Builder` beans with the observation registry already set.
Instrumentation is using the `org.springframework.web.reactive.function.client.ClientRequestObservationConvention` by default, backed by the `ClientRequestObservationContext`.
Instrumentation uses the `org.springframework.web.reactive.function.client.ClientRequestObservationConvention` by default, backed by the `ClientRequestObservationContext`.
.Low cardinality Keys
[cols="a,a"]
@@ -10,7 +10,7 @@ Resource locations are typically XML configuration files or Groovy scripts locat
classpath, while component classes are typically `@Configuration` classes. However,
resource locations can also refer to files and scripts in the file system, and component
classes can be `@Component` classes, `@Service` classes, and so on. See
xref:testing/testcontext-framework/ctx-management/javaconfig.adoc#testcontext-ctx-management-javaconfig-component-classes[null] for further details.
xref:testing/testcontext-framework/ctx-management/javaconfig.adoc#testcontext-ctx-management-javaconfig-component-classes[Component Classes] for further details.
The following example shows a `@ContextConfiguration` annotation that refers to an XML
file:
@@ -189,6 +189,9 @@ lets applications use the Servlet API directly if they need to. Spring WebFlux
relies on Servlet non-blocking I/O and uses the Servlet API behind a low-level
adapter. It is not exposed for direct use.
NOTE: It is strongly advised not to map Servlet filters or directly manipulate the Servlet API in the context of a WebFlux application.
For the reasons listed above, mixing blocking I/O and non-blocking I/O in the same context will cause runtime issues.
For Undertow, Spring WebFlux uses Undertow APIs directly without the Servlet API.
@@ -197,9 +200,9 @@ For Undertow, Spring WebFlux uses Undertow APIs directly without the Servlet API
== Performance
Performance has many characteristics and meanings. Reactive and non-blocking generally
do not make applications run faster. They can, in some cases, (for example, if using the
`WebClient` to run remote calls in parallel). On the whole, it requires more work to do
things the non-blocking way and that can slightly increase the required processing time.
do not make applications run faster. They can in some cases for example, if using the
`WebClient` to run remote calls in parallel. However, it requires more work to do
things the non-blocking way, and that can slightly increase the required processing time.
The key expected benefit of reactive and non-blocking is the ability to scale with a small,
fixed number of threads and less memory. That makes applications more resilient under load,
@@ -221,10 +224,10 @@ block the current thread, (for example, for remote calls). For this reason, serv
use a large thread pool to absorb potential blocking during request handling.
In Spring WebFlux (and non-blocking servers in general), it is assumed that applications
do not block. Therefore, non-blocking servers use a small, fixed-size thread pool
do not block. Therefore, non-blocking servers use a small, fixed-size thread pool
(event loop workers) to handle requests.
TIP: "`To scale`" and "`small number of threads`" may sound contradictory but to never block the
TIP: "`To scale`" and "`small number of threads`" may sound contradictory, but to never block the
current thread (and rely on callbacks instead) means that you do not need extra threads, as
there are no blocking calls to absorb.
@@ -250,7 +253,7 @@ application code within that pipeline is never invoked concurrently.
What threads should you expect to see on a server running with Spring WebFlux?
* On a "`vanilla`" Spring WebFlux server (for example, no data access nor other optional
* On a "`vanilla`" Spring WebFlux server (for example, no data access or other optional
dependencies), you can expect one thread for the server and several others for request
processing (typically as many as the number of CPU cores). Servlet containers, however,
may start with more threads (for example, 10 on Tomcat), in support of both servlet (blocking) I/O
@@ -69,9 +69,10 @@ written to the response and computing an MD5 hash from it. The next time a clien
it does the same, but it also compares the computed value against the `If-None-Match`
request header and, if the two are equal, returns a 304 (NOT_MODIFIED).
This strategy saves network bandwidth but not CPU, as the full response must be computed
for each request. Other strategies at the controller level, described earlier, can avoid
the computation. See xref:web/webmvc/mvc-caching.adoc[HTTP Caching].
This strategy saves network bandwidth but not CPU, as the full response must be computed for each request.
State-changing HTTP methods and other HTTP conditional request headers such as `If-Match` and `If-Unmodified-Since` are outside the scope of this filter.
Other strategies at the controller level can avoid the computation and have a broader support for HTTP conditional requests.
See xref:web/webmvc/mvc-caching.adoc[HTTP Caching].
This filter has a `writeWeakETag` parameter that configures the filter to write weak ETags
similar to the following: `W/"02a2d595e6ed9a0b24f027f2b63b134d6"` (as defined in
@@ -3,7 +3,7 @@
STOMP over WebSocket support is available in the `spring-messaging` and
`spring-websocket` modules. Once you have those dependencies, you can expose a STOMP
endpoint over WebSocket with xref:web/websocket/fallback.adoc[SockJS Fallback], as the following example shows:
endpoint over WebSocket, as the following example shows:
[source,java,indent=0,subs="verbatim,quotes"]
----
@@ -16,7 +16,7 @@ endpoint over WebSocket with xref:web/websocket/fallback.adoc[SockJS Fallback],
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/portfolio").withSockJS(); // <1>
registry.addEndpoint("/portfolio"); // <1>
}
@Override
@@ -49,9 +49,7 @@ The following example shows the XML configuration equivalent of the preceding ex
https://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:message-broker application-destination-prefix="/app">
<websocket:stomp-endpoint path="/portfolio">
<websocket:sockjs/>
</websocket:stomp-endpoint>
<websocket:stomp-endpoint path="/portfolio" />
<websocket:simple-broker prefix="/topic, /queue"/>
</websocket:message-broker>
@@ -64,34 +62,27 @@ messaging (that is, many subscribers versus one consumer). When you use an exter
check the STOMP page of the broker to understand what kind of STOMP destinations and
prefixes it supports.
To connect from a browser, for SockJS, you can use the
https://github.com/sockjs/sockjs-client[`sockjs-client`]. For STOMP, many applications have
used the https://github.com/jmesnil/stomp-websocket[jmesnil/stomp-websocket] library
(also known as stomp.js), which is feature-complete and has been used in production for
years but is no longer maintained. At present the
https://github.com/JSteunou/webstomp-client[JSteunou/webstomp-client] is the most
actively maintained and evolving successor of that library. The following example code
is based on it:
To connect from a browser, for STOMP, you can use
https://github.com/stomp-js/stompjs[`stomp-js/stompjs`] which is the most
actively maintained JavaScript library.
The following example code is based on it:
[source,javascript,indent=0,subs="verbatim,quotes"]
----
var socket = new SockJS("/spring-websocket-portfolio/portfolio");
var stompClient = webstomp.over(socket);
stompClient.connect({}, function(frame) {
}
const stompClient = new StompJs.Client({
brokerURL: 'ws://domain.com/portfolio',
onConnect: () => {
// ...
}
});
----
Alternatively, if you connect through WebSocket (without SockJS), you can use the following code:
[source,javascript,indent=0,subs="verbatim,quotes"]
----
var socket = new WebSocket("/spring-websocket-portfolio/portfolio");
var stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
}
----
Alternatively, if you connect through SockJS, you can enable the
xref:web/websocket/fallback.adoc[SockJS Fallback] on server-side with
`registry.addEndpoint("/portfolio").withSockJS()` and on JavaScript side,
by following
https://stomp-js.github.io/guide/stompjs/rx-stomp/using-stomp-with-sockjs.html[those instructions].
Note that `stompClient` in the preceding example does not need to specify `login`
and `passcode` headers. Even if it did, they would be ignored (or, rather,
+13 -12
View File
@@ -8,18 +8,18 @@ javaPlatform {
dependencies {
api(platform("com.fasterxml.jackson:jackson-bom:2.14.3"))
api(platform("io.micrometer:micrometer-bom:1.10.8"))
api(platform("io.netty:netty-bom:4.1.93.Final"))
api(platform("io.micrometer:micrometer-bom:1.10.9"))
api(platform("io.netty:netty-bom:4.1.94.Final"))
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
api(platform("io.projectreactor:reactor-bom:2022.0.8"))
api(platform("io.projectreactor:reactor-bom:2022.0.9"))
api(platform("io.rsocket:rsocket-bom:1.1.3"))
api(platform("org.apache.groovy:groovy-bom:4.0.12"))
api(platform("org.apache.groovy:groovy-bom:4.0.13"))
api(platform("org.apache.logging.log4j:log4j-bom:2.20.0"))
api(platform("org.eclipse.jetty:jetty-bom:11.0.15"))
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.3.1"))
api(platform("org.mockito:mockito-bom:5.4.0"))
constraints {
api("com.fasterxml:aalto-xml:1.3.2")
@@ -46,7 +46,7 @@ dependencies {
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.3")
api("io.micrometer:context-propagation:1.0.4")
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")
@@ -82,6 +82,7 @@ dependencies {
api("jakarta.websocket:jakarta.websocket-api:2.1.0")
api("jakarta.websocket:jakarta.websocket-client-api:2.1.0")
api("jakarta.xml.bind:jakarta.xml.bind-api:3.0.1")
api("javax.annotation:javax.annotation-api:1.3.2")
api("javax.cache:cache-api:1.1.1")
api("javax.money:money-api:1.1")
api("jaxen:jaxen:1.2.0")
@@ -99,10 +100,10 @@ dependencies {
api("org.apache.httpcomponents.client5:httpclient5:5.2.1")
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.2.1")
api("org.apache.poi:poi-ooxml:5.2.3")
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.9")
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.9")
api("org.apache.tomcat:tomcat-util:10.1.8")
api("org.apache.tomcat:tomcat-websocket:10.1.8")
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")
@@ -126,7 +127,7 @@ dependencies {
api("org.hamcrest:hamcrest:2.2")
api("org.hibernate:hibernate-core-jakarta:5.6.15.Final")
api("org.hibernate:hibernate-validator:7.0.5.Final")
api("org.hsqldb:hsqldb:2.7.1")
api("org.hsqldb:hsqldb:2.7.2")
api("org.javamoney:moneta:1.4.2")
api("org.jruby:jruby:9.4.3.0")
api("org.junit.support:testng-engine:1.0.4")
@@ -140,7 +141,7 @@ dependencies {
api("org.slf4j:slf4j-api:2.0.7")
api("org.testng:testng:7.8.0")
api("org.webjars:underscorejs:1.8.3")
api("org.webjars:webjars-locator-core:0.52")
api("org.webjars:webjars-locator-core:0.53")
api("org.xmlunit:xmlunit-assertj:2.9.1")
api("org.xmlunit:xmlunit-matchers:2.9.1")
api("org.yaml:snakeyaml:1.33")
+1 -1
View File
@@ -1,4 +1,4 @@
version=6.0.10-SNAPSHOT
version=6.0.11
org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m
Binary file not shown.
+2 -1
View File
@@ -1,6 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+4 -1
View File
@@ -130,10 +130,13 @@ location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
@@ -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.
@@ -21,11 +21,15 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.MethodExecutor;
import org.springframework.expression.TypedValue;
import static org.assertj.core.api.Assertions.assertThat;
class Spr7538Tests {
@@ -49,8 +53,9 @@ class Spr7538Tests {
ReflectiveMethodResolver resolver = new ReflectiveMethodResolver();
MethodExecutor executor = resolver.resolve(context, target, "checkCompleteness", argumentTypes);
Object result = executor.execute(context, target, arguments);
System.out.println("Result: " + result);
TypedValue typedValue = executor.execute(context, target, arguments);
assertThat(typedValue.getValue()).asInstanceOf(InstanceOfAssertFactories.BOOLEAN).isTrue();
assertThat(typedValue.getTypeDescriptor().getType()).isEqualTo(Boolean.class);
}
static class AlwaysTrueReleaseStrategy {
+1
View File
@@ -10,4 +10,5 @@ dependencies {
testImplementation(testFixtures(project(":spring-core")))
testFixturesImplementation(testFixtures(project(":spring-beans")))
testFixturesImplementation(testFixtures(project(":spring-core")))
testFixturesImplementation("com.google.code.findbugs:jsr305")
}
@@ -0,0 +1,90 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.aop.aspectj.annotation;
import java.util.List;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AbstractAspectJAdvice;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.ReflectionHints;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
import org.springframework.beans.factory.aot.BeanFactoryInitializationCode;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
/**
* {@link BeanFactoryInitializationAotProcessor} implementation responsible for registering
* hints for AOP advices.
*
* @author Sebastien Deleuze
* @author Stephane Nicoll
* @since 6.0.11
*/
class AspectJBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor {
private static final boolean aspectJPresent = ClassUtils.isPresent(
"org.aspectj.lang.annotation.Pointcut", AspectJBeanFactoryInitializationAotProcessor.class.getClassLoader());
@Nullable
@Override
public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
if (aspectJPresent) {
return AspectDelegate.processAheadOfTime(beanFactory);
}
return null;
}
/**
* Inner class to avoid a hard dependency on AspectJ at runtime.
*/
private static class AspectDelegate {
@Nullable
private static AspectContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
BeanFactoryAspectJAdvisorsBuilder builder = new BeanFactoryAspectJAdvisorsBuilder(beanFactory);
List<Advisor> advisors = builder.buildAspectJAdvisors();
return advisors.isEmpty() ? null : new AspectContribution(advisors);
}
}
private static class AspectContribution implements BeanFactoryInitializationAotContribution {
private final List<Advisor> advisors;
public AspectContribution(List<Advisor> advisors) {
this.advisors = advisors;
}
@Override
public void applyTo(GenerationContext generationContext, BeanFactoryInitializationCode beanFactoryInitializationCode) {
ReflectionHints reflectionHints = generationContext.getRuntimeHints().reflection();
for (Advisor advisor : this.advisors) {
if (advisor.getAdvice() instanceof AbstractAspectJAdvice aspectJAdvice) {
reflectionHints.registerMethod(aspectJAdvice.getAspectJAdviceMethod(), ExecutableMode.INVOKE);
}
}
}
}
}
@@ -1,2 +1,5 @@
org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=\
org.springframework.aop.scope.ScopedProxyBeanRegistrationAotProcessor
org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor= \
org.springframework.aop.aspectj.annotation.AspectJBeanFactoryInitializationAotProcessor
@@ -637,17 +637,20 @@ abstract class AbstractAspectJAdvisorFactoryTests {
}
static class CommonPointcuts {
@Pointcut("execution(* getAge())")
void getAge() {
}
}
@Aspect
static class NamedPointcutAspectWithFQN {
@SuppressWarnings("unused")
private ITestBean fieldThatShouldBeIgnoredBySpringAtAspectJProcessing = new TestBean();
@Pointcut("execution(* getAge())")
void getAge() {
}
@Around("org.springframework.aop.aspectj.annotation.AbstractAspectJAdvisorFactoryTests.NamedPointcutAspectWithFQN.getAge()")
@Around("org.springframework.aop.aspectj.annotation.AbstractAspectJAdvisorFactoryTests.CommonPointcuts.getAge()()")
int changeReturnValue(ProceedingJoinPoint pjp) {
return -1;
}
@@ -0,0 +1,89 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.aop.aspectj.annotation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.junit.jupiter.api.Test;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.aot.test.generate.TestGenerationContext;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.lang.Nullable;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link AspectJBeanFactoryInitializationAotProcessor}.
*
* @author Sebastien Deleuze
*/
class AspectJBeanFactoryInitializationAotProcessorTests {
private final GenerationContext generationContext = new TestGenerationContext();
@Test
void shouldSkipEmptyClass() {
assertThat(createContribution(EmptyClass.class)).isNull();
}
@Test
void shouldProcessAspect() {
process(TestAspect.class);
assertThat(RuntimeHintsPredicates.reflection().onMethod(TestAspect.class, "alterReturnValue").invoke())
.accepts(this.generationContext.getRuntimeHints());
}
private void process(Class<?> beanClass) {
BeanFactoryInitializationAotContribution contribution = createContribution(beanClass);
if (contribution != null) {
contribution.applyTo(this.generationContext, mock());
}
}
@Nullable
private static BeanFactoryInitializationAotContribution createContribution(Class<?> beanClass) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerBeanDefinition(beanClass.getName(), new RootBeanDefinition(beanClass));
return new AspectJBeanFactoryInitializationAotProcessor().processAheadOfTime(beanFactory);
}
static class EmptyClass { }
@Aspect
static class TestAspect {
@Around("pointcut()")
public Object alterReturnValue(ProceedingJoinPoint joinPoint) throws Throwable {
joinPoint.proceed();
return "A-from-aspect";
}
@Pointcut("execution(* com.example.aspect.Test*.methodA(..))")
private void pointcut() {
}
}
}
@@ -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.
@@ -19,9 +19,9 @@ package org.springframework.aop.config;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.core.io.ClassPathResource;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.core.testfixture.io.ResourceTestUtils.qualifiedResource;
@@ -29,25 +29,28 @@ import static org.springframework.core.testfixture.io.ResourceTestUtils.qualifie
/**
* @author Mark Fisher
* @author Chris Beams
* @author Sam Brannen
*/
class AopNamespaceHandlerPointcutErrorTests {
@Test
void duplicatePointcutConfig() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
assertThatExceptionOfType(BeanDefinitionStoreException.class).isThrownBy(() ->
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(
qualifiedResource(getClass(), "pointcutDuplication.xml")))
.satisfies(ex -> ex.contains(BeanDefinitionParsingException.class));
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(bf);
ClassPathResource resource = qualifiedResource(getClass(), "pointcutDuplication.xml");
assertThatExceptionOfType(BeanDefinitionStoreException.class)
.isThrownBy(() -> reader.loadBeanDefinitions(resource));
}
@Test
void missingPointcutConfig() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
assertThatExceptionOfType(BeanDefinitionStoreException.class).isThrownBy(() ->
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(
qualifiedResource(getClass(), "pointcutMissing.xml")))
.satisfies(ex -> ex.contains(BeanDefinitionParsingException.class));
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(bf);
ClassPathResource resource = qualifiedResource(getClass(), "pointcutMissing.xml");
assertThatExceptionOfType(BeanDefinitionStoreException.class)
.isThrownBy(() -> reader.loadBeanDefinitions(resource));
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 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,6 +82,8 @@ public class IntroductionBenchmarkTests {
}
sw.stop();
System.out.println(sw.prettyPrint());
// TODO Add reasonable assertions.
// System.out.println(sw.prettyPrint());
}
}
+1
View File
@@ -14,4 +14,5 @@ dependencies {
testImplementation("jakarta.annotation:jakarta.annotation-api")
testFixturesApi("org.junit.jupiter:junit-jupiter-api")
testFixturesImplementation("org.assertj:assertj-core")
testFixturesImplementation("com.google.code.findbugs:jsr305")
}
@@ -287,7 +287,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
RootBeanDefinition beanDefinition = registeredBean.getMergedBeanDefinition();
InjectionMetadata metadata = findInjectionMetadata(beanName, beanClass, beanDefinition);
Collection<AutowiredElement> autowiredElements = getAutowiredElements(metadata,
registeredBean.getMergedBeanDefinition().getPropertyValues());
beanDefinition.getPropertyValues());
if (!ObjectUtils.isEmpty(autowiredElements)) {
return new AotContribution(beanClass, autowiredElements, getAutowireCandidateResolver());
}
@@ -638,7 +638,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
* Resolve the specified cached method argument or field value.
*/
@Nullable
private Object resolvedCachedArgument(@Nullable String beanName, @Nullable Object cachedArgument) {
private Object resolveCachedArgument(@Nullable String beanName, @Nullable Object cachedArgument) {
if (cachedArgument instanceof DependencyDescriptor descriptor) {
Assert.state(this.beanFactory != null, "No BeanFactory available");
return this.beanFactory.resolveDependency(descriptor, beanName, null, null);
@@ -683,10 +683,12 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
Object value;
if (this.cached) {
try {
value = resolvedCachedArgument(beanName, this.cachedFieldValue);
value = resolveCachedArgument(beanName, this.cachedFieldValue);
}
catch (NoSuchBeanDefinitionException ex) {
// Unexpected removal of target bean for cached argument -> re-resolve
catch (BeansException ex) {
// Unexpected target bean mismatch for cached argument -> re-resolve
this.cached = false;
logger.debug("Failed to resolve cached argument", ex);
value = resolveFieldValue(field, bean, beanName);
}
}
@@ -715,9 +717,8 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
}
synchronized (this) {
if (!this.cached) {
Object cachedFieldValue = null;
if (value != null || this.required) {
cachedFieldValue = desc;
Object cachedFieldValue = desc;
registerDependentBeans(beanName, autowiredBeanNames);
if (value != null && autowiredBeanNames.size() == 1) {
String autowiredBeanName = autowiredBeanNames.iterator().next();
@@ -727,9 +728,13 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
desc, autowiredBeanName, field.getType());
}
}
this.cachedFieldValue = cachedFieldValue;
this.cached = true;
}
else {
this.cachedFieldValue = null;
// cached flag remains false
}
this.cachedFieldValue = cachedFieldValue;
this.cached = true;
}
}
return value;
@@ -760,10 +765,12 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
Object[] arguments;
if (this.cached) {
try {
arguments = resolveCachedArguments(beanName);
arguments = resolveCachedArguments(beanName, this.cachedMethodArguments);
}
catch (NoSuchBeanDefinitionException ex) {
// Unexpected removal of target bean for cached argument -> re-resolve
catch (BeansException ex) {
// Unexpected target bean mismatch for cached argument -> re-resolve
this.cached = false;
logger.debug("Failed to resolve cached argument", ex);
arguments = resolveMethodArguments(method, bean, beanName);
}
}
@@ -782,14 +789,13 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
}
@Nullable
private Object[] resolveCachedArguments(@Nullable String beanName) {
Object[] cachedMethodArguments = this.cachedMethodArguments;
private Object[] resolveCachedArguments(@Nullable String beanName, @Nullable Object[] cachedMethodArguments) {
if (cachedMethodArguments == null) {
return null;
}
Object[] arguments = new Object[cachedMethodArguments.length];
for (int i = 0; i < arguments.length; i++) {
arguments[i] = resolvedCachedArgument(beanName, cachedMethodArguments[i]);
arguments[i] = resolveCachedArgument(beanName, cachedMethodArguments[i]);
}
return arguments;
}
@@ -822,7 +828,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
synchronized (this) {
if (!this.cached) {
if (arguments != null) {
DependencyDescriptor[] cachedMethodArguments = Arrays.copyOf(descriptors, arguments.length);
DependencyDescriptor[] cachedMethodArguments = Arrays.copyOf(descriptors, argumentCount);
registerDependentBeans(beanName, autowiredBeans);
if (autowiredBeans.size() == argumentCount) {
Iterator<String> it = autowiredBeans.iterator();
@@ -837,11 +843,12 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
}
}
this.cachedMethodArguments = cachedMethodArguments;
this.cached = true;
}
else {
this.cachedMethodArguments = null;
// cached flag remains false
}
this.cached = true;
}
}
return arguments;
@@ -24,7 +24,6 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
@@ -79,6 +78,7 @@ import org.springframework.util.ReflectionUtils;
* @author Juergen Hoeller
* @author Stephane Nicoll
* @author Phillip Webb
* @author Sam Brannen
* @since 2.5
* @see #setInitAnnotationType
* @see #setDestroyAnnotationType
@@ -90,7 +90,7 @@ public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareB
private final transient LifecycleMetadata emptyLifecycleMetadata =
new LifecycleMetadata(Object.class, Collections.emptyList(), Collections.emptyList()) {
@Override
public void checkConfigMembers(RootBeanDefinition beanDefinition) {
public void checkInitDestroyMethods(RootBeanDefinition beanDefinition) {
}
@Override
public void invokeInitMethods(Object target, String beanName) {
@@ -107,11 +107,9 @@ public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareB
protected transient Log logger = LogFactory.getLog(getClass());
@Nullable
private Class<? extends Annotation> initAnnotationType;
private final Set<Class<? extends Annotation>> initAnnotationTypes = new LinkedHashSet<>(2);
@Nullable
private Class<? extends Annotation> destroyAnnotationType;
private final Set<Class<? extends Annotation>> destroyAnnotationTypes = new LinkedHashSet<>(2);
private int order = Ordered.LOWEST_PRECEDENCE;
@@ -125,9 +123,23 @@ public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareB
* <p>Any custom annotation can be used, since there are no required
* annotation attributes. There is no default, although a typical choice
* is the {@link jakarta.annotation.PostConstruct} annotation.
* @see #addInitAnnotationType
*/
public void setInitAnnotationType(Class<? extends Annotation> initAnnotationType) {
this.initAnnotationType = initAnnotationType;
this.initAnnotationTypes.clear();
this.initAnnotationTypes.add(initAnnotationType);
}
/**
* Add an init annotation to check for, indicating initialization
* methods to call after configuration of a bean.
* @since 6.0.11
* @see #setInitAnnotationType
*/
public void addInitAnnotationType(@Nullable Class<? extends Annotation> initAnnotationType) {
if (initAnnotationType != null) {
this.initAnnotationTypes.add(initAnnotationType);
}
}
/**
@@ -136,9 +148,23 @@ public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareB
* <p>Any custom annotation can be used, since there are no required
* annotation attributes. There is no default, although a typical choice
* is the {@link jakarta.annotation.PreDestroy} annotation.
* @see #addDestroyAnnotationType
*/
public void setDestroyAnnotationType(Class<? extends Annotation> destroyAnnotationType) {
this.destroyAnnotationType = destroyAnnotationType;
this.destroyAnnotationTypes.clear();
this.destroyAnnotationTypes.add(destroyAnnotationType);
}
/**
* Add a destroy annotation to check for, indicating destruction
* methods to call when the context is shutting down.
* @since 6.0.11
* @see #setDestroyAnnotationType
*/
public void addDestroyAnnotationType(@Nullable Class<? extends Annotation> destroyAnnotationType) {
if (destroyAnnotationType != null) {
this.destroyAnnotationTypes.add(destroyAnnotationType);
}
}
public void setOrder(int order) {
@@ -152,8 +178,8 @@ public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareB
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
findInjectionMetadata(beanDefinition, beanType);
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanClass, String beanName) {
findLifecycleMetadata(beanDefinition, beanClass);
}
@Override
@@ -161,7 +187,7 @@ public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareB
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
RootBeanDefinition beanDefinition = registeredBean.getMergedBeanDefinition();
beanDefinition.resolveDestroyMethodIfNecessary();
LifecycleMetadata metadata = findInjectionMetadata(beanDefinition, registeredBean.getBeanClass());
LifecycleMetadata metadata = findLifecycleMetadata(beanDefinition, registeredBean.getBeanClass());
if (!CollectionUtils.isEmpty(metadata.initMethods)) {
String[] initMethodNames = safeMerge(beanDefinition.getInitMethodNames(), metadata.initMethods);
beanDefinition.setInitMethodNames(initMethodNames);
@@ -173,16 +199,16 @@ public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareB
return null;
}
private LifecycleMetadata findInjectionMetadata(RootBeanDefinition beanDefinition, Class<?> beanType) {
LifecycleMetadata metadata = findLifecycleMetadata(beanType);
metadata.checkConfigMembers(beanDefinition);
private LifecycleMetadata findLifecycleMetadata(RootBeanDefinition beanDefinition, Class<?> beanClass) {
LifecycleMetadata metadata = findLifecycleMetadata(beanClass);
metadata.checkInitDestroyMethods(beanDefinition);
return metadata;
}
private String[] safeMerge(@Nullable String[] existingNames, Collection<LifecycleElement> detectedElements) {
Stream<String> detectedNames = detectedElements.stream().map(LifecycleElement::getIdentifier);
private static String[] safeMerge(@Nullable String[] existingNames, Collection<LifecycleMethod> detectedMethods) {
Stream<String> detectedNames = detectedMethods.stream().map(LifecycleMethod::getIdentifier);
Stream<String> mergedNames = (existingNames != null ?
Stream.concat(Stream.of(existingNames), detectedNames) : detectedNames);
Stream.concat(detectedNames, Stream.of(existingNames)) : detectedNames);
return mergedNames.distinct().toArray(String[]::new);
}
@@ -217,12 +243,14 @@ public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareB
if (logger.isDebugEnabled()) {
logger.warn(msg, ex.getTargetException());
}
else {
else if (logger.isWarnEnabled()) {
logger.warn(msg + ": " + ex.getTargetException());
}
}
catch (Throwable ex) {
logger.warn("Failed to invoke destroy method on bean with name '" + beanName + "'", ex);
if (logger.isWarnEnabled()) {
logger.warn("Failed to invoke destroy method on bean with name '" + beanName + "'", ex);
}
}
}
@@ -232,19 +260,19 @@ public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareB
}
private LifecycleMetadata findLifecycleMetadata(Class<?> clazz) {
private LifecycleMetadata findLifecycleMetadata(Class<?> beanClass) {
if (this.lifecycleMetadataCache == null) {
// Happens after deserialization, during destruction...
return buildLifecycleMetadata(clazz);
return buildLifecycleMetadata(beanClass);
}
// Quick check on the concurrent map first, with minimal locking.
LifecycleMetadata metadata = this.lifecycleMetadataCache.get(clazz);
LifecycleMetadata metadata = this.lifecycleMetadataCache.get(beanClass);
if (metadata == null) {
synchronized (this.lifecycleMetadataCache) {
metadata = this.lifecycleMetadataCache.get(clazz);
metadata = this.lifecycleMetadataCache.get(beanClass);
if (metadata == null) {
metadata = buildLifecycleMetadata(clazz);
this.lifecycleMetadataCache.put(clazz, metadata);
metadata = buildLifecycleMetadata(beanClass);
this.lifecycleMetadataCache.put(beanClass, metadata);
}
return metadata;
}
@@ -252,43 +280,47 @@ public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareB
return metadata;
}
private LifecycleMetadata buildLifecycleMetadata(final Class<?> clazz) {
if (!AnnotationUtils.isCandidateClass(clazz, Arrays.asList(this.initAnnotationType, this.destroyAnnotationType))) {
private LifecycleMetadata buildLifecycleMetadata(final Class<?> beanClass) {
if (!AnnotationUtils.isCandidateClass(beanClass, this.initAnnotationTypes) &&
!AnnotationUtils.isCandidateClass(beanClass, this.destroyAnnotationTypes)) {
return this.emptyLifecycleMetadata;
}
List<LifecycleElement> initMethods = new ArrayList<>();
List<LifecycleElement> destroyMethods = new ArrayList<>();
Class<?> targetClass = clazz;
List<LifecycleMethod> initMethods = new ArrayList<>();
List<LifecycleMethod> destroyMethods = new ArrayList<>();
Class<?> currentClass = beanClass;
do {
final List<LifecycleElement> currInitMethods = new ArrayList<>();
final List<LifecycleElement> currDestroyMethods = new ArrayList<>();
final List<LifecycleMethod> currInitMethods = new ArrayList<>();
final List<LifecycleMethod> currDestroyMethods = new ArrayList<>();
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
if (this.initAnnotationType != null && method.isAnnotationPresent(this.initAnnotationType)) {
LifecycleElement element = new LifecycleElement(method);
currInitMethods.add(element);
if (logger.isTraceEnabled()) {
logger.trace("Found init method on class [" + clazz.getName() + "]: " + method);
ReflectionUtils.doWithLocalMethods(currentClass, method -> {
for (Class<? extends Annotation> initAnnotationType : this.initAnnotationTypes) {
if (initAnnotationType != null && method.isAnnotationPresent(initAnnotationType)) {
currInitMethods.add(new LifecycleMethod(method, beanClass));
if (logger.isTraceEnabled()) {
logger.trace("Found init method on class [" + beanClass.getName() + "]: " + method);
}
}
}
if (this.destroyAnnotationType != null && method.isAnnotationPresent(this.destroyAnnotationType)) {
currDestroyMethods.add(new LifecycleElement(method));
if (logger.isTraceEnabled()) {
logger.trace("Found destroy method on class [" + clazz.getName() + "]: " + method);
for (Class<? extends Annotation> destroyAnnotationType : this.destroyAnnotationTypes) {
if (destroyAnnotationType != null && method.isAnnotationPresent(destroyAnnotationType)) {
currDestroyMethods.add(new LifecycleMethod(method, beanClass));
if (logger.isTraceEnabled()) {
logger.trace("Found destroy method on class [" + beanClass.getName() + "]: " + method);
}
}
}
});
initMethods.addAll(0, currInitMethods);
destroyMethods.addAll(currDestroyMethods);
targetClass = targetClass.getSuperclass();
currentClass = currentClass.getSuperclass();
}
while (targetClass != null && targetClass != Object.class);
while (currentClass != null && currentClass != Object.class);
return (initMethods.isEmpty() && destroyMethods.isEmpty() ? this.emptyLifecycleMetadata :
new LifecycleMetadata(clazz, initMethods, destroyMethods));
new LifecycleMetadata(beanClass, initMethods, destroyMethods));
}
@@ -310,46 +342,46 @@ public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareB
*/
private class LifecycleMetadata {
private final Class<?> targetClass;
private final Class<?> beanClass;
private final Collection<LifecycleElement> initMethods;
private final Collection<LifecycleMethod> initMethods;
private final Collection<LifecycleElement> destroyMethods;
private final Collection<LifecycleMethod> destroyMethods;
@Nullable
private volatile Set<LifecycleElement> checkedInitMethods;
private volatile Set<LifecycleMethod> checkedInitMethods;
@Nullable
private volatile Set<LifecycleElement> checkedDestroyMethods;
private volatile Set<LifecycleMethod> checkedDestroyMethods;
public LifecycleMetadata(Class<?> targetClass, Collection<LifecycleElement> initMethods,
Collection<LifecycleElement> destroyMethods) {
public LifecycleMetadata(Class<?> beanClass, Collection<LifecycleMethod> initMethods,
Collection<LifecycleMethod> destroyMethods) {
this.targetClass = targetClass;
this.beanClass = beanClass;
this.initMethods = initMethods;
this.destroyMethods = destroyMethods;
}
public void checkConfigMembers(RootBeanDefinition beanDefinition) {
Set<LifecycleElement> checkedInitMethods = new LinkedHashSet<>(this.initMethods.size());
for (LifecycleElement element : this.initMethods) {
String methodIdentifier = element.getIdentifier();
public void checkInitDestroyMethods(RootBeanDefinition beanDefinition) {
Set<LifecycleMethod> checkedInitMethods = new LinkedHashSet<>(this.initMethods.size());
for (LifecycleMethod lifecycleMethod : this.initMethods) {
String methodIdentifier = lifecycleMethod.getIdentifier();
if (!beanDefinition.isExternallyManagedInitMethod(methodIdentifier)) {
beanDefinition.registerExternallyManagedInitMethod(methodIdentifier);
checkedInitMethods.add(element);
checkedInitMethods.add(lifecycleMethod);
if (logger.isTraceEnabled()) {
logger.trace("Registered init method on class [" + this.targetClass.getName() + "]: " + methodIdentifier);
logger.trace("Registered init method on class [" + this.beanClass.getName() + "]: " + methodIdentifier);
}
}
}
Set<LifecycleElement> checkedDestroyMethods = new LinkedHashSet<>(this.destroyMethods.size());
for (LifecycleElement element : this.destroyMethods) {
String methodIdentifier = element.getIdentifier();
Set<LifecycleMethod> checkedDestroyMethods = new LinkedHashSet<>(this.destroyMethods.size());
for (LifecycleMethod lifecycleMethod : this.destroyMethods) {
String methodIdentifier = lifecycleMethod.getIdentifier();
if (!beanDefinition.isExternallyManagedDestroyMethod(methodIdentifier)) {
beanDefinition.registerExternallyManagedDestroyMethod(methodIdentifier);
checkedDestroyMethods.add(element);
checkedDestroyMethods.add(lifecycleMethod);
if (logger.isTraceEnabled()) {
logger.trace("Registered destroy method on class [" + this.targetClass.getName() + "]: " + methodIdentifier);
logger.trace("Registered destroy method on class [" + this.beanClass.getName() + "]: " + methodIdentifier);
}
}
}
@@ -358,36 +390,36 @@ public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareB
}
public void invokeInitMethods(Object target, String beanName) throws Throwable {
Collection<LifecycleElement> checkedInitMethods = this.checkedInitMethods;
Collection<LifecycleElement> initMethodsToIterate =
Collection<LifecycleMethod> checkedInitMethods = this.checkedInitMethods;
Collection<LifecycleMethod> initMethodsToIterate =
(checkedInitMethods != null ? checkedInitMethods : this.initMethods);
if (!initMethodsToIterate.isEmpty()) {
for (LifecycleElement element : initMethodsToIterate) {
for (LifecycleMethod lifecycleMethod : initMethodsToIterate) {
if (logger.isTraceEnabled()) {
logger.trace("Invoking init method on bean '" + beanName + "': " + element.getMethod());
logger.trace("Invoking init method on bean '" + beanName + "': " + lifecycleMethod.getMethod());
}
element.invoke(target);
lifecycleMethod.invoke(target);
}
}
}
public void invokeDestroyMethods(Object target, String beanName) throws Throwable {
Collection<LifecycleElement> checkedDestroyMethods = this.checkedDestroyMethods;
Collection<LifecycleElement> destroyMethodsToUse =
Collection<LifecycleMethod> checkedDestroyMethods = this.checkedDestroyMethods;
Collection<LifecycleMethod> destroyMethodsToUse =
(checkedDestroyMethods != null ? checkedDestroyMethods : this.destroyMethods);
if (!destroyMethodsToUse.isEmpty()) {
for (LifecycleElement element : destroyMethodsToUse) {
for (LifecycleMethod lifecycleMethod : destroyMethodsToUse) {
if (logger.isTraceEnabled()) {
logger.trace("Invoking destroy method on bean '" + beanName + "': " + element.getMethod());
logger.trace("Invoking destroy method on bean '" + beanName + "': " + lifecycleMethod.getMethod());
}
element.invoke(target);
lifecycleMethod.invoke(target);
}
}
}
public boolean hasDestroyMethods() {
Collection<LifecycleElement> checkedDestroyMethods = this.checkedDestroyMethods;
Collection<LifecycleElement> destroyMethodsToUse =
Collection<LifecycleMethod> checkedDestroyMethods = this.checkedDestroyMethods;
Collection<LifecycleMethod> destroyMethodsToUse =
(checkedDestroyMethods != null ? checkedDestroyMethods : this.destroyMethods);
return !destroyMethodsToUse.isEmpty();
}
@@ -395,20 +427,20 @@ public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareB
/**
* Class representing injection information about an annotated method.
* Class representing an annotated init or destroy method.
*/
private static class LifecycleElement {
private static class LifecycleMethod {
private final Method method;
private final String identifier;
public LifecycleElement(Method method) {
public LifecycleMethod(Method method, Class<?> beanClass) {
if (method.getParameterCount() != 0) {
throw new IllegalStateException("Lifecycle method annotation requires a no-arg method: " + method);
throw new IllegalStateException("Lifecycle annotation requires a no-arg method: " + method);
}
this.method = method;
this.identifier = (Modifier.isPrivate(method.getModifiers()) ?
this.identifier = (isPrivateOrNotVisible(method, beanClass) ?
ClassUtils.getQualifiedMethodName(method) : method.getName());
}
@@ -422,24 +454,36 @@ public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareB
public void invoke(Object target) throws Throwable {
ReflectionUtils.makeAccessible(this.method);
this.method.invoke(target, (Object[]) null);
this.method.invoke(target);
}
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof LifecycleElement otherElement)) {
return false;
}
return (this.identifier.equals(otherElement.identifier));
return (this == other || (other instanceof LifecycleMethod that &&
this.identifier.equals(that.identifier)));
}
@Override
public int hashCode() {
return this.identifier.hashCode();
}
/**
* Determine if the supplied lifecycle {@link Method} is private or not
* visible to the supplied bean {@link Class}.
* @since 6.0.11
*/
private static boolean isPrivateOrNotVisible(Method method, Class<?> beanClass) {
int modifiers = method.getModifiers();
if (Modifier.isPrivate(modifiers)) {
return true;
}
// Method is declared in a class that resides in a different package
// than the bean class and the method is neither public nor protected?
return (!method.getDeclaringClass().getPackageName().equals(beanClass.getPackageName()) &&
!(Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers)));
}
}
}
@@ -56,24 +56,23 @@ import org.springframework.util.StringUtils;
/**
* Internal code generator to set {@link RootBeanDefinition} properties.
* <p>
* Generates code in the following form:<blockquote><pre class="code">
*
* <p>Generates code in the following form:<pre class="code">
* beanDefinition.setPrimary(true);
* beanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
* ...
* </pre></blockquote>
* <p>
* The generated code expects the following variables to be available:
* <p>
* </pre>
*
* <p>The generated code expects the following variables to be available:
* <ul>
* <li>{@code beanDefinition} - The {@link RootBeanDefinition} to
* configure.</li>
* <li>{@code beanDefinition}: the {@link RootBeanDefinition} to configure</li>
* </ul>
* <p>
* Note that this generator does <b>not</b> set the {@link InstanceSupplier}.
*
* <p>Note that this generator does <b>not</b> set the {@link InstanceSupplier}.
*
* @author Phillip Webb
* @author Stephane Nicoll
* @author Sam Brannen
* @since 6.0
*/
class BeanDefinitionPropertiesCodeGenerator {
@@ -127,8 +126,8 @@ class BeanDefinitionPropertiesCodeGenerator {
return code.build();
}
private void addInitDestroyMethods(Builder code,
AbstractBeanDefinition beanDefinition, @Nullable String[] methodNames, String format) {
private void addInitDestroyMethods(Builder code, AbstractBeanDefinition beanDefinition,
@Nullable String[] methodNames, String format) {
if (!ObjectUtils.isEmpty(methodNames)) {
Class<?> beanType = ClassUtils.getUserClass(beanDefinition.getResolvableType().toClass());
Arrays.stream(methodNames).forEach(methodName -> addInitDestroyHint(beanType, methodName));
@@ -140,17 +139,33 @@ class BeanDefinitionPropertiesCodeGenerator {
}
private void addInitDestroyHint(Class<?> beanUserClass, String methodName) {
Method method = ReflectionUtils.findMethod(beanUserClass, methodName);
Class<?> methodDeclaringClass = beanUserClass;
// Parse fully-qualified method name if necessary.
int indexOfDot = methodName.lastIndexOf('.');
if (indexOfDot > 0) {
String className = methodName.substring(0, indexOfDot);
methodName = methodName.substring(indexOfDot + 1);
if (!beanUserClass.getName().equals(className)) {
try {
methodDeclaringClass = ClassUtils.forName(className, beanUserClass.getClassLoader());
}
catch (Throwable ex) {
throw new IllegalStateException("Failed to load Class [" + className +
"] from ClassLoader [" + beanUserClass.getClassLoader() + "]", ex);
}
}
}
Method method = ReflectionUtils.findMethod(methodDeclaringClass, methodName);
if (method != null) {
this.hints.reflection().registerMethod(method, ExecutableMode.INVOKE);
}
}
private void addConstructorArgumentValues(CodeBlock.Builder code,
BeanDefinition beanDefinition) {
Map<Integer, ValueHolder> argumentValues = beanDefinition
.getConstructorArgumentValues().getIndexedArgumentValues();
private void addConstructorArgumentValues(CodeBlock.Builder code, BeanDefinition beanDefinition) {
Map<Integer, ValueHolder> argumentValues =
beanDefinition.getConstructorArgumentValues().getIndexedArgumentValues();
if (!argumentValues.isEmpty()) {
argumentValues.forEach((index, valueHolder) -> {
CodeBlock valueCode = generateValue(valueHolder.getName(), valueHolder.getValue());
@@ -161,9 +176,7 @@ class BeanDefinitionPropertiesCodeGenerator {
}
}
private void addPropertyValues(CodeBlock.Builder code,
RootBeanDefinition beanDefinition) {
private void addPropertyValues(CodeBlock.Builder code, RootBeanDefinition beanDefinition) {
MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
if (!propertyValues.isEmpty()) {
for (PropertyValue propertyValue : propertyValues) {
@@ -185,9 +198,7 @@ class BeanDefinitionPropertiesCodeGenerator {
}
}
private void addQualifiers(CodeBlock.Builder code,
RootBeanDefinition beanDefinition) {
private void addQualifiers(CodeBlock.Builder code, RootBeanDefinition beanDefinition) {
Set<AutowireCandidateQualifier> qualifiers = beanDefinition.getQualifiers();
if (!qualifiers.isEmpty()) {
for (AutowireCandidateQualifier qualifier : qualifiers) {
@@ -246,8 +257,8 @@ class BeanDefinitionPropertiesCodeGenerator {
}
private boolean hasScope(String defaultValue, String actualValue) {
return StringUtils.hasText(actualValue)
&& !ConfigurableBeanFactory.SCOPE_SINGLETON.equals(actualValue);
return StringUtils.hasText(actualValue) &&
!ConfigurableBeanFactory.SCOPE_SINGLETON.equals(actualValue);
}
private boolean hasDependsOn(String[] defaultValue, String[] actualValue) {
@@ -259,16 +270,15 @@ class BeanDefinitionPropertiesCodeGenerator {
}
private CodeBlock toStringVarArgs(String[] strings) {
return Arrays.stream(strings).map(string -> CodeBlock.of("$S", string))
.collect(CodeBlock.joining(","));
return Arrays.stream(strings).map(string -> CodeBlock.of("$S", string)).collect(CodeBlock.joining(","));
}
private Object toRole(int value) {
return switch (value) {
case BeanDefinition.ROLE_INFRASTRUCTURE -> CodeBlock.builder()
.add("$T.ROLE_INFRASTRUCTURE", BeanDefinition.class).build();
case BeanDefinition.ROLE_SUPPORT -> CodeBlock.builder()
.add("$T.ROLE_SUPPORT", BeanDefinition.class).build();
case BeanDefinition.ROLE_INFRASTRUCTURE ->
CodeBlock.builder().add("$T.ROLE_INFRASTRUCTURE", BeanDefinition.class).build();
case BeanDefinition.ROLE_SUPPORT ->
CodeBlock.builder().add("$T.ROLE_SUPPORT", BeanDefinition.class).build();
default -> value;
};
}
@@ -278,16 +288,14 @@ class BeanDefinitionPropertiesCodeGenerator {
Function<B, T> getter, String format) {
addStatementForValue(code, beanDefinition, getter,
(defaultValue, actualValue) -> !Objects.equals(defaultValue, actualValue),
format);
(defaultValue, actualValue) -> !Objects.equals(defaultValue, actualValue), format);
}
private <B extends BeanDefinition, T> void addStatementForValue(
CodeBlock.Builder code, BeanDefinition beanDefinition,
Function<B, T> getter, BiPredicate<T, T> filter, String format) {
addStatementForValue(code, beanDefinition, getter, filter, format,
actualValue -> actualValue);
addStatementForValue(code, beanDefinition, getter, filter, format, actualValue -> actualValue);
}
@SuppressWarnings("unchecked")
@@ -299,8 +307,7 @@ class BeanDefinitionPropertiesCodeGenerator {
T defaultValue = getter.apply((B) DEFAULT_BEAN_DEFINITION);
T actualValue = getter.apply((B) beanDefinition);
if (filter.test(defaultValue, actualValue)) {
code.addStatement(format, BEAN_DEFINITION_VARIABLE,
formatter.apply(actualValue));
code.addStatement(format, BEAN_DEFINITION_VARIABLE, formatter.apply(actualValue));
}
}
@@ -172,7 +172,9 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
* {@code generator} supplier to instantiate the underlying bean.
* @param generator a {@link ThrowingSupplier} to instantiate the underlying bean
* @return a new {@link BeanInstanceSupplier} instance with the specified generator
* @deprecated in favor of {@link #withGenerator(ThrowingFunction)}
*/
@Deprecated(since = "6.0.11", forRemoval = true)
public BeanInstanceSupplier<T> withGenerator(ThrowingSupplier<T> generator) {
Assert.notNull(generator, "'generator' must not be null");
return new BeanInstanceSupplier<>(this.lookup,
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.beans.factory.aot;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.function.Predicate;
@@ -47,12 +48,6 @@ import org.springframework.util.ClassUtils;
*/
class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragments {
/**
* The variable name used to hold the bean type.
*/
private static final String BEAN_TYPE_VARIABLE = "beanType";
private final BeanRegistrationsCode beanRegistrationsCode;
private final RegisteredBean registeredBean;
@@ -118,19 +113,45 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
ResolvableType beanType, BeanRegistrationCode beanRegistrationCode) {
CodeBlock.Builder code = CodeBlock.builder();
code.addStatement(generateBeanTypeCode(beanType));
RootBeanDefinition mergedBeanDefinition = this.registeredBean.getMergedBeanDefinition();
Class<?> beanClass = (mergedBeanDefinition.hasBeanClass()
? ClassUtils.getUserClass(mergedBeanDefinition.getBeanClass()) : null);
CodeBlock beanClassCode = generateBeanClassCode(
beanRegistrationCode.getClassName().packageName(), beanClass);
code.addStatement("$T $L = new $T($L)", RootBeanDefinition.class,
BEAN_DEFINITION_VARIABLE, RootBeanDefinition.class, BEAN_TYPE_VARIABLE);
BEAN_DEFINITION_VARIABLE, RootBeanDefinition.class, beanClassCode);
if (targetTypeNecessary(beanType, beanClass)) {
code.addStatement("$L.setTargetType($L)", BEAN_DEFINITION_VARIABLE,
generateBeanTypeCode(beanType));
}
return code.build();
}
private CodeBlock generateBeanClassCode(String targetPackage, @Nullable Class<?> beanClass) {
if (beanClass != null) {
if (Modifier.isPublic(beanClass.getModifiers()) || targetPackage.equals(beanClass.getPackageName())) {
return CodeBlock.of("$T.class", beanClass);
}
else {
return CodeBlock.of("$S", beanClass.getName());
}
}
return CodeBlock.of("");
}
private CodeBlock generateBeanTypeCode(ResolvableType beanType) {
if (!beanType.hasGenerics()) {
return CodeBlock.of("$T<?> $L = $T.class", Class.class, BEAN_TYPE_VARIABLE,
ClassUtils.getUserClass(beanType.toClass()));
return CodeBlock.of("$T.class", ClassUtils.getUserClass(beanType.toClass()));
}
return CodeBlock.of("$T $L = $L", ResolvableType.class, BEAN_TYPE_VARIABLE,
ResolvableTypeCodeGenerator.generateCode(beanType));
return ResolvableTypeCodeGenerator.generateCode(beanType);
}
private boolean targetTypeNecessary(ResolvableType beanType, @Nullable Class<?> beanClass) {
if (beanType.hasGenerics() || beanClass == null) {
return true;
}
return (!beanType.toClass().equals(beanClass)
|| this.registeredBean.getMergedBeanDefinition().getFactoryMethodName() != null);
}
@Override
@@ -220,7 +220,8 @@ class InstanceSupplierCodeGenerator {
CodeBlock.Builder code = CodeBlock.builder();
code.add("$T.<$T>forFactoryMethod($T.class, $S)", BeanInstanceSupplier.class,
suppliedType, declaringClass, factoryMethod.getName());
code.add(".withGenerator($T::$L)", declaringClass, factoryMethod.getName());
code.add(".withGenerator(($L) -> $T.$L())", REGISTERED_BEAN_PARAMETER_NAME,
declaringClass, factoryMethod.getName());
return code.build();
}
@@ -1794,10 +1794,11 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
}
/**
* Give a bean a chance to react now all its properties are set,
* Give a bean a chance to initialize itself after all its properties are set,
* and a chance to know about its owning bean factory (this object).
* This means checking whether the bean implements InitializingBean or defines
* a custom init method, and invoking the necessary callback(s) if it does.
* <p>This means checking whether the bean implements {@link InitializingBean}
* or defines any custom init methods, and invoking the necessary callback(s)
* if it does.
* @param beanName the bean name in the factory (for debugging purposes)
* @param bean the new bean instance we may need to initialize
* @param mbd the merged bean definition that the bean was created with
@@ -1832,26 +1833,30 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
/**
* Invoke the specified custom init method on the given bean.
* Called by invokeInitMethods.
* <p>Can be overridden in subclasses for custom resolution of init
* methods with arguments.
* <p>Called by {@link #invokeInitMethods(String, Object, RootBeanDefinition)}.
* <p>Can be overridden in subclasses for custom resolution of init methods
* with arguments.
* @see #invokeInitMethods
*/
protected void invokeCustomInitMethod(String beanName, Object bean, RootBeanDefinition mbd, String initMethodName)
throws Throwable {
Class<?> beanClass = bean.getClass();
MethodDescriptor descriptor = MethodDescriptor.create(beanName, beanClass, initMethodName);
String methodName = descriptor.methodName();
Method initMethod = (mbd.isNonPublicAccessAllowed() ?
BeanUtils.findMethod(bean.getClass(), initMethodName) :
ClassUtils.getMethodIfAvailable(bean.getClass(), initMethodName));
BeanUtils.findMethod(descriptor.declaringClass(), methodName) :
ClassUtils.getMethodIfAvailable(beanClass, methodName));
if (initMethod == null) {
if (mbd.isEnforceInitMethod()) {
throw new BeanDefinitionValidationException("Could not find an init method named '" +
initMethodName + "' on bean with name '" + beanName + "'");
methodName + "' on bean with name '" + beanName + "'");
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No default init method named '" + initMethodName +
logger.trace("No default init method named '" + methodName +
"' found on bean with name '" + beanName + "'");
}
// Ignore non-existent default lifecycle methods.
@@ -1860,9 +1865,9 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
}
if (logger.isTraceEnabled()) {
logger.trace("Invoking init method '" + initMethodName + "' on bean with name '" + beanName + "'");
logger.trace("Invoking init method '" + methodName + "' on bean with name '" + beanName + "'");
}
Method methodToInvoke = ClassUtils.getInterfaceMethodIfPossible(initMethod, bean.getClass());
Method methodToInvoke = ClassUtils.getInterfaceMethodIfPossible(initMethod, beanClass);
try {
ReflectionUtils.makeAccessible(methodToInvoke);
@@ -390,7 +390,8 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
@Override
@Nullable
public String getBeanClassName() {
return (this.beanClass instanceof Class<?> clazz ? clazz.getName() : (String) this.beanClass);
Object beanClassObject = this.beanClass; // defensive access to volatile beanClass field
return (beanClassObject instanceof Class<?> clazz ? clazz.getName() : (String) beanClassObject);
}
/**
@@ -423,7 +424,7 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
* @see #resolveBeanClass(ClassLoader)
*/
public Class<?> getBeanClass() throws IllegalStateException {
Object beanClassObject = this.beanClass;
Object beanClassObject = this.beanClass; // defensive access to volatile beanClass field
if (beanClassObject == null) {
throw new IllegalStateException("No bean class specified on bean definition");
}
@@ -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.
@@ -125,7 +125,8 @@ public final class BeanDefinitionBuilder {
* @since 5.3.9
*/
public static <T> BeanDefinitionBuilder rootBeanDefinition(ResolvableType beanType, Supplier<T> instanceSupplier) {
RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType);
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setTargetType(beanType);
beanDefinition.setInstanceSupplier(instanceSupplier);
return new BeanDefinitionBuilder(beanDefinition);
}
@@ -979,7 +979,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
for (String beanName : beanNames) {
Object singletonInstance = getSingleton(beanName);
if (singletonInstance instanceof SmartInitializingSingleton smartSingleton) {
StartupStep smartInitialize = this.getApplicationStartup().start("spring.beans.smart-initialize")
StartupStep smartInitialize = getApplicationStartup().start("spring.beans.smart-initialize")
.tag("beanName", beanName);
smartSingleton.afterSingletonsInstantiated();
smartInitialize.end();
@@ -447,17 +447,17 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
}
String canonicalName = canonicalName(beanName);
Set<String> dependentBeans = this.dependentBeanMap.get(canonicalName);
if (dependentBeans == null) {
if (dependentBeans == null || dependentBeans.isEmpty()) {
return false;
}
if (dependentBeans.contains(dependentBeanName)) {
return true;
}
if (alreadySeen == null) {
alreadySeen = new HashSet<>();
}
alreadySeen.add(beanName);
for (String transitiveDependency : dependentBeans) {
if (alreadySeen == null) {
alreadySeen = new HashSet<>();
}
alreadySeen.add(beanName);
if (isDependent(transitiveDependency, dependentBeanName, alreadySeen)) {
return true;
}
@@ -116,8 +116,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
if (!this.invokeAutoCloseable) {
this.destroyMethodNames = destroyMethodNames;
List<Method> destroyMethods = new ArrayList<>(destroyMethodNames.length);
for (int i = 0; i < destroyMethodNames.length; i++) {
String destroyMethodName = destroyMethodNames[i];
for (String destroyMethodName : destroyMethodNames) {
Method destroyMethod = determineDestroyMethod(destroyMethodName);
if (destroyMethod == null) {
if (beanDefinition.isEnforceDestroyMethod()) {
@@ -252,15 +251,18 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
@Nullable
private Method determineDestroyMethod(String name) {
private Method determineDestroyMethod(String destroyMethodName) {
try {
Class<?> beanClass = this.bean.getClass();
Method destroyMethod = findDestroyMethod(beanClass, name);
MethodDescriptor descriptor = MethodDescriptor.create(this.beanName, beanClass, destroyMethodName);
String methodName = descriptor.methodName();
Method destroyMethod = findDestroyMethod(descriptor.declaringClass(), methodName);
if (destroyMethod != null) {
return destroyMethod;
}
for (Class<?> beanInterface : beanClass.getInterfaces()) {
destroyMethod = findDestroyMethod(beanInterface, name);
destroyMethod = findDestroyMethod(beanInterface, methodName);
if (destroyMethod != null) {
return destroyMethod;
}
@@ -0,0 +1,73 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.support;
import org.springframework.util.ClassUtils;
/**
* Descriptor for a {@link java.lang.reflect.Method Method} which holds a
* reference to the method's {@linkplain #declaringClass declaring class},
* {@linkplain #methodName name}, and {@linkplain #parameterTypes parameter types}.
*
* @param declaringClass the method's declaring class
* @param methodName the name of the method
* @param parameterTypes the types of parameters accepted by the method
* @author Sam Brannen
* @since 6.0.11
*/
record MethodDescriptor(Class<?> declaringClass, String methodName, Class<?>... parameterTypes) {
/**
* Create a {@link MethodDescriptor} for the supplied bean class and method name.
* <p>The supplied {@code methodName} may be a {@linkplain Method#getName()
* simple method name} or a
* {@linkplain org.springframework.util.ClassUtils#getQualifiedMethodName(Method)
* qualified method name}.
* <p>If the method name is fully qualified, this utility will parse the
* method name and its declaring class from the qualified method name and then
* attempt to load the method's declaring class using the {@link ClassLoader}
* of the supplied {@code beanClass}. Otherwise, the returned descriptor will
* reference the supplied {@code beanClass} and {@code methodName}.
* @param beanName the bean name in the factory (for debugging purposes)
* @param beanClass the bean class
* @param methodName the name of the method
* @return a new {@code MethodDescriptor}; never {@code null}
*/
static MethodDescriptor create(String beanName, Class<?> beanClass, String methodName) {
try {
Class<?> declaringClass = beanClass;
String methodNameToUse = methodName;
// Parse fully-qualified method name if necessary.
int indexOfDot = methodName.lastIndexOf('.');
if (indexOfDot > 0) {
String className = methodName.substring(0, indexOfDot);
methodNameToUse = methodName.substring(indexOfDot + 1);
if (!beanClass.getName().equals(className)) {
declaringClass = ClassUtils.forName(className, beanClass.getClassLoader());
}
}
return new MethodDescriptor(declaringClass, methodNameToUse);
}
catch (Exception | LinkageError ex) {
throw new BeanDefinitionValidationException(
"Could not create MethodDescriptor for method '%s' on bean with name '%s': %s"
.formatted(methodName, beanName, ex.getMessage()));
}
}
}
@@ -161,7 +161,9 @@ public class RootBeanDefinition extends AbstractBeanDefinition {
* @param beanType the type of bean to instantiate
* @since 6.0
* @see #setTargetType(ResolvableType)
* @deprecated as of 6.0.11, in favor of an extra {@link #setTargetType(ResolvableType)} call
*/
@Deprecated(since = "6.0.11")
public RootBeanDefinition(@Nullable ResolvableType beanType) {
setTargetType(beanType);
}
@@ -495,14 +497,15 @@ public class RootBeanDefinition extends AbstractBeanDefinition {
/**
* Register an externally managed configuration initialization method &mdash;
* for example, a method annotated with JSR-250's
* {@link jakarta.annotation.PostConstruct} annotation.
* <p>The supplied {@code initMethod} may be the
* {@linkplain Method#getName() simple method name} for non-private methods or the
* for example, a method annotated with JSR-250's {@code javax.annotation.PostConstruct}
* or Jakarta's {@link jakarta.annotation.PostConstruct} annotation.
* <p>The supplied {@code initMethod} may be a
* {@linkplain Method#getName() simple method name} or a
* {@linkplain org.springframework.util.ClassUtils#getQualifiedMethodName(Method)
* qualified method name} for {@code private} methods. A qualified name is
* necessary for {@code private} methods in order to disambiguate between
* multiple private methods with the same name within a class hierarchy.
* qualified method name} for package-private and {@code private} methods.
* A qualified name is necessary for package-private and {@code private} methods
* in order to disambiguate between multiple such methods with the same name
* within a type hierarchy.
*/
public void registerExternallyManagedInitMethod(String initMethod) {
synchronized (this.postProcessingLock) {
@@ -541,23 +544,12 @@ public class RootBeanDefinition extends AbstractBeanDefinition {
if (isExternallyManagedInitMethod(initMethod)) {
return true;
}
if (this.externallyManagedInitMethods != null) {
for (String candidate : this.externallyManagedInitMethods) {
int indexOfDot = candidate.lastIndexOf('.');
if (indexOfDot >= 0) {
String methodName = candidate.substring(indexOfDot + 1);
if (methodName.equals(initMethod)) {
return true;
}
}
}
}
return false;
return hasAnyExternallyManagedMethod(this.externallyManagedInitMethods, initMethod);
}
}
/**
* Return all externally managed initialization methods (as an immutable Set).
* Get all externally managed initialization methods (as an immutable Set).
* <p>See {@link #registerExternallyManagedInitMethod} for details
* regarding the format for the initialization methods in the returned set.
* @since 5.3.11
@@ -627,19 +619,23 @@ public class RootBeanDefinition extends AbstractBeanDefinition {
if (isExternallyManagedDestroyMethod(destroyMethod)) {
return true;
}
if (this.externallyManagedDestroyMethods != null) {
for (String candidate : this.externallyManagedDestroyMethods) {
int indexOfDot = candidate.lastIndexOf('.');
if (indexOfDot >= 0) {
String methodName = candidate.substring(indexOfDot + 1);
if (methodName.equals(destroyMethod)) {
return true;
}
return hasAnyExternallyManagedMethod(this.externallyManagedDestroyMethods, destroyMethod);
}
}
private static boolean hasAnyExternallyManagedMethod(Set<String> candidates, String methodName) {
if (candidates != null) {
for (String candidate : candidates) {
int indexOfDot = candidate.lastIndexOf('.');
if (indexOfDot > 0) {
String candidateMethodName = candidate.substring(indexOfDot + 1);
if (candidateMethodName.equals(methodName)) {
return true;
}
}
}
return false;
}
return false;
}
/**
@@ -667,22 +667,22 @@ abstract class AbstractPropertyAccessorTests {
accessor.setPropertyValue("myDouble", doubleValue);
assertThat(target.getMyPrimitiveByte()).isEqualTo(Byte.MAX_VALUE);
assertThat(target.getMyByte().byteValue()).isEqualTo(Byte.MAX_VALUE);
assertThat(target.getMyByte()).isEqualTo(Byte.MAX_VALUE);
assertThat(target.getMyPrimitiveShort()).isEqualTo(Short.MAX_VALUE);
assertThat(target.getMyShort().shortValue()).isEqualTo(Short.MAX_VALUE);
assertThat(target.getMyShort()).isEqualTo(Short.MAX_VALUE);
assertThat(target.getMyPrimitiveInt()).isEqualTo(Integer.MAX_VALUE);
assertThat(target.getMyInteger().intValue()).isEqualTo(Integer.MAX_VALUE);
assertThat(target.getMyInteger()).isEqualTo(Integer.MAX_VALUE);
assertThat(target.getMyPrimitiveLong()).isEqualTo(Long.MAX_VALUE);
assertThat(target.getMyLong().longValue()).isEqualTo(Long.MAX_VALUE);
assertThat(target.getMyLong()).isEqualTo(Long.MAX_VALUE);
assertThat((double) target.getMyPrimitiveFloat()).isCloseTo(Float.MAX_VALUE, within(0.001));
assertThat((double) target.getMyFloat()).isCloseTo(Float.MAX_VALUE, within(0.001));
assertThat(target.getMyPrimitiveDouble()).isCloseTo(Double.MAX_VALUE, within(0.001));
assertThat(target.getMyDouble().doubleValue()).isCloseTo(Double.MAX_VALUE, within(0.001));
assertThat(target.getMyDouble()).isCloseTo(Double.MAX_VALUE, within(0.001));
}
@Test
@@ -1994,6 +1994,7 @@ abstract class AbstractPropertyAccessorTests {
}
@SuppressWarnings("unchecked")
static class Spr13837Bean implements Spr13837 {
protected Integer something;
@@ -2004,7 +2005,6 @@ abstract class AbstractPropertyAccessorTests {
}
@Override
@SuppressWarnings("unchecked")
public Spr13837Bean setSomething(final Integer something) {
this.something = something;
return this;
@@ -35,8 +35,7 @@ public abstract class AbstractPropertyValuesTests {
assertThat(pvs.contains("forname")).as("Contains forname").isTrue();
assertThat(pvs.contains("surname")).as("Contains surname").isTrue();
assertThat(pvs.contains("age")).as("Contains age").isTrue();
boolean condition1 = !pvs.contains("tory");
assertThat(condition1).as("Doesn't contain tory").isTrue();
assertThat(!pvs.contains("tory")).as("Doesn't contain tory").isTrue();
PropertyValue[] ps = pvs.getPropertyValues();
Map<String, String> m = new HashMap<>();
@@ -46,8 +45,7 @@ public abstract class AbstractPropertyValuesTests {
for (PropertyValue element : ps) {
Object val = m.get(element.getName());
assertThat(val).as("Can't have unexpected value").isNotNull();
boolean condition = val instanceof String;
assertThat(condition).as("Val i string").isTrue();
assertThat(val instanceof String).as("Val i string").isTrue();
assertThat(val.equals(element.getValue())).as("val matches expected").isTrue();
m.remove(element.getName());
}
@@ -335,7 +335,7 @@ class BeanWrapperGenericsTests {
Object obj = gb.getMapOfListOfInteger().get("testKey").get(0);
assertThat(obj).isInstanceOf(Integer.class);
assertThat(((Integer) obj).intValue()).isEqualTo(1);
assertThat(obj).isEqualTo(1);
}
@Test
@@ -351,7 +351,7 @@ class BeanWrapperGenericsTests {
Object obj = gb.getListOfMapOfInteger().get(0).get("testKey");
assertThat(obj).isInstanceOf(Integer.class);
assertThat(((Integer) obj).intValue()).isEqualTo(5);
assertThat(obj).isEqualTo(5);
}
@Test
@@ -366,7 +366,7 @@ class BeanWrapperGenericsTests {
Object obj = gb.getMapOfListOfListOfInteger().get("testKey").get(0).get(0);
assertThat(obj).isInstanceOf(Integer.class);
assertThat(((Integer) obj).intValue()).isEqualTo(1);
assertThat(obj).isEqualTo(1);
}
@Test
@@ -155,8 +155,7 @@ public class BeanFactoryUtilsTests {
assertThat(beans.get("t1")).isEqualTo(t1);
assertThat(beans.get("t2")).isEqualTo(t2);
assertThat(beans.get("t3")).isEqualTo(t3.getObject());
boolean condition = beans.get("t4") instanceof TestBean;
assertThat(condition).isTrue();
assertThat(beans.get("t4") instanceof TestBean).isTrue();
beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(lbf, DummyFactory.class, true, true);
assertThat(beans).hasSize(2);
@@ -192,8 +191,7 @@ public class BeanFactoryUtilsTests {
assertThat(beans.get("t1")).isEqualTo(t1);
assertThat(beans.get("t2")).isEqualTo(t2);
assertThat(beans.get("t3")).isEqualTo(t3.getObject());
boolean condition2 = beans.get("t4") instanceof TestBean;
assertThat(condition2).isTrue();
assertThat(beans.get("t4") instanceof TestBean).isTrue();
// t3 and t4 are found here as of Spring 2.0, since they are pre-registered
// singleton instances, while testFactory1 and testFactory are *not* found
// because they are FactoryBean definitions that haven't been initialized yet.
@@ -212,13 +210,11 @@ public class BeanFactoryUtilsTests {
assertThat(beans.get("test3")).isEqualTo(test3);
assertThat(beans.get("test")).isEqualTo(test);
assertThat(beans.get("testFactory1")).isEqualTo(testFactory1);
boolean condition1 = beans.get("testFactory2") instanceof TestBean;
assertThat(condition1).isTrue();
assertThat(beans.get("testFactory2") instanceof TestBean).isTrue();
assertThat(beans.get("t1")).isEqualTo(t1);
assertThat(beans.get("t2")).isEqualTo(t2);
assertThat(beans.get("t3")).isEqualTo(t3.getObject());
boolean condition = beans.get("t4") instanceof TestBean;
assertThat(condition).isTrue();
assertThat(beans.get("t4") instanceof TestBean).isTrue();
beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.listableBeanFactory, DummyFactory.class, true, true);
assertThat(beans).hasSize(4);
@@ -261,8 +257,7 @@ public class BeanFactoryUtilsTests {
assertThat(beans.get("test3")).isEqualTo(test3);
assertThat(beans.get("test")).isEqualTo(test);
assertThat(beans.get("testFactory1")).isEqualTo(testFactory1);
boolean condition = beans.get("testFactory2") instanceof TestBean;
assertThat(condition).isTrue();
assertThat(beans.get("testFactory2") instanceof TestBean).isTrue();
beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.listableBeanFactory, DummyFactory.class, true, true);
assertThat(beans).hasSize(2);
@@ -3190,6 +3190,7 @@ class DefaultListableBeanFactoryTests {
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private static class CustomTypeConverter implements TypeConverter {
private final NumberFormat numberFormat;
@@ -3199,11 +3200,10 @@ class DefaultListableBeanFactoryTests {
}
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public Object convertIfNecessary(Object value, @Nullable Class requiredType) {
if (value instanceof String && Float.class.isAssignableFrom(requiredType)) {
if (value instanceof String text && Float.class.isAssignableFrom(requiredType)) {
try {
return this.numberFormat.parse((String) value).floatValue();
return this.numberFormat.parse(text).floatValue();
}
catch (ParseException ex) {
throw new TypeMismatchException(value, requiredType, ex);
@@ -3218,13 +3218,11 @@ class DefaultListableBeanFactoryTests {
}
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public Object convertIfNecessary(Object value, @Nullable Class requiredType, @Nullable MethodParameter methodParam) {
return convertIfNecessary(value, requiredType);
}
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public Object convertIfNecessary(Object value, @Nullable Class requiredType, @Nullable Field field) {
return convertIfNecessary(value, requiredType);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,8 @@ package org.springframework.beans.factory.annotation;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RegisteredBean;
@@ -29,12 +31,15 @@ import org.springframework.beans.testfixture.beans.factory.generator.lifecycle.I
import org.springframework.beans.testfixture.beans.factory.generator.lifecycle.MultiInitDestroyBean;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.SoftAssertions.assertSoftly;
/**
* Tests for {@link InitDestroyAnnotationBeanPostProcessor}.
*
* @since 6.0
* @author Stephane Nicoll
* @author Phillip Webb
* @author Sam Brannen
*/
class InitDestroyAnnotationBeanPostProcessorTests {
@@ -65,8 +70,8 @@ class InitDestroyAnnotationBeanPostProcessorTests {
beanDefinition.setDestroyMethodNames("customDestroyMethod");
processAheadOfTime(beanDefinition);
RootBeanDefinition mergedBeanDefinition = getMergedBeanDefinition();
assertThat(mergedBeanDefinition.getInitMethodNames()).containsExactly("customInitMethod", "initMethod");
assertThat(mergedBeanDefinition.getDestroyMethodNames()).containsExactly("customDestroyMethod", "destroyMethod");
assertThat(mergedBeanDefinition.getInitMethodNames()).containsExactly("initMethod", "customInitMethod");
assertThat(mergedBeanDefinition.getDestroyMethodNames()).containsExactly("destroyMethod", "customDestroyMethod");
}
@Test
@@ -109,6 +114,35 @@ class InitDestroyAnnotationBeanPostProcessorTests {
assertThat(mergedBeanDefinition.getDestroyMethodNames()).containsExactly("anotherDestroyMethod", "destroyMethod");
}
@Test
void processAheadOfTimeWithMultipleLevelsOfPublicAndPrivateInitAndDestroyMethods() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(CustomAnnotatedPrivateSameNameInitDestroyBean.class);
// We explicitly define "afterPropertiesSet" as a "custom init method"
// to ensure that it will be tracked as such even though it has the same
// name as InitializingBean#afterPropertiesSet().
beanDefinition.setInitMethodNames("afterPropertiesSet", "customInit");
// We explicitly define "destroy" as a "custom destroy method"
// to ensure that it will be tracked as such even though it has the same
// name as DisposableBean#destroy().
beanDefinition.setDestroyMethodNames("destroy", "customDestroy");
processAheadOfTime(beanDefinition);
RootBeanDefinition mergedBeanDefinition = getMergedBeanDefinition();
assertSoftly(softly -> {
softly.assertThat(mergedBeanDefinition.getInitMethodNames()).containsExactly(
CustomAnnotatedPrivateInitDestroyBean.class.getName() + ".privateInit", // fully-qualified private method
CustomAnnotatedPrivateSameNameInitDestroyBean.class.getName() + ".privateInit", // fully-qualified private method
"afterPropertiesSet",
"customInit"
);
softly.assertThat(mergedBeanDefinition.getDestroyMethodNames()).containsExactly(
CustomAnnotatedPrivateSameNameInitDestroyBean.class.getName() + ".privateDestroy", // fully-qualified private method
CustomAnnotatedPrivateInitDestroyBean.class.getName() + ".privateDestroy", // fully-qualified private method
"destroy",
"customDestroy"
);
});
}
private void processAheadOfTime(RootBeanDefinition beanDefinition) {
RegisteredBean registeredBean = registerBean(beanDefinition);
assertThat(createAotBeanPostProcessor().processAheadOfTime(registeredBean)).isNull();
@@ -133,4 +167,49 @@ class InitDestroyAnnotationBeanPostProcessorTests {
static class NoInitDestroyBean {}
static class CustomInitDestroyBean {
public void customInit() {
}
public void customDestroy() {
}
}
static class CustomInitializingDisposableBean extends CustomInitDestroyBean
implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() {
}
@Override
public void destroy() {
}
}
static class CustomAnnotatedPrivateInitDestroyBean extends CustomInitializingDisposableBean {
@Init
private void privateInit() {
}
@Destroy
private void privateDestroy() {
}
}
static class CustomAnnotatedPrivateSameNameInitDestroyBean extends CustomAnnotatedPrivateInitDestroyBean {
@Init
@SuppressWarnings("unused")
private void privateInit() {
}
@Destroy
@SuppressWarnings("unused")
private void privateDestroy() {
}
}
}
@@ -85,8 +85,7 @@ public class InjectAnnotationBeanPostProcessorTests {
bf.getBean("testBean");
}
catch (BeanCreationException ex) {
boolean condition = ex.getRootCause() instanceof IllegalStateException;
assertThat(condition).isTrue();
assertThat(ex.getRootCause() instanceof IllegalStateException).isTrue();
}
}
@@ -47,6 +47,10 @@ import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.beans.testfixture.beans.factory.aot.InnerBeanConfiguration;
import org.springframework.beans.testfixture.beans.factory.aot.MockBeanRegistrationsCode;
import org.springframework.beans.testfixture.beans.factory.aot.SimpleBean;
import org.springframework.beans.testfixture.beans.factory.aot.TestHierarchy;
import org.springframework.beans.testfixture.beans.factory.aot.TestHierarchy.Implementation;
import org.springframework.beans.testfixture.beans.factory.aot.TestHierarchy.One;
import org.springframework.beans.testfixture.beans.factory.aot.TestHierarchy.Two;
import org.springframework.core.ResolvableType;
import org.springframework.core.test.io.support.MockSpringFactoriesLoader;
import org.springframework.core.test.tools.CompileWithForkedClassLoader;
@@ -56,6 +60,7 @@ import org.springframework.core.test.tools.TestCompiler;
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.MethodSpec;
import org.springframework.javapoet.ParameterizedTypeName;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
@@ -89,8 +94,10 @@ class BeanDefinitionMethodGeneratorTests {
@Test
void generateBeanDefinitionMethodGeneratesMethod() {
RegisteredBean registeredBean = registerBean(new RootBeanDefinition(TestBean.class));
void generateBeanDefinitionMethodWithOnlyTargetTypeDoesNotSetBeanClass() {
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setTargetType(TestBean.class);
RegisteredBean registeredBean = registerBean(beanDefinition);
BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator(
this.methodGeneratorFactory, registeredBean, null,
Collections.emptyList());
@@ -99,7 +106,67 @@ class BeanDefinitionMethodGeneratorTests {
compile(method, (actual, compiled) -> {
SourceFile sourceFile = compiled.getSourceFile(".*BeanDefinitions");
assertThat(sourceFile).contains("Get the bean definition for 'testBean'");
assertThat(sourceFile).contains("beanType = TestBean.class");
assertThat(sourceFile).contains("new RootBeanDefinition()");
assertThat(sourceFile).contains("setTargetType(TestBean.class)");
assertThat(sourceFile).contains("setInstanceSupplier(TestBean::new)");
assertThat(actual).isInstanceOf(RootBeanDefinition.class);
});
}
@Test
void generateBeanDefinitionMethodSpecifiesBeanClassIfSet() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(TestBean.class);
RegisteredBean registeredBean = registerBean(beanDefinition);
BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator(
this.methodGeneratorFactory, registeredBean, null,
Collections.emptyList());
MethodReference method = generator.generateBeanDefinitionMethod(
this.generationContext, this.beanRegistrationsCode);
compile(method, (actual, compiled) -> {
SourceFile sourceFile = compiled.getSourceFile(".*BeanDefinitions");
assertThat(sourceFile).contains("Get the bean definition for 'testBean'");
assertThat(sourceFile).contains("new RootBeanDefinition(TestBean.class)");
assertThat(sourceFile).doesNotContain("setTargetType(");
assertThat(sourceFile).contains("setInstanceSupplier(TestBean::new)");
assertThat(actual).isInstanceOf(RootBeanDefinition.class);
});
}
@Test
void generateBeanDefinitionMethodSpecifiesBeanClassAndTargetTypIfDifferent() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(One.class);
beanDefinition.setTargetType(Implementation.class);
beanDefinition.setResolvedFactoryMethod(ReflectionUtils.findMethod(TestHierarchy.class, "oneBean"));
RegisteredBean registeredBean = registerBean(beanDefinition);
BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator(
this.methodGeneratorFactory, registeredBean, null,
Collections.emptyList());
MethodReference method = generator.generateBeanDefinitionMethod(
this.generationContext, this.beanRegistrationsCode);
compile(method, (actual, compiled) -> {
SourceFile sourceFile = compiled.getSourceFile(".*BeanDefinitions");
assertThat(sourceFile).contains("Get the bean definition for 'testBean'");
assertThat(sourceFile).contains("new RootBeanDefinition(TestHierarchy.One.class)");
assertThat(sourceFile).contains("setTargetType(TestHierarchy.Implementation.class)");
assertThat(actual).isInstanceOf(RootBeanDefinition.class);
});
}
@Test
void generateBeanDefinitionMethodUSeBeanClassNameIfNotReachable() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(PackagePrivateTestBean.class);
beanDefinition.setTargetType(TestBean.class);
RegisteredBean registeredBean = registerBean(beanDefinition);
BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator(
this.methodGeneratorFactory, registeredBean, null,
Collections.emptyList());
MethodReference method = generator.generateBeanDefinitionMethod(
this.generationContext, this.beanRegistrationsCode);
compile(method, (actual, compiled) -> {
SourceFile sourceFile = compiled.getSourceFile(".*BeanDefinitions");
assertThat(sourceFile).contains("Get the bean definition for 'testBean'");
assertThat(sourceFile).contains("new RootBeanDefinition(\"org.springframework.beans.factory.aot.PackagePrivateTestBean\"");
assertThat(sourceFile).contains("setTargetType(TestBean.class)");
assertThat(sourceFile).contains("setInstanceSupplier(TestBean::new)");
assertThat(actual).isInstanceOf(RootBeanDefinition.class);
});
@@ -116,7 +183,6 @@ class BeanDefinitionMethodGeneratorTests {
compile(method, (actual, compiled) -> {
SourceFile sourceFile = compiled.getSourceFile(".*BeanDefinitions");
assertThat(sourceFile).contains("Get the bean definition for 'testBean'");
assertThat(sourceFile).contains("beanType = TestBean.class");
assertThat(sourceFile).contains("setInstanceSupplier(TestBean::new)");
assertThat(actual).isInstanceOf(RootBeanDefinition.class);
});
@@ -170,8 +236,9 @@ class BeanDefinitionMethodGeneratorTests {
@Test
void generateBeanDefinitionMethodWhenHasGenericsGeneratesMethod() {
RegisteredBean registeredBean = registerBean(new RootBeanDefinition(
ResolvableType.forClassWithGenerics(GenericBean.class, Integer.class)));
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setTargetType(ResolvableType.forClassWithGenerics(GenericBean.class, Integer.class));
RegisteredBean registeredBean = registerBean(beanDefinition);
BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator(
this.methodGeneratorFactory, registeredBean, null,
Collections.emptyList());
@@ -182,12 +249,26 @@ class BeanDefinitionMethodGeneratorTests {
SourceFile sourceFile = compiled.getSourceFile(".*BeanDefinitions");
assertThat(sourceFile).contains("Get the bean definition for 'testBean'");
assertThat(sourceFile).contains(
"beanType = ResolvableType.forClassWithGenerics(GenericBean.class, Integer.class)");
"setTargetType(ResolvableType.forClassWithGenerics(GenericBean.class, Integer.class))");
assertThat(sourceFile).contains("setInstanceSupplier(GenericBean::new)");
assertThat(actual).isInstanceOf(RootBeanDefinition.class);
});
}
@Test
void generateBeanDefinitionMethodWhenHasExplicitResolvableType() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(One.class);
beanDefinition.setResolvedFactoryMethod(ReflectionUtils.findMethod(TestHierarchy.class, "oneBean"));
beanDefinition.setTargetType(Two.class);
RegisteredBean registeredBean = registerBean(beanDefinition);
BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator(
this.methodGeneratorFactory, registeredBean, null,
Collections.emptyList());
MethodReference method = generator.generateBeanDefinitionMethod(
this.generationContext, this.beanRegistrationsCode);
compile(method, (actual, compiled) -> assertThat(actual.getResolvableType().resolve()).isEqualTo(Two.class));
}
@Test
void generateBeanDefinitionMethodWhenHasInstancePostProcessorGeneratesMethod() {
RegisteredBean registeredBean = registerBean(new RootBeanDefinition(TestBean.class));
@@ -28,6 +28,8 @@ import java.util.function.Supplier;
import javax.lang.model.element.Modifier;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.aot.generate.GeneratedClass;
@@ -61,6 +63,8 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Phillip Webb
* @author Stephane Nicoll
* @author Olga Maciaszek-Sharma
* @author Sam Brannen
* @since 6.0
*/
class BeanDefinitionPropertiesCodeGeneratorTests {
@@ -104,8 +108,7 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
@Test
void setScopeWhenOther() {
this.beanDefinition.setScope("prototype");
compile((actual, compiled) -> assertThat(actual.getScope())
.isEqualTo("prototype"));
compile((actual, compiled) -> assertThat(actual.getScope()).isEqualTo("prototype"));
}
@Test
@@ -120,8 +123,7 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
@Test
void setDependsOnWhenNotEmpty() {
this.beanDefinition.setDependsOn("a", "b", "c");
compile((actual, compiled) -> assertThat(actual.getDependsOn())
.containsExactly("a", "b", "c"));
compile((actual, compiled) -> assertThat(actual.getDependsOn()).containsExactly("a", "b", "c"));
}
@Test
@@ -154,8 +156,7 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
@Test
void setAutowireCandidateWhenFalse() {
this.beanDefinition.setAutowireCandidate(false);
compile(
(actual, compiled) -> assertThat(actual.isAutowireCandidate()).isFalse());
compile((actual, compiled) -> assertThat(actual.isAutowireCandidate()).isFalse());
}
@Test
@@ -179,8 +180,7 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
@Test
void setSyntheticWhenTrue() {
this.beanDefinition.setSynthetic(true);
compile(
(actual, compiled) -> assertThat(actual.isSynthetic()).isTrue());
compile((actual, compiled) -> assertThat(actual.isSynthetic()).isTrue());
}
@Test
@@ -196,8 +196,7 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
void setRoleWhenInfrastructure() {
this.beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
compile((actual, compiled) -> {
assertThat(compiled.getSourceFile())
.contains("setRole(BeanDefinition.ROLE_INFRASTRUCTURE);");
assertThat(compiled.getSourceFile()).contains("setRole(BeanDefinition.ROLE_INFRASTRUCTURE);");
assertThat(actual.getRole()).isEqualTo(BeanDefinition.ROLE_INFRASTRUCTURE);
});
}
@@ -206,8 +205,7 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
void setRoleWhenSupport() {
this.beanDefinition.setRole(BeanDefinition.ROLE_SUPPORT);
compile((actual, compiled) -> {
assertThat(compiled.getSourceFile())
.contains("setRole(BeanDefinition.ROLE_SUPPORT);");
assertThat(compiled.getSourceFile()).contains("setRole(BeanDefinition.ROLE_SUPPORT);");
assertThat(actual.getRole()).isEqualTo(BeanDefinition.ROLE_SUPPORT);
});
}
@@ -215,81 +213,16 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
@Test
void setRoleWhenOther() {
this.beanDefinition.setRole(999);
compile(
(actual, compiled) -> assertThat(actual.getRole()).isEqualTo(999));
}
@Test
void setInitMethodWhenSingleInitMethod() {
this.beanDefinition.setTargetType(InitDestroyBean.class);
this.beanDefinition.setInitMethodName("i1");
compile((actual, compiled) -> assertThat(actual.getInitMethodNames())
.containsExactly("i1"));
String[] methodNames = { "i1" };
assertHasMethodInvokeHints(InitDestroyBean.class, methodNames);
}
@Test
void setInitMethodWhenNoInitMethod() {
this.beanDefinition.setTargetType(InitDestroyBean.class);
compile((actual, compiled) -> assertThat(actual.getInitMethodNames()).isNull());
}
@Test
void setInitMethodWhenMultipleInitMethods() {
this.beanDefinition.setTargetType(InitDestroyBean.class);
this.beanDefinition.setInitMethodNames("i1", "i2");
compile((actual, compiled) -> assertThat(actual.getInitMethodNames())
.containsExactly("i1", "i2"));
String[] methodNames = { "i1", "i2" };
assertHasMethodInvokeHints(InitDestroyBean.class, methodNames);
}
@Test
void setDestroyMethodWhenDestroyInitMethod() {
this.beanDefinition.setTargetType(InitDestroyBean.class);
this.beanDefinition.setDestroyMethodName("d1");
compile(
(actual, compiled) -> assertThat(actual.getDestroyMethodNames())
.containsExactly("d1"));
String[] methodNames = { "d1" };
assertHasMethodInvokeHints(InitDestroyBean.class, methodNames);
}
@Test
void setDestroyMethodWhenNoDestroyMethod() {
this.beanDefinition.setTargetType(InitDestroyBean.class);
compile((actual, compiled) -> assertThat(actual.getDestroyMethodNames()).isNull());
}
@Test
void setDestroyMethodWhenMultipleDestroyMethods() {
this.beanDefinition.setTargetType(InitDestroyBean.class);
this.beanDefinition.setDestroyMethodNames("d1", "d2");
compile(
(actual, compiled) -> assertThat(actual.getDestroyMethodNames())
.containsExactly("d1", "d2"));
String[] methodNames = { "d1", "d2" };
assertHasMethodInvokeHints(InitDestroyBean.class, methodNames);
}
private void assertHasMethodInvokeHints(Class<?> beanType, String... methodNames) {
assertThat(methodNames).allMatch(methodName -> RuntimeHintsPredicates.reflection()
.onMethod(beanType, methodName).invoke()
.test(this.generationContext.getRuntimeHints()));
compile((actual, compiled) -> assertThat(actual.getRole()).isEqualTo(999));
}
@Test
void constructorArgumentValuesWhenValues() {
this.beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,
String.class);
this.beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(1,
"test");
this.beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(2,
123);
this.beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, String.class);
this.beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(1, "test");
this.beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(2, 123);
compile((actual, compiled) -> {
Map<Integer, ValueHolder> values = actual.getConstructorArgumentValues()
.getIndexedArgumentValues();
Map<Integer, ValueHolder> values = actual.getConstructorArgumentValues().getIndexedArgumentValues();
assertThat(values.get(0).getValue()).isEqualTo(String.class);
assertThat(values.get(1).getValue()).isEqualTo("test");
assertThat(values.get(2).getValue()).isEqualTo(123);
@@ -305,20 +238,17 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
assertThat(actual.getPropertyValues().get("test")).isEqualTo(String.class);
assertThat(actual.getPropertyValues().get("spring")).isEqualTo("framework");
});
String[] methodNames = { "setTest", "setSpring" };
assertHasMethodInvokeHints(PropertyValuesBean.class, methodNames);
assertHasMethodInvokeHints(PropertyValuesBean.class, "setTest", "setSpring");
}
@Test
void propertyValuesWhenContainsBeanReference() {
this.beanDefinition.getPropertyValues().add("myService",
new RuntimeBeanNameReference("test"));
this.beanDefinition.getPropertyValues().add("myService", new RuntimeBeanNameReference("test"));
compile((actual, compiled) -> {
assertThat(actual.getPropertyValues().contains("myService")).isTrue();
assertThat(actual.getPropertyValues().get("myService"))
.isInstanceOfSatisfying(RuntimeBeanReference.class,
beanReference -> assertThat(beanReference.getBeanName())
.isEqualTo("test"));
beanReference -> assertThat(beanReference.getBeanName()).isEqualTo("test"));
});
}
@@ -342,8 +272,7 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
compile((actual, compiled) -> {
Object value = actual.getPropertyValues().get("value");
assertThat(value).isInstanceOf(ManagedSet.class);
assertThat(((Set<?>) value).iterator().next())
.isInstanceOf(BeanReference.class);
assertThat(((Set<?>) value).iterator().next()).isInstanceOf(BeanReference.class);
});
}
@@ -369,8 +298,7 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
assertThat(actual.getPropertyValues().get("prefix")).isEqualTo("Hello");
assertThat(actual.getPropertyValues().get("name")).isEqualTo("World");
});
String[] methodNames = { "setPrefix", "setName" };
assertHasMethodInvokeHints(PropertyValuesFactoryBean.class, methodNames);
assertHasMethodInvokeHints(PropertyValuesFactoryBean.class, "setPrefix", "setName" );
}
@Test
@@ -445,13 +373,82 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
});
}
@Nested
class InitDestroyMethodTests {
private final String privateInitMethod = InitDestroyBean.class.getName() + ".privateInit";
private final String privateDestroyMethod = InitDestroyBean.class.getName() + ".privateDestroy";
@BeforeEach
void setTargetType() {
beanDefinition.setTargetType(InitDestroyBean.class);
}
@Test
void noInitMethod() {
compile((beanDef, compiled) -> assertThat(beanDef.getInitMethodNames()).isNull());
}
@Test
void singleInitMethod() {
beanDefinition.setInitMethodName("init");
compile((beanDef, compiled) -> assertThat(beanDef.getInitMethodNames()).containsExactly("init"));
assertHasMethodInvokeHints(InitDestroyBean.class, "init");
}
@Test
void privateInitMethod() {
beanDefinition.setInitMethodName(privateInitMethod);
compile((beanDef, compiled) -> assertThat(beanDef.getInitMethodNames()).containsExactly(privateInitMethod));
assertHasMethodInvokeHints(InitDestroyBean.class, "privateInit");
}
@Test
void multipleInitMethods() {
beanDefinition.setInitMethodNames("init", privateInitMethod);
compile((beanDef, compiled) -> assertThat(beanDef.getInitMethodNames()).containsExactly("init", privateInitMethod));
assertHasMethodInvokeHints(InitDestroyBean.class, "init", "privateInit");
}
@Test
void noDestroyMethod() {
compile((beanDef, compiled) -> assertThat(beanDef.getDestroyMethodNames()).isNull());
}
@Test
void singleDestroyMethod() {
beanDefinition.setDestroyMethodName("destroy");
compile((beanDef, compiled) -> assertThat(beanDef.getDestroyMethodNames()).containsExactly("destroy"));
assertHasMethodInvokeHints(InitDestroyBean.class, "destroy");
}
@Test
void privateDestroyMethod() {
beanDefinition.setDestroyMethodName(privateDestroyMethod);
compile((beanDef, compiled) -> assertThat(beanDef.getDestroyMethodNames()).containsExactly(privateDestroyMethod));
assertHasMethodInvokeHints(InitDestroyBean.class, "privateDestroy");
}
@Test
void multipleDestroyMethods() {
beanDefinition.setDestroyMethodNames("destroy", privateDestroyMethod);
compile((beanDef, compiled) -> assertThat(beanDef.getDestroyMethodNames()).containsExactly("destroy", privateDestroyMethod));
assertHasMethodInvokeHints(InitDestroyBean.class, "destroy", "privateDestroy");
}
}
private void assertHasMethodInvokeHints(Class<?> beanType, String... methodNames) {
assertThat(methodNames).allMatch(methodName -> RuntimeHintsPredicates.reflection()
.onMethod(beanType, methodName).invoke()
.test(this.generationContext.getRuntimeHints()));
}
private void compile(BiConsumer<RootBeanDefinition, Compiled> result) {
compile(attribute -> true, result);
}
private void compile(
Predicate<String> attributeFilter,
BiConsumer<RootBeanDefinition, Compiled> result) {
private void compile(Predicate<String> attributeFilter, BiConsumer<RootBeanDefinition, Compiled> result) {
DeferredTypeBuilder typeBuilder = new DeferredTypeBuilder();
GeneratedClass generatedClass = this.generationContext.getGeneratedClasses().addForFeature("TestCode", typeBuilder);
BeanDefinitionPropertiesCodeGenerator codeGenerator = new BeanDefinitionPropertiesCodeGenerator(
@@ -471,24 +468,25 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
});
this.generationContext.writeGeneratedContent();
TestCompiler.forSystem().with(this.generationContext).compile(compiled -> {
RootBeanDefinition suppliedBeanDefinition = (RootBeanDefinition) compiled
.getInstance(Supplier.class).get();
RootBeanDefinition suppliedBeanDefinition = (RootBeanDefinition) compiled.getInstance(Supplier.class).get();
result.accept(suppliedBeanDefinition, compiled);
});
}
static class InitDestroyBean {
void i1() {
void init() {
}
void i2() {
@SuppressWarnings("unused")
private void privateInit() {
}
void d1() {
void destroy() {
}
void d2() {
@SuppressWarnings("unused")
private void privateDestroy() {
}
}
@@ -186,6 +186,8 @@ class BeanInstanceSupplierTests {
}
@Test
@Deprecated
@SuppressWarnings("removal")
void withGeneratorWhenSupplierIsNullThrowsException() {
BeanInstanceSupplier<Object> resolver = BeanInstanceSupplier
.forConstructor();
@@ -245,6 +247,8 @@ class BeanInstanceSupplierTests {
}
@Test
@Deprecated
@SuppressWarnings("removal")
void getWithGeneratorCallsSupplier() throws Exception {
BeanRegistrar registrar = new BeanRegistrar(SingleArgConstructor.class);
this.beanFactory.registerSingleton("one", "1");
@@ -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.
@@ -172,8 +172,9 @@ class DefaultBeanRegistrationCodeFragmentsTests {
}
private RegisteredBean registerTestBean(ResolvableType beanType) {
this.beanFactory.registerBeanDefinition("testBean",
new RootBeanDefinition(beanType));
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setTargetType(beanType);
this.beanFactory.registerBeanDefinition("testBean", beanDefinition);
return RegisteredBean.of(this.beanFactory, "testBean");
}
@@ -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.
@@ -223,7 +223,7 @@ class InstanceSupplierCodeGeneratorTests {
assertThat(bean).isInstanceOf(Integer.class);
assertThat(bean).isEqualTo(42);
assertThat(compiled.getSourceFile())
.contains("SimpleConfiguration::integerBean");
.contains("(registeredBean) -> SimpleConfiguration.integerBean()");
});
assertThat(getReflectionHints().getTypeHint(SimpleConfiguration.class))
.satisfies(hasMethodWithMode(ExecutableMode.INTROSPECT));
@@ -66,8 +66,7 @@ public class CustomScopeConfigurerTests {
CustomScopeConfigurer figurer = new CustomScopeConfigurer();
figurer.setScopes(scopes);
figurer.postProcessBeanFactory(factory);
boolean condition = factory.getRegisteredScope(FOO_SCOPE) instanceof NoOpScope;
assertThat(condition).isTrue();
assertThat(factory.getRegisteredScope(FOO_SCOPE) instanceof NoOpScope).isTrue();
}
@Test
@@ -77,8 +76,7 @@ public class CustomScopeConfigurerTests {
CustomScopeConfigurer figurer = new CustomScopeConfigurer();
figurer.setScopes(scopes);
figurer.postProcessBeanFactory(factory);
boolean condition = factory.getRegisteredScope(FOO_SCOPE) instanceof NoOpScope;
assertThat(condition).isTrue();
assertThat(factory.getRegisteredScope(FOO_SCOPE) instanceof NoOpScope).isTrue();
}
@Test
@@ -135,9 +135,9 @@ public class MethodInvokingFactoryBeanTests {
mcfb.setTargetMethod("method1");
mcfb.afterPropertiesSet();
Integer i = (Integer) mcfb.getObject();
assertThat(i.intValue()).isEqualTo(1);
assertThat(i).isEqualTo(1);
i = (Integer) mcfb.getObject();
assertThat(i.intValue()).isEqualTo(1);
assertThat(i).isEqualTo(1);
// non-singleton, non-static
tc1 = new TestClass1();
@@ -147,9 +147,9 @@ public class MethodInvokingFactoryBeanTests {
mcfb.setSingleton(false);
mcfb.afterPropertiesSet();
i = (Integer) mcfb.getObject();
assertThat(i.intValue()).isEqualTo(1);
assertThat(i).isEqualTo(1);
i = (Integer) mcfb.getObject();
assertThat(i.intValue()).isEqualTo(2);
assertThat(i).isEqualTo(2);
// singleton, static
TestClass1._staticField1 = 0;
@@ -158,9 +158,9 @@ public class MethodInvokingFactoryBeanTests {
mcfb.setTargetMethod("staticMethod1");
mcfb.afterPropertiesSet();
i = (Integer) mcfb.getObject();
assertThat(i.intValue()).isEqualTo(1);
assertThat(i).isEqualTo(1);
i = (Integer) mcfb.getObject();
assertThat(i.intValue()).isEqualTo(1);
assertThat(i).isEqualTo(1);
// non-singleton, static
TestClass1._staticField1 = 0;
@@ -169,9 +169,9 @@ public class MethodInvokingFactoryBeanTests {
mcfb.setSingleton(false);
mcfb.afterPropertiesSet();
i = (Integer) mcfb.getObject();
assertThat(i.intValue()).isEqualTo(1);
assertThat(i).isEqualTo(1);
i = (Integer) mcfb.getObject();
assertThat(i.intValue()).isEqualTo(2);
assertThat(i).isEqualTo(2);
// void return value
mcfb = new MethodInvokingFactoryBean();
@@ -49,8 +49,7 @@ public class PropertyPathFactoryBeanTests {
assertThat(xbf.getType("otb.spouse")).isEqualTo(ITestBean.class);
Object result1 = xbf.getBean("otb.spouse");
Object result2 = xbf.getBean("otb.spouse");
boolean condition = result1 instanceof TestBean;
assertThat(condition).isTrue();
assertThat(result1 instanceof TestBean).isTrue();
assertThat(result1).isSameAs(result2);
assertThat(((TestBean) result1).getAge()).isEqualTo(99);
}
@@ -64,12 +63,9 @@ public class PropertyPathFactoryBeanTests {
Object result1 = xbf.getBean("tb.spouse");
Object result2 = xbf.getBean("propertyPath3");
Object result3 = xbf.getBean("propertyPath3");
boolean condition2 = result1 instanceof TestBean;
assertThat(condition2).isTrue();
boolean condition1 = result2 instanceof TestBean;
assertThat(condition1).isTrue();
boolean condition = result3 instanceof TestBean;
assertThat(condition).isTrue();
assertThat(result1 instanceof TestBean).isTrue();
assertThat(result2 instanceof TestBean).isTrue();
assertThat(result3 instanceof TestBean).isTrue();
assertThat(((TestBean) result1).getAge()).isEqualTo(11);
assertThat(((TestBean) result2).getAge()).isEqualTo(11);
assertThat(((TestBean) result3).getAge()).isEqualTo(11);
@@ -93,8 +89,7 @@ public class PropertyPathFactoryBeanTests {
TestBean spouse = (TestBean) xbf.getBean("otb.spouse");
TestBean tbWithInner = (TestBean) xbf.getBean("tbWithInner");
assertThat(tbWithInner.getSpouse()).isSameAs(spouse);
boolean condition = !tbWithInner.getFriends().isEmpty();
assertThat(condition).isTrue();
assertThat(!tbWithInner.getFriends().isEmpty()).isTrue();
assertThat(tbWithInner.getFriends().iterator().next()).isSameAs(spouse);
}
@@ -198,14 +198,10 @@ public class ServiceLocatorFactoryBeanTests {
assertThat(testBean3).isNotSameAs(testBean2);
assertThat(testBean4).isNotSameAs(testBean2);
assertThat(testBean4).isNotSameAs(testBean3);
boolean condition3 = testBean1 instanceof ExtendedTestService;
assertThat(condition3).isFalse();
boolean condition2 = testBean2 instanceof ExtendedTestService;
assertThat(condition2).isFalse();
boolean condition1 = testBean3 instanceof ExtendedTestService;
assertThat(condition1).isFalse();
boolean condition = testBean4 instanceof ExtendedTestService;
assertThat(condition).isTrue();
assertThat(testBean1 instanceof ExtendedTestService).isFalse();
assertThat(testBean2 instanceof ExtendedTestService).isFalse();
assertThat(testBean3 instanceof ExtendedTestService).isFalse();
assertThat(testBean4 instanceof ExtendedTestService).isTrue();
}
@Test
@@ -264,17 +260,14 @@ public class ServiceLocatorFactoryBeanTests {
public static class TestService {
}
public static class ExtendedTestService extends TestService {
}
public static class TestService2 {
}
@@ -345,7 +338,6 @@ public class ServiceLocatorFactoryBeanTests {
@SuppressWarnings("serial")
public static class ExceptionClassWithOnlyZeroArgCtor extends Exception {
}
}
@@ -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.
@@ -99,8 +99,7 @@ public class YamlMapFactoryBeanTests {
assertThat(map).hasSize(1);
assertThat(map.containsKey("foo")).isTrue();
Object object = map.get("foo");
boolean condition = object instanceof LinkedHashMap;
assertThat(condition).isTrue();
assertThat(object instanceof LinkedHashMap).isTrue();
@SuppressWarnings("unchecked")
Map<String, Object> sub = (Map<String, Object>) object;
assertThat(sub.containsKey("key1.key2")).isTrue();
@@ -115,8 +114,7 @@ public class YamlMapFactoryBeanTests {
assertThat(map).hasSize(1);
assertThat(map.containsKey("foo")).isTrue();
Object object = map.get("foo");
boolean condition = object instanceof LinkedHashMap;
assertThat(condition).isTrue();
assertThat(object instanceof LinkedHashMap).isTrue();
@SuppressWarnings("unchecked")
Map<String, Object> sub = (Map<String, Object>) object;
assertThat(sub).hasSize(1);
@@ -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.
@@ -48,8 +48,7 @@ class ServiceLoaderTests {
bd.getPropertyValues().add("serviceType", DocumentBuilderFactory.class.getName());
bf.registerBeanDefinition("service", bd);
ServiceLoader<?> serviceLoader = (ServiceLoader<?>) bf.getBean("service");
boolean condition = serviceLoader.iterator().next() instanceof DocumentBuilderFactory;
assertThat(condition).isTrue();
assertThat(serviceLoader.iterator().next() instanceof DocumentBuilderFactory).isTrue();
}
@Test
@@ -58,8 +57,7 @@ class ServiceLoaderTests {
RootBeanDefinition bd = new RootBeanDefinition(ServiceFactoryBean.class);
bd.getPropertyValues().add("serviceType", DocumentBuilderFactory.class.getName());
bf.registerBeanDefinition("service", bd);
boolean condition = bf.getBean("service") instanceof DocumentBuilderFactory;
assertThat(condition).isTrue();
assertThat(bf.getBean("service") instanceof DocumentBuilderFactory).isTrue();
}
@Test
@@ -69,8 +67,7 @@ class ServiceLoaderTests {
bd.getPropertyValues().add("serviceType", DocumentBuilderFactory.class.getName());
bf.registerBeanDefinition("service", bd);
List<?> serviceList = (List<?>) bf.getBean("service");
boolean condition = serviceList.get(0) instanceof DocumentBuilderFactory;
assertThat(condition).isTrue();
assertThat(serviceList.get(0) instanceof DocumentBuilderFactory).isTrue();
}
}
@@ -35,10 +35,8 @@ public class BeanDefinitionTests {
bd.setLazyInit(true);
bd.setScope("request");
RootBeanDefinition otherBd = new RootBeanDefinition(TestBean.class);
boolean condition1 = !bd.equals(otherBd);
assertThat(condition1).isTrue();
boolean condition = !otherBd.equals(bd);
assertThat(condition).isTrue();
assertThat(!bd.equals(otherBd)).isTrue();
assertThat(!otherBd.equals(bd)).isTrue();
otherBd.setAbstract(true);
otherBd.setLazyInit(true);
otherBd.setScope("request");
@@ -54,15 +52,11 @@ public class BeanDefinitionTests {
bd.getPropertyValues().add("age", "99");
RootBeanDefinition otherBd = new RootBeanDefinition(TestBean.class);
otherBd.getPropertyValues().add("name", "myName");
boolean condition3 = !bd.equals(otherBd);
assertThat(condition3).isTrue();
boolean condition2 = !otherBd.equals(bd);
assertThat(condition2).isTrue();
assertThat(!bd.equals(otherBd)).isTrue();
assertThat(!otherBd.equals(bd)).isTrue();
otherBd.getPropertyValues().add("age", "11");
boolean condition1 = !bd.equals(otherBd);
assertThat(condition1).isTrue();
boolean condition = !otherBd.equals(bd);
assertThat(condition).isTrue();
assertThat(!bd.equals(otherBd)).isTrue();
assertThat(!otherBd.equals(bd)).isTrue();
otherBd.getPropertyValues().add("age", "99");
assertThat(bd.equals(otherBd)).isTrue();
assertThat(otherBd.equals(bd)).isTrue();
@@ -76,15 +70,11 @@ public class BeanDefinitionTests {
bd.getConstructorArgumentValues().addIndexedArgumentValue(1, 5);
RootBeanDefinition otherBd = new RootBeanDefinition(TestBean.class);
otherBd.getConstructorArgumentValues().addGenericArgumentValue("test");
boolean condition3 = !bd.equals(otherBd);
assertThat(condition3).isTrue();
boolean condition2 = !otherBd.equals(bd);
assertThat(condition2).isTrue();
assertThat(!bd.equals(otherBd)).isTrue();
assertThat(!otherBd.equals(bd)).isTrue();
otherBd.getConstructorArgumentValues().addIndexedArgumentValue(1, 9);
boolean condition1 = !bd.equals(otherBd);
assertThat(condition1).isTrue();
boolean condition = !otherBd.equals(bd);
assertThat(condition).isTrue();
assertThat(!bd.equals(otherBd)).isTrue();
assertThat(!otherBd.equals(bd)).isTrue();
otherBd.getConstructorArgumentValues().addIndexedArgumentValue(1, 5);
assertThat(bd.equals(otherBd)).isTrue();
assertThat(otherBd.equals(bd)).isTrue();
@@ -99,15 +89,11 @@ public class BeanDefinitionTests {
RootBeanDefinition otherBd = new RootBeanDefinition(TestBean.class);
otherBd.getConstructorArgumentValues().addGenericArgumentValue("test", "int");
otherBd.getConstructorArgumentValues().addIndexedArgumentValue(1, 5);
boolean condition3 = !bd.equals(otherBd);
assertThat(condition3).isTrue();
boolean condition2 = !otherBd.equals(bd);
assertThat(condition2).isTrue();
assertThat(!bd.equals(otherBd)).isTrue();
assertThat(!otherBd.equals(bd)).isTrue();
otherBd.getConstructorArgumentValues().addIndexedArgumentValue(1, 5, "int");
boolean condition1 = !bd.equals(otherBd);
assertThat(condition1).isTrue();
boolean condition = !otherBd.equals(bd);
assertThat(condition).isTrue();
assertThat(!bd.equals(otherBd)).isTrue();
assertThat(!otherBd.equals(bd)).isTrue();
otherBd.getConstructorArgumentValues().addIndexedArgumentValue(1, 5, "long");
assertThat(bd.equals(otherBd)).isTrue();
assertThat(otherBd.equals(bd)).isTrue();
@@ -125,10 +111,8 @@ public class BeanDefinitionTests {
otherBd.setScope("request");
otherBd.setAbstract(true);
otherBd.setLazyInit(true);
boolean condition1 = !bd.equals(otherBd);
assertThat(condition1).isTrue();
boolean condition = !otherBd.equals(bd);
assertThat(condition).isTrue();
assertThat(!bd.equals(otherBd)).isTrue();
assertThat(!otherBd.equals(bd)).isTrue();
otherBd.setParentName("parent");
assertThat(bd.equals(otherBd)).isTrue();
assertThat(otherBd.equals(bd)).isTrue();
@@ -153,10 +137,8 @@ public class BeanDefinitionTests {
bd.setScope("request");
BeanDefinitionHolder holder = new BeanDefinitionHolder(bd, "bd");
RootBeanDefinition otherBd = new RootBeanDefinition(TestBean.class);
boolean condition1 = !bd.equals(otherBd);
assertThat(condition1).isTrue();
boolean condition = !otherBd.equals(bd);
assertThat(condition).isTrue();
assertThat(!bd.equals(otherBd)).isTrue();
assertThat(!otherBd.equals(bd)).isTrue();
otherBd.setAbstract(true);
otherBd.setLazyInit(true);
otherBd.setScope("request");
@@ -422,10 +422,8 @@ class BeanFactoryGenericsTests {
bf.registerBeanDefinition("genericBean", rbd);
GenericBean<?> gb = (GenericBean<?>) bf.getBean("genericBean");
boolean condition1 = gb.getCollectionMap().get(1) instanceof HashSet;
assertThat(condition1).isTrue();
boolean condition = gb.getCollectionMap().get(2) instanceof ArrayList;
assertThat(condition).isTrue();
assertThat(gb.getCollectionMap().get(1) instanceof HashSet).isTrue();
assertThat(gb.getCollectionMap().get(2) instanceof ArrayList).isTrue();
}
@@ -577,10 +575,8 @@ class BeanFactoryGenericsTests {
bf.registerBeanDefinition("genericBean", rbd);
GenericBean<?> gb = (GenericBean<?>) bf.getBean("genericBean");
boolean condition1 = gb.getCollectionMap().get(1) instanceof HashSet;
assertThat(condition1).isTrue();
boolean condition = gb.getCollectionMap().get(2) instanceof ArrayList;
assertThat(condition).isTrue();
assertThat(gb.getCollectionMap().get(1) instanceof HashSet).isTrue();
assertThat(gb.getCollectionMap().get(2) instanceof ArrayList).isTrue();
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,12 +28,13 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Chris Beams
* @since 04.07.2006
*/
public class DefaultSingletonBeanRegistryTests {
class DefaultSingletonBeanRegistryTests {
private final DefaultSingletonBeanRegistry beanRegistry = new DefaultSingletonBeanRegistry();
@Test
public void testSingletons() {
DefaultSingletonBeanRegistry beanRegistry = new DefaultSingletonBeanRegistry();
void singletons() {
TestBean tb = new TestBean();
beanRegistry.registerSingleton("tb", tb);
assertThat(beanRegistry.getSingleton("tb")).isSameAs(tb);
@@ -44,20 +45,15 @@ public class DefaultSingletonBeanRegistryTests {
assertThat(beanRegistry.getSingleton("tb")).isSameAs(tb);
assertThat(beanRegistry.getSingleton("tb2")).isSameAs(tb2);
assertThat(beanRegistry.getSingletonCount()).isEqualTo(2);
String[] names = beanRegistry.getSingletonNames();
assertThat(names).hasSize(2);
assertThat(names[0]).isEqualTo("tb");
assertThat(names[1]).isEqualTo("tb2");
assertThat(beanRegistry.getSingletonNames()).containsExactly("tb", "tb2");
beanRegistry.destroySingletons();
assertThat(beanRegistry.getSingletonCount()).isEqualTo(0);
assertThat(beanRegistry.getSingletonCount()).isZero();
assertThat(beanRegistry.getSingletonNames()).isEmpty();
}
@Test
public void testDisposableBean() {
DefaultSingletonBeanRegistry beanRegistry = new DefaultSingletonBeanRegistry();
void disposableBean() {
DerivedTestBean tb = new DerivedTestBean();
beanRegistry.registerSingleton("tb", tb);
beanRegistry.registerDisposableBean("tb", tb);
@@ -65,21 +61,17 @@ public class DefaultSingletonBeanRegistryTests {
assertThat(beanRegistry.getSingleton("tb")).isSameAs(tb);
assertThat(beanRegistry.getSingletonCount()).isEqualTo(1);
String[] names = beanRegistry.getSingletonNames();
assertThat(names).hasSize(1);
assertThat(names[0]).isEqualTo("tb");
assertThat(beanRegistry.getSingletonNames()).containsExactly("tb");
assertThat(tb.wasDestroyed()).isFalse();
beanRegistry.destroySingletons();
assertThat(beanRegistry.getSingletonCount()).isEqualTo(0);
assertThat(beanRegistry.getSingletonCount()).isZero();
assertThat(beanRegistry.getSingletonNames()).isEmpty();
assertThat(tb.wasDestroyed()).isTrue();
}
@Test
public void testDependentRegistration() {
DefaultSingletonBeanRegistry beanRegistry = new DefaultSingletonBeanRegistry();
void dependentRegistration() {
beanRegistry.registerDependentBean("a", "b");
beanRegistry.registerDependentBean("b", "c");
beanRegistry.registerDependentBean("c", "b");
@@ -337,16 +337,14 @@ public class FactoryMethodTests {
// Check that listInstance is not considered a bean of type FactoryMethods.
assertThat(List.class.isAssignableFrom(xbf.getType("listInstance"))).isTrue();
String[] names = xbf.getBeanNamesForType(FactoryMethods.class);
boolean condition1 = !Arrays.asList(names).contains("listInstance");
assertThat(condition1).isTrue();
assertThat(Arrays.asList(names).contains("listInstance")).isFalse();
names = xbf.getBeanNamesForType(List.class);
assertThat(Arrays.asList(names).contains("listInstance")).isTrue();
xbf.preInstantiateSingletons();
assertThat(List.class.isAssignableFrom(xbf.getType("listInstance"))).isTrue();
names = xbf.getBeanNamesForType(FactoryMethods.class);
boolean condition = !Arrays.asList(names).contains("listInstance");
assertThat(condition).isTrue();
assertThat(Arrays.asList(names).contains("listInstance")).isFalse();
names = xbf.getBeanNamesForType(List.class);
assertThat(Arrays.asList(names).contains("listInstance")).isTrue();
List<?> list = (List<?>) xbf.getBean("listInstance");
@@ -65,13 +65,13 @@ public class UtilNamespaceHandlerTests {
@Test
void testConstant() {
Integer min = (Integer) this.beanFactory.getBean("min");
assertThat(min.intValue()).isEqualTo(Integer.MIN_VALUE);
assertThat(min).isEqualTo(Integer.MIN_VALUE);
}
@Test
void testConstantWithDefaultName() {
Integer max = (Integer) this.beanFactory.getBean("java.lang.Integer.MAX_VALUE");
assertThat(max.intValue()).isEqualTo(Integer.MAX_VALUE);
assertThat(max).isEqualTo(Integer.MAX_VALUE);
}
@Test
@@ -70,24 +70,21 @@ public class XmlBeanCollectionTests {
ListFactoryBean listFactory = new ListFactoryBean();
listFactory.setSourceList(new LinkedList());
listFactory.afterPropertiesSet();
boolean condition2 = listFactory.getObject() instanceof ArrayList;
assertThat(condition2).isTrue();
assertThat(listFactory.getObject() instanceof ArrayList).isTrue();
SetFactoryBean setFactory = new SetFactoryBean();
setFactory.setSourceSet(new TreeSet());
setFactory.afterPropertiesSet();
boolean condition1 = setFactory.getObject() instanceof LinkedHashSet;
assertThat(condition1).isTrue();
assertThat(setFactory.getObject() instanceof LinkedHashSet).isTrue();
MapFactoryBean mapFactory = new MapFactoryBean();
mapFactory.setSourceMap(new TreeMap());
mapFactory.afterPropertiesSet();
boolean condition = mapFactory.getObject() instanceof LinkedHashMap;
assertThat(condition).isTrue();
assertThat(mapFactory.getObject() instanceof LinkedHashMap).isTrue();
}
@Test
public void testRefSubelement() throws Exception {
public void testRefSubelement() {
//assertTrue("5 beans in reftypes, not " + this.beanFactory.getBeanDefinitionCount(), this.beanFactory.getBeanDefinitionCount() == 5);
TestBean jen = (TestBean) this.beanFactory.getBean("jenny");
TestBean dave = (TestBean) this.beanFactory.getBean("david");
@@ -95,25 +92,25 @@ public class XmlBeanCollectionTests {
}
@Test
public void testPropertyWithLiteralValueSubelement() throws Exception {
public void testPropertyWithLiteralValueSubelement() {
TestBean verbose = (TestBean) this.beanFactory.getBean("verbose");
assertThat(verbose.getName()).isEqualTo("verbose");
}
@Test
public void testPropertyWithIdRefLocalAttrSubelement() throws Exception {
public void testPropertyWithIdRefLocalAttrSubelement() {
TestBean verbose = (TestBean) this.beanFactory.getBean("verbose2");
assertThat(verbose.getName()).isEqualTo("verbose");
}
@Test
public void testPropertyWithIdRefBeanAttrSubelement() throws Exception {
public void testPropertyWithIdRefBeanAttrSubelement() {
TestBean verbose = (TestBean) this.beanFactory.getBean("verbose3");
assertThat(verbose.getName()).isEqualTo("verbose");
}
@Test
public void testRefSubelementsBuildCollection() throws Exception {
public void testRefSubelementsBuildCollection() {
TestBean jen = (TestBean) this.beanFactory.getBean("jenny");
TestBean dave = (TestBean) this.beanFactory.getBean("david");
TestBean rod = (TestBean) this.beanFactory.getBean("rod");
@@ -130,7 +127,7 @@ public class XmlBeanCollectionTests {
}
@Test
public void testRefSubelementsBuildCollectionWithPrototypes() throws Exception {
public void testRefSubelementsBuildCollectionWithPrototypes() {
TestBean jen = (TestBean) this.beanFactory.getBean("pJenny");
TestBean dave = (TestBean) this.beanFactory.getBean("pDavid");
TestBean rod = (TestBean) this.beanFactory.getBean("pRod");
@@ -153,7 +150,7 @@ public class XmlBeanCollectionTests {
}
@Test
public void testRefSubelementsBuildCollectionFromSingleElement() throws Exception {
public void testRefSubelementsBuildCollectionFromSingleElement() {
TestBean loner = (TestBean) this.beanFactory.getBean("loner");
TestBean dave = (TestBean) this.beanFactory.getBean("david");
assertThat(loner.getFriends().size()).isEqualTo(1);
@@ -161,7 +158,7 @@ public class XmlBeanCollectionTests {
}
@Test
public void testBuildCollectionFromMixtureOfReferencesAndValues() throws Exception {
public void testBuildCollectionFromMixtureOfReferencesAndValues() {
MixedCollectionBean jumble = (MixedCollectionBean) this.beanFactory.getBean("jumble");
assertThat(jumble.getJumble().size()).as("Expected 5 elements, not " + jumble.getJumble().size()).isEqualTo(5);
List l = (List) jumble.getJumble();
@@ -175,7 +172,7 @@ public class XmlBeanCollectionTests {
}
@Test
public void testInvalidBeanNameReference() throws Exception {
public void testInvalidBeanNameReference() {
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(() ->
this.beanFactory.getBean("jumble2"))
.withCauseInstanceOf(BeanDefinitionStoreException.class)
@@ -183,13 +180,13 @@ public class XmlBeanCollectionTests {
}
@Test
public void testEmptyMap() throws Exception {
public void testEmptyMap() {
HasMap hasMap = (HasMap) this.beanFactory.getBean("emptyMap");
assertThat(hasMap.getMap().size()).isEqualTo(0);
}
@Test
public void testMapWithLiteralsOnly() throws Exception {
public void testMapWithLiteralsOnly() {
HasMap hasMap = (HasMap) this.beanFactory.getBean("literalMap");
assertThat(hasMap.getMap().size()).isEqualTo(3);
assertThat(hasMap.getMap().get("foo").equals("bar")).isTrue();
@@ -198,23 +195,21 @@ public class XmlBeanCollectionTests {
}
@Test
public void testMapWithLiteralsAndReferences() throws Exception {
public void testMapWithLiteralsAndReferences() {
HasMap hasMap = (HasMap) this.beanFactory.getBean("mixedMap");
assertThat(hasMap.getMap().size()).isEqualTo(5);
assertThat(hasMap.getMap().get("foo").equals(10)).isTrue();
TestBean jenny = (TestBean) this.beanFactory.getBean("jenny");
assertThat(hasMap.getMap().get("jenny")).isSameAs(jenny);
assertThat(hasMap.getMap().get(5).equals("david")).isTrue();
boolean condition1 = hasMap.getMap().get("bar") instanceof Long;
assertThat(condition1).isTrue();
assertThat(hasMap.getMap().get("bar") instanceof Long).isTrue();
assertThat(hasMap.getMap().get("bar").equals(100L)).isTrue();
boolean condition = hasMap.getMap().get("baz") instanceof Integer;
assertThat(condition).isTrue();
assertThat(hasMap.getMap().get("baz") instanceof Integer).isTrue();
assertThat(hasMap.getMap().get("baz").equals(200)).isTrue();
}
@Test
public void testMapWithLiteralsAndPrototypeReferences() throws Exception {
public void testMapWithLiteralsAndPrototypeReferences() {
TestBean jenny = (TestBean) this.beanFactory.getBean("pJenny");
HasMap hasMap = (HasMap) this.beanFactory.getBean("pMixedMap");
assertThat(hasMap.getMap().size()).isEqualTo(2);
@@ -230,7 +225,7 @@ public class XmlBeanCollectionTests {
}
@Test
public void testMapWithLiteralsReferencesAndList() throws Exception {
public void testMapWithLiteralsReferencesAndList() {
HasMap hasMap = (HasMap) this.beanFactory.getBean("mixedMapWithList");
assertThat(hasMap.getMap().size()).isEqualTo(4);
assertThat(hasMap.getMap().get(null).equals("bar")).isTrue();
@@ -267,13 +262,13 @@ public class XmlBeanCollectionTests {
}
@Test
public void testEmptySet() throws Exception {
public void testEmptySet() {
HasMap hasMap = (HasMap) this.beanFactory.getBean("emptySet");
assertThat(hasMap.getSet().size()).isEqualTo(0);
}
@Test
public void testPopulatedSet() throws Exception {
public void testPopulatedSet() {
HasMap hasMap = (HasMap) this.beanFactory.getBean("set");
assertThat(hasMap.getSet().size()).isEqualTo(3);
assertThat(hasMap.getSet().contains("bar")).isTrue();
@@ -287,7 +282,7 @@ public class XmlBeanCollectionTests {
}
@Test
public void testPopulatedConcurrentSet() throws Exception {
public void testPopulatedConcurrentSet() {
HasMap hasMap = (HasMap) this.beanFactory.getBean("concurrentSet");
assertThat(hasMap.getConcurrentSet().size()).isEqualTo(3);
assertThat(hasMap.getConcurrentSet().contains("bar")).isTrue();
@@ -297,7 +292,7 @@ public class XmlBeanCollectionTests {
}
@Test
public void testPopulatedIdentityMap() throws Exception {
public void testPopulatedIdentityMap() {
HasMap hasMap = (HasMap) this.beanFactory.getBean("identityMap");
assertThat(hasMap.getIdentityMap().size()).isEqualTo(2);
HashSet set = new HashSet(hasMap.getIdentityMap().keySet());
@@ -306,14 +301,14 @@ public class XmlBeanCollectionTests {
}
@Test
public void testEmptyProps() throws Exception {
public void testEmptyProps() {
HasMap hasMap = (HasMap) this.beanFactory.getBean("emptyProps");
assertThat(hasMap.getProps().size()).isEqualTo(0);
assertThat(Properties.class).isEqualTo(hasMap.getProps().getClass());
}
@Test
public void testPopulatedProps() throws Exception {
public void testPopulatedProps() {
HasMap hasMap = (HasMap) this.beanFactory.getBean("props");
assertThat(hasMap.getProps().size()).isEqualTo(2);
assertThat(hasMap.getProps().get("foo").equals("bar")).isTrue();
@@ -321,7 +316,7 @@ public class XmlBeanCollectionTests {
}
@Test
public void testObjectArray() throws Exception {
public void testObjectArray() {
HasMap hasMap = (HasMap) this.beanFactory.getBean("objectArray");
assertThat(hasMap.getObjectArray().length).isEqualTo(2);
assertThat(hasMap.getObjectArray()[0].equals("one")).isTrue();
@@ -329,7 +324,7 @@ public class XmlBeanCollectionTests {
}
@Test
public void testIntegerArray() throws Exception {
public void testIntegerArray() {
HasMap hasMap = (HasMap) this.beanFactory.getBean("integerArray");
assertThat(hasMap.getIntegerArray().length).isEqualTo(3);
assertThat(hasMap.getIntegerArray()[0]).isEqualTo(0);
@@ -338,7 +333,7 @@ public class XmlBeanCollectionTests {
}
@Test
public void testClassArray() throws Exception {
public void testClassArray() {
HasMap hasMap = (HasMap) this.beanFactory.getBean("classArray");
assertThat(hasMap.getClassArray().length).isEqualTo(2);
assertThat(hasMap.getClassArray()[0].equals(String.class)).isTrue();
@@ -346,7 +341,7 @@ public class XmlBeanCollectionTests {
}
@Test
public void testClassList() throws Exception {
public void testClassList() {
HasMap hasMap = (HasMap) this.beanFactory.getBean("classList");
assertThat(hasMap.getClassList().size()).isEqualTo(2);
assertThat(hasMap.getClassList().get(0).equals(String.class)).isTrue();
@@ -354,7 +349,7 @@ public class XmlBeanCollectionTests {
}
@Test
public void testProps() throws Exception {
public void testProps() {
HasMap hasMap = (HasMap) this.beanFactory.getBean("props");
assertThat(hasMap.getProps()).hasSize(2);
assertThat(hasMap.getProps().getProperty("foo")).isEqualTo("bar");
@@ -367,60 +362,54 @@ public class XmlBeanCollectionTests {
}
@Test
public void testListFactory() throws Exception {
public void testListFactory() {
List list = (List) this.beanFactory.getBean("listFactory");
boolean condition = list instanceof LinkedList;
assertThat(condition).isTrue();
assertThat(list instanceof LinkedList).isTrue();
assertThat(list.size()).isEqualTo(2);
assertThat(list.get(0)).isEqualTo("bar");
assertThat(list.get(1)).isEqualTo("jenny");
}
@Test
public void testPrototypeListFactory() throws Exception {
public void testPrototypeListFactory() {
List list = (List) this.beanFactory.getBean("pListFactory");
boolean condition = list instanceof LinkedList;
assertThat(condition).isTrue();
assertThat(list instanceof LinkedList).isTrue();
assertThat(list.size()).isEqualTo(2);
assertThat(list.get(0)).isEqualTo("bar");
assertThat(list.get(1)).isEqualTo("jenny");
}
@Test
public void testSetFactory() throws Exception {
public void testSetFactory() {
Set set = (Set) this.beanFactory.getBean("setFactory");
boolean condition = set instanceof TreeSet;
assertThat(condition).isTrue();
assertThat(set instanceof TreeSet).isTrue();
assertThat(set.size()).isEqualTo(2);
assertThat(set.contains("bar")).isTrue();
assertThat(set.contains("jenny")).isTrue();
}
@Test
public void testPrototypeSetFactory() throws Exception {
public void testPrototypeSetFactory() {
Set set = (Set) this.beanFactory.getBean("pSetFactory");
boolean condition = set instanceof TreeSet;
assertThat(condition).isTrue();
assertThat(set instanceof TreeSet).isTrue();
assertThat(set.size()).isEqualTo(2);
assertThat(set.contains("bar")).isTrue();
assertThat(set.contains("jenny")).isTrue();
}
@Test
public void testMapFactory() throws Exception {
public void testMapFactory() {
Map map = (Map) this.beanFactory.getBean("mapFactory");
boolean condition = map instanceof TreeMap;
assertThat(condition).isTrue();
assertThat(map instanceof TreeMap).isTrue();
assertThat(map.size()).isEqualTo(2);
assertThat(map.get("foo")).isEqualTo("bar");
assertThat(map.get("jen")).isEqualTo("jenny");
}
@Test
public void testPrototypeMapFactory() throws Exception {
public void testPrototypeMapFactory() {
Map map = (Map) this.beanFactory.getBean("pMapFactory");
boolean condition = map instanceof TreeMap;
assertThat(condition).isTrue();
assertThat(map instanceof TreeMap).isTrue();
assertThat(map.size()).isEqualTo(2);
assertThat(map.get("foo")).isEqualTo("bar");
assertThat(map.get("jen")).isEqualTo("jenny");
@@ -429,8 +418,7 @@ public class XmlBeanCollectionTests {
@Test
public void testChoiceBetweenSetAndMap() {
MapAndSet sam = (MapAndSet) this.beanFactory.getBean("setAndMap");
boolean condition = sam.getObject() instanceof Map;
assertThat(condition).as("Didn't choose constructor with Map argument").isTrue();
assertThat(sam.getObject() instanceof Map).as("Didn't choose constructor with Map argument").isTrue();
Map map = (Map) sam.getObject();
assertThat(map).hasSize(3);
assertThat(map.get("key1")).isEqualTo("val1");
@@ -439,7 +427,7 @@ public class XmlBeanCollectionTests {
}
@Test
public void testEnumSetFactory() throws Exception {
public void testEnumSetFactory() {
Set set = (Set) this.beanFactory.getBean("enumSetFactory");
assertThat(set.size()).isEqualTo(2);
assertThat(set.contains("ONE")).isTrue();
@@ -34,33 +34,32 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
public class CustomCollectionEditorTests {
@Test
public void testCtorWithNullCollectionType() throws Exception {
public void testCtorWithNullCollectionType() {
assertThatIllegalArgumentException().isThrownBy(() ->
new CustomCollectionEditor(null));
}
@Test
@SuppressWarnings({ "unchecked", "rawtypes" })
public void testCtorWithNonCollectionType() throws Exception {
public void testCtorWithNonCollectionType() {
assertThatIllegalArgumentException().isThrownBy(() ->
new CustomCollectionEditor((Class) String.class));
}
@Test
public void testWithCollectionTypeThatDoesNotExposeAPublicNoArgCtor() throws Exception {
public void testWithCollectionTypeThatDoesNotExposeAPublicNoArgCtor() {
CustomCollectionEditor editor = new CustomCollectionEditor(CollectionTypeWithNoNoArgCtor.class);
assertThatIllegalArgumentException().isThrownBy(() ->
editor.setValue("1"));
}
@Test
public void testSunnyDaySetValue() throws Exception {
public void testSunnyDaySetValue() {
CustomCollectionEditor editor = new CustomCollectionEditor(ArrayList.class);
editor.setValue(new int[] {0, 1, 2});
Object value = editor.getValue();
assertThat(value).isNotNull();
boolean condition = value instanceof ArrayList;
assertThat(condition).isTrue();
assertThat(value instanceof ArrayList).isTrue();
List<?> list = (List<?>) value;
assertThat(list).as("There must be 3 elements in the converted collection").hasSize(3);
assertThat(list.get(0)).isEqualTo(0);
@@ -69,7 +68,7 @@ public class CustomCollectionEditorTests {
}
@Test
public void testWhenTargetTypeIsExactlyTheCollectionInterfaceUsesFallbackCollectionType() throws Exception {
public void testWhenTargetTypeIsExactlyTheCollectionInterfaceUsesFallbackCollectionType() {
CustomCollectionEditor editor = new CustomCollectionEditor(Collection.class);
editor.setValue("0, 1, 2");
Collection<?> value = (Collection<?>) editor.getValue();
@@ -79,13 +78,12 @@ public class CustomCollectionEditorTests {
}
@Test
public void testSunnyDaySetAsTextYieldsSingleValue() throws Exception {
public void testSunnyDaySetAsTextYieldsSingleValue() {
CustomCollectionEditor editor = new CustomCollectionEditor(ArrayList.class);
editor.setValue("0, 1, 2");
Object value = editor.getValue();
assertThat(value).isNotNull();
boolean condition = value instanceof ArrayList;
assertThat(condition).isTrue();
assertThat(value instanceof ArrayList).isTrue();
List<?> list = (List<?>) value;
assertThat(list).as("There must be 1 element in the converted collection").hasSize(1);
assertThat(list.get(0)).isEqualTo("0, 1, 2");
@@ -193,36 +193,31 @@ class CustomEditorTests {
bw.setPropertyValue("bool1", "false");
assertThat(Boolean.FALSE.equals(bw.getPropertyValue("bool1"))).as("Correct bool1 value").isTrue();
boolean condition4 = !tb.isBool1();
assertThat(condition4).as("Correct bool1 value").isTrue();
assertThat(!tb.isBool1()).as("Correct bool1 value").isTrue();
bw.setPropertyValue("bool1", " true ");
assertThat(tb.isBool1()).as("Correct bool1 value").isTrue();
bw.setPropertyValue("bool1", " false ");
boolean condition3 = !tb.isBool1();
assertThat(condition3).as("Correct bool1 value").isTrue();
assertThat(!tb.isBool1()).as("Correct bool1 value").isTrue();
bw.setPropertyValue("bool1", "on");
assertThat(tb.isBool1()).as("Correct bool1 value").isTrue();
bw.setPropertyValue("bool1", "off");
boolean condition2 = !tb.isBool1();
assertThat(condition2).as("Correct bool1 value").isTrue();
assertThat(!tb.isBool1()).as("Correct bool1 value").isTrue();
bw.setPropertyValue("bool1", "yes");
assertThat(tb.isBool1()).as("Correct bool1 value").isTrue();
bw.setPropertyValue("bool1", "no");
boolean condition1 = !tb.isBool1();
assertThat(condition1).as("Correct bool1 value").isTrue();
assertThat(!tb.isBool1()).as("Correct bool1 value").isTrue();
bw.setPropertyValue("bool1", "1");
assertThat(tb.isBool1()).as("Correct bool1 value").isTrue();
bw.setPropertyValue("bool1", "0");
boolean condition = !tb.isBool1();
assertThat(condition).as("Correct bool1 value").isTrue();
assertThat(!tb.isBool1()).as("Correct bool1 value").isTrue();
assertThatExceptionOfType(BeansException.class).isThrownBy(() ->
bw.setPropertyValue("bool1", "argh"));
@@ -239,29 +234,25 @@ class CustomEditorTests {
bw.setPropertyValue("bool2", "false");
assertThat(Boolean.FALSE.equals(bw.getPropertyValue("bool2"))).as("Correct bool2 value").isTrue();
boolean condition3 = !tb.getBool2();
assertThat(condition3).as("Correct bool2 value").isTrue();
assertThat(!tb.getBool2()).as("Correct bool2 value").isTrue();
bw.setPropertyValue("bool2", "on");
assertThat(tb.getBool2().booleanValue()).as("Correct bool2 value").isTrue();
bw.setPropertyValue("bool2", "off");
boolean condition2 = !tb.getBool2();
assertThat(condition2).as("Correct bool2 value").isTrue();
assertThat(!tb.getBool2()).as("Correct bool2 value").isTrue();
bw.setPropertyValue("bool2", "yes");
assertThat(tb.getBool2().booleanValue()).as("Correct bool2 value").isTrue();
bw.setPropertyValue("bool2", "no");
boolean condition1 = !tb.getBool2();
assertThat(condition1).as("Correct bool2 value").isTrue();
assertThat(!tb.getBool2()).as("Correct bool2 value").isTrue();
bw.setPropertyValue("bool2", "1");
assertThat(tb.getBool2().booleanValue()).as("Correct bool2 value").isTrue();
bw.setPropertyValue("bool2", "0");
boolean condition = !tb.getBool2();
assertThat(condition).as("Correct bool2 value").isTrue();
assertThat(!tb.getBool2()).as("Correct bool2 value").isTrue();
bw.setPropertyValue("bool2", "");
assertThat(tb.getBool2()).as("Correct bool2 value").isNull();
@@ -279,29 +270,25 @@ class CustomEditorTests {
bw.setPropertyValue("bool2", "false");
assertThat(Boolean.FALSE.equals(bw.getPropertyValue("bool2"))).as("Correct bool2 value").isTrue();
boolean condition3 = !tb.getBool2();
assertThat(condition3).as("Correct bool2 value").isTrue();
assertThat(!tb.getBool2()).as("Correct bool2 value").isTrue();
bw.setPropertyValue("bool2", "on");
assertThat(tb.getBool2().booleanValue()).as("Correct bool2 value").isTrue();
bw.setPropertyValue("bool2", "off");
boolean condition2 = !tb.getBool2();
assertThat(condition2).as("Correct bool2 value").isTrue();
assertThat(!tb.getBool2()).as("Correct bool2 value").isTrue();
bw.setPropertyValue("bool2", "yes");
assertThat(tb.getBool2().booleanValue()).as("Correct bool2 value").isTrue();
bw.setPropertyValue("bool2", "no");
boolean condition1 = !tb.getBool2();
assertThat(condition1).as("Correct bool2 value").isTrue();
assertThat(!tb.getBool2()).as("Correct bool2 value").isTrue();
bw.setPropertyValue("bool2", "1");
assertThat(tb.getBool2().booleanValue()).as("Correct bool2 value").isTrue();
bw.setPropertyValue("bool2", "0");
boolean condition = !tb.getBool2();
assertThat(condition).as("Correct bool2 value").isTrue();
assertThat(!tb.getBool2()).as("Correct bool2 value").isTrue();
bw.setPropertyValue("bool2", "");
assertThat(bw.getPropertyValue("bool2")).as("Correct bool2 value").isNull();
@@ -309,7 +296,7 @@ class CustomEditorTests {
}
@Test
void testCustomBooleanEditorWithSpecialTrueAndFalseStrings() throws Exception {
void testCustomBooleanEditorWithSpecialTrueAndFalseStrings() {
String trueString = "pechorin";
String falseString = "nash";
@@ -458,7 +445,7 @@ class CustomEditorTests {
}
@Test
void testCustomNumberEditorWithFrenchBigDecimal() throws Exception {
void testCustomNumberEditorWithFrenchBigDecimal() {
NumberFormat nf = NumberFormat.getNumberInstance(Locale.FRENCH);
NumberTestBean tb = new NumberTestBean();
BeanWrapper bw = new BeanWrapperImpl(tb);
@@ -542,14 +529,14 @@ class CustomEditorTests {
}
@Test
void testCharacterEditorSetAsTextWithStringLongerThanOneCharacter() throws Exception {
void testCharacterEditorSetAsTextWithStringLongerThanOneCharacter() {
PropertyEditor charEditor = new CharacterEditor(false);
assertThatIllegalArgumentException().isThrownBy(() ->
charEditor.setAsText("ColdWaterCanyon"));
}
@Test
void testCharacterEditorGetAsTextReturnsEmptyStringIfValueIsNull() throws Exception {
void testCharacterEditorGetAsTextReturnsEmptyStringIfValueIsNull() {
PropertyEditor charEditor = new CharacterEditor(false);
assertThat(charEditor.getAsText()).isEmpty();
charEditor = new CharacterEditor(true);
@@ -562,7 +549,7 @@ class CustomEditorTests {
}
@Test
void testCharacterEditorSetAsTextWithNullNotAllowingEmptyAsNull() throws Exception {
void testCharacterEditorSetAsTextWithNullNotAllowingEmptyAsNull() {
PropertyEditor charEditor = new CharacterEditor(false);
assertThatIllegalArgumentException().isThrownBy(() ->
charEditor.setAsText(null));
@@ -584,7 +571,7 @@ class CustomEditorTests {
}
@Test
void testClassEditorWithNonExistentClass() throws Exception {
void testClassEditorWithNonExistentClass() {
PropertyEditor classEditor = new ClassEditor();
assertThatIllegalArgumentException().isThrownBy(() ->
classEditor.setAsText("hairdresser.on.Fire"));
@@ -602,7 +589,7 @@ class CustomEditorTests {
* SPR_2165 - ClassEditor is inconsistent with multidimensional arrays
*/
@Test
void testGetAsTextWithTwoDimensionalArray() throws Exception {
void testGetAsTextWithTwoDimensionalArray() {
String[][] chessboard = new String[8][8];
ClassEditor editor = new ClassEditor();
editor.setValue(chessboard.getClass());
@@ -613,7 +600,7 @@ class CustomEditorTests {
* SPR_2165 - ClassEditor is inconsistent with multidimensional arrays
*/
@Test
void testGetAsTextWithRidiculousMultiDimensionalArray() throws Exception {
void testGetAsTextWithRidiculousMultiDimensionalArray() {
String[][][][][] ridiculousChessboard = new String[8][4][0][1][3];
ClassEditor editor = new ClassEditor();
editor.setValue(ridiculousChessboard.getClass());
@@ -1413,7 +1400,7 @@ class CustomEditorTests {
}
@Test
void testClassArrayEditorSunnyDay() throws Exception {
void testClassArrayEditorSunnyDay() {
ClassArrayEditor classArrayEditor = new ClassArrayEditor();
classArrayEditor.setAsText("java.lang.String,java.util.HashMap");
Class<?>[] classes = (Class<?>[]) classArrayEditor.getValue();
@@ -1426,7 +1413,7 @@ class CustomEditorTests {
}
@Test
void testClassArrayEditorSunnyDayWithArrayTypes() throws Exception {
void testClassArrayEditorSunnyDayWithArrayTypes() {
ClassArrayEditor classArrayEditor = new ClassArrayEditor();
classArrayEditor.setAsText("java.lang.String[],java.util.Map[],int[],float[][][]");
Class<?>[] classes = (Class<?>[]) classArrayEditor.getValue();
@@ -1441,7 +1428,7 @@ class CustomEditorTests {
}
@Test
void testClassArrayEditorSetAsTextWithNull() throws Exception {
void testClassArrayEditorSetAsTextWithNull() {
ClassArrayEditor editor = new ClassArrayEditor();
editor.setAsText(null);
assertThat(editor.getValue()).isNull();
@@ -1449,7 +1436,7 @@ class CustomEditorTests {
}
@Test
void testClassArrayEditorSetAsTextWithEmptyString() throws Exception {
void testClassArrayEditorSetAsTextWithEmptyString() {
ClassArrayEditor editor = new ClassArrayEditor();
editor.setAsText("");
assertThat(editor.getValue()).isNull();
@@ -1457,7 +1444,7 @@ class CustomEditorTests {
}
@Test
void testClassArrayEditorSetAsTextWithWhitespaceString() throws Exception {
void testClassArrayEditorSetAsTextWithWhitespaceString() {
ClassArrayEditor editor = new ClassArrayEditor();
editor.setAsText("\n");
assertThat(editor.getValue()).isNull();
@@ -1465,7 +1452,7 @@ class CustomEditorTests {
}
@Test
void testCharsetEditor() throws Exception {
void testCharsetEditor() {
CharsetEditor editor = new CharsetEditor();
String name = "UTF-8";
editor.setAsText(name);
@@ -34,57 +34,51 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
public class FileEditorTests {
@Test
public void testClasspathFileName() throws Exception {
public void testClasspathFileName() {
PropertyEditor fileEditor = new FileEditor();
fileEditor.setAsText("classpath:" + ClassUtils.classPackageAsResourcePath(getClass()) + "/" +
ClassUtils.getShortName(getClass()) + ".class");
Object value = fileEditor.getValue();
boolean condition = value instanceof File;
assertThat(condition).isTrue();
assertThat(value instanceof File).isTrue();
File file = (File) value;
assertThat(file).exists();
}
@Test
public void testWithNonExistentResource() throws Exception {
public void testWithNonExistentResource() {
PropertyEditor propertyEditor = new FileEditor();
assertThatIllegalArgumentException().isThrownBy(() ->
propertyEditor.setAsText("classpath:no_way_this_file_is_found.doc"));
}
@Test
public void testWithNonExistentFile() throws Exception {
public void testWithNonExistentFile() {
PropertyEditor fileEditor = new FileEditor();
fileEditor.setAsText("file:no_way_this_file_is_found.doc");
Object value = fileEditor.getValue();
boolean condition1 = value instanceof File;
assertThat(condition1).isTrue();
assertThat(value instanceof File).isTrue();
File file = (File) value;
boolean condition = !file.exists();
assertThat(condition).isTrue();
assertThat(file).doesNotExist();
}
@Test
public void testAbsoluteFileName() throws Exception {
public void testAbsoluteFileName() {
PropertyEditor fileEditor = new FileEditor();
fileEditor.setAsText("/no_way_this_file_is_found.doc");
Object value = fileEditor.getValue();
boolean condition1 = value instanceof File;
assertThat(condition1).isTrue();
assertThat(value instanceof File).isTrue();
File file = (File) value;
boolean condition = !file.exists();
assertThat(condition).isTrue();
assertThat(file).doesNotExist();
}
@Test
public void testUnqualifiedFileNameFound() throws Exception {
public void testUnqualifiedFileNameFound() {
PropertyEditor fileEditor = new FileEditor();
String fileName = ClassUtils.classPackageAsResourcePath(getClass()) + "/" +
ClassUtils.getShortName(getClass()) + ".class";
fileEditor.setAsText(fileName);
Object value = fileEditor.getValue();
boolean condition = value instanceof File;
assertThat(condition).isTrue();
assertThat(value instanceof File).isTrue();
File file = (File) value;
assertThat(file).exists();
String absolutePath = file.getAbsolutePath().replace('\\', '/');
@@ -92,14 +86,13 @@ public class FileEditorTests {
}
@Test
public void testUnqualifiedFileNameNotFound() throws Exception {
public void testUnqualifiedFileNameNotFound() {
PropertyEditor fileEditor = new FileEditor();
String fileName = ClassUtils.classPackageAsResourcePath(getClass()) + "/" +
ClassUtils.getShortName(getClass()) + ".clazz";
fileEditor.setAsText(fileName);
Object value = fileEditor.getValue();
boolean condition = value instanceof File;
assertThat(condition).isTrue();
assertThat(value instanceof File).isTrue();
File file = (File) value;
assertThat(file).doesNotExist();
String absolutePath = file.getAbsolutePath().replace('\\', '/');
@@ -16,6 +16,7 @@
package org.springframework.beans.propertyeditors;
import java.io.IOException;
import java.io.InputStream;
import org.junit.jupiter.api.Test;
@@ -34,13 +35,13 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
public class InputStreamEditorTests {
@Test
public void testCtorWithNullResourceEditor() throws Exception {
public void testCtorWithNullResourceEditor() {
assertThatIllegalArgumentException().isThrownBy(() ->
new InputStreamEditor(null));
}
@Test
public void testSunnyDay() throws Exception {
public void testSunnyDay() throws IOException {
InputStream stream = null;
try {
String resource = "classpath:" + ClassUtils.classPackageAsResourcePath(getClass()) +
@@ -49,8 +50,7 @@ public class InputStreamEditorTests {
editor.setAsText(resource);
Object value = editor.getValue();
assertThat(value).isNotNull();
boolean condition = value instanceof InputStream;
assertThat(condition).isTrue();
assertThat(value instanceof InputStream).isTrue();
stream = (InputStream) value;
assertThat(stream.available()).isGreaterThan(0);
}
@@ -62,14 +62,14 @@ public class InputStreamEditorTests {
}
@Test
public void testWhenResourceDoesNotExist() throws Exception {
public void testWhenResourceDoesNotExist() {
InputStreamEditor editor = new InputStreamEditor();
assertThatIllegalArgumentException().isThrownBy(() ->
editor.setAsText("classpath:bingo!"));
}
@Test
public void testGetAsTextReturnsNullByDefault() throws Exception {
public void testGetAsTextReturnsNullByDefault() {
assertThat(new InputStreamEditor().getAsText()).isNull();
String resource = "classpath:" + ClassUtils.classPackageAsResourcePath(getClass()) +
"/" + ClassUtils.getShortName(getClass()) + ".class";
@@ -151,7 +151,7 @@ public class PropertiesEditorTests {
}
@Test
public void usingMapAsValueSource() throws Exception {
public void usingMapAsValueSource() {
Map<String, String> map = new HashMap<>();
map.put("one", "1");
map.put("two", "2");
@@ -160,8 +160,7 @@ public class PropertiesEditorTests {
pe.setValue(map);
Object value = pe.getValue();
assertThat(value).isNotNull();
boolean condition = value instanceof Properties;
assertThat(condition).isTrue();
assertThat(value instanceof Properties).isTrue();
Properties props = (Properties) value;
assertThat(props).hasSize(3);
assertThat(props.getProperty("one")).isEqualTo("1");
@@ -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.
@@ -16,6 +16,7 @@
package org.springframework.beans.propertyeditors;
import java.io.IOException;
import java.io.Reader;
import org.junit.jupiter.api.Test;
@@ -34,13 +35,13 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
public class ReaderEditorTests {
@Test
public void testCtorWithNullResourceEditor() throws Exception {
public void testCtorWithNullResourceEditor() {
assertThatIllegalArgumentException().isThrownBy(() ->
new ReaderEditor(null));
}
@Test
public void testSunnyDay() throws Exception {
public void testSunnyDay() throws IOException {
Reader reader = null;
try {
String resource = "classpath:" + ClassUtils.classPackageAsResourcePath(getClass()) +
@@ -49,8 +50,7 @@ public class ReaderEditorTests {
editor.setAsText(resource);
Object value = editor.getValue();
assertThat(value).isNotNull();
boolean condition = value instanceof Reader;
assertThat(condition).isTrue();
assertThat(value instanceof Reader).isTrue();
reader = (Reader) value;
assertThat(reader.ready()).isTrue();
}
@@ -62,7 +62,7 @@ public class ReaderEditorTests {
}
@Test
public void testWhenResourceDoesNotExist() throws Exception {
public void testWhenResourceDoesNotExist() {
String resource = "classpath:bingo!";
ReaderEditor editor = new ReaderEditor();
assertThatIllegalArgumentException().isThrownBy(() ->
@@ -70,7 +70,7 @@ public class ReaderEditorTests {
}
@Test
public void testGetAsTextReturnsNullByDefault() throws Exception {
public void testGetAsTextReturnsNullByDefault() {
assertThat(new ReaderEditor().getAsText()).isNull();
String resource = "classpath:" + ClassUtils.classPackageAsResourcePath(getClass()) +
"/" + ClassUtils.getShortName(getClass()) + ".class";
@@ -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.
@@ -37,93 +37,88 @@ public class ResourceBundleEditorTests {
@Test
public void testSetAsTextWithJustBaseName() throws Exception {
public void testSetAsTextWithJustBaseName() {
ResourceBundleEditor editor = new ResourceBundleEditor();
editor.setAsText(BASE_NAME);
Object value = editor.getValue();
assertThat(value).as("Returned ResourceBundle was null (must not be for valid setAsText(..) call).").isNotNull();
boolean condition = value instanceof ResourceBundle;
assertThat(condition).as("Returned object was not a ResourceBundle (must be for valid setAsText(..) call).").isTrue();
assertThat(value instanceof ResourceBundle).as("Returned object was not a ResourceBundle (must be for valid setAsText(..) call).").isTrue();
ResourceBundle bundle = (ResourceBundle) value;
String string = bundle.getString(MESSAGE_KEY);
assertThat(string).isEqualTo(MESSAGE_KEY);
}
@Test
public void testSetAsTextWithBaseNameThatEndsInDefaultSeparator() throws Exception {
public void testSetAsTextWithBaseNameThatEndsInDefaultSeparator() {
ResourceBundleEditor editor = new ResourceBundleEditor();
editor.setAsText(BASE_NAME + "_");
Object value = editor.getValue();
assertThat(value).as("Returned ResourceBundle was null (must not be for valid setAsText(..) call).").isNotNull();
boolean condition = value instanceof ResourceBundle;
assertThat(condition).as("Returned object was not a ResourceBundle (must be for valid setAsText(..) call).").isTrue();
assertThat(value instanceof ResourceBundle).as("Returned object was not a ResourceBundle (must be for valid setAsText(..) call).").isTrue();
ResourceBundle bundle = (ResourceBundle) value;
String string = bundle.getString(MESSAGE_KEY);
assertThat(string).isEqualTo(MESSAGE_KEY);
}
@Test
public void testSetAsTextWithBaseNameAndLanguageCode() throws Exception {
public void testSetAsTextWithBaseNameAndLanguageCode() {
ResourceBundleEditor editor = new ResourceBundleEditor();
editor.setAsText(BASE_NAME + "Lang" + "_en");
Object value = editor.getValue();
assertThat(value).as("Returned ResourceBundle was null (must not be for valid setAsText(..) call).").isNotNull();
boolean condition = value instanceof ResourceBundle;
assertThat(condition).as("Returned object was not a ResourceBundle (must be for valid setAsText(..) call).").isTrue();
assertThat(value instanceof ResourceBundle).as("Returned object was not a ResourceBundle (must be for valid setAsText(..) call).").isTrue();
ResourceBundle bundle = (ResourceBundle) value;
String string = bundle.getString(MESSAGE_KEY);
assertThat(string).isEqualTo("yob");
}
@Test
public void testSetAsTextWithBaseNameLanguageAndCountryCode() throws Exception {
public void testSetAsTextWithBaseNameLanguageAndCountryCode() {
ResourceBundleEditor editor = new ResourceBundleEditor();
editor.setAsText(BASE_NAME + "LangCountry" + "_en_GB");
Object value = editor.getValue();
assertThat(value).as("Returned ResourceBundle was null (must not be for valid setAsText(..) call).").isNotNull();
boolean condition = value instanceof ResourceBundle;
assertThat(condition).as("Returned object was not a ResourceBundle (must be for valid setAsText(..) call).").isTrue();
assertThat(value instanceof ResourceBundle).as("Returned object was not a ResourceBundle (must be for valid setAsText(..) call).").isTrue();
ResourceBundle bundle = (ResourceBundle) value;
String string = bundle.getString(MESSAGE_KEY);
assertThat(string).isEqualTo("chav");
}
@Test
public void testSetAsTextWithTheKitchenSink() throws Exception {
public void testSetAsTextWithTheKitchenSink() {
ResourceBundleEditor editor = new ResourceBundleEditor();
editor.setAsText(BASE_NAME + "LangCountryDialect" + "_en_GB_GLASGOW");
Object value = editor.getValue();
assertThat(value).as("Returned ResourceBundle was null (must not be for valid setAsText(..) call).").isNotNull();
boolean condition = value instanceof ResourceBundle;
assertThat(condition).as("Returned object was not a ResourceBundle (must be for valid setAsText(..) call).").isTrue();
assertThat(value instanceof ResourceBundle).as("Returned object was not a ResourceBundle (must be for valid setAsText(..) call).").isTrue();
ResourceBundle bundle = (ResourceBundle) value;
String string = bundle.getString(MESSAGE_KEY);
assertThat(string).isEqualTo("ned");
}
@Test
public void testSetAsTextWithNull() throws Exception {
public void testSetAsTextWithNull() {
ResourceBundleEditor editor = new ResourceBundleEditor();
assertThatIllegalArgumentException().isThrownBy(() ->
editor.setAsText(null));
}
@Test
public void testSetAsTextWithEmptyString() throws Exception {
public void testSetAsTextWithEmptyString() {
ResourceBundleEditor editor = new ResourceBundleEditor();
assertThatIllegalArgumentException().isThrownBy(() ->
editor.setAsText(""));
}
@Test
public void testSetAsTextWithWhiteSpaceString() throws Exception {
public void testSetAsTextWithWhiteSpaceString() {
ResourceBundleEditor editor = new ResourceBundleEditor();
assertThatIllegalArgumentException().isThrownBy(() ->
editor.setAsText(" "));
}
@Test
public void testSetAsTextWithJustSeparatorString() throws Exception {
public void testSetAsTextWithJustSeparatorString() {
ResourceBundleEditor editor = new ResourceBundleEditor();
assertThatIllegalArgumentException().isThrownBy(() ->
editor.setAsText("_"));
@@ -32,78 +32,72 @@ import static org.assertj.core.api.Assertions.assertThat;
public class URIEditorTests {
@Test
public void standardURI() throws Exception {
public void standardURI() {
doTestURI("mailto:juergen.hoeller@interface21.com");
}
@Test
public void withNonExistentResource() throws Exception {
public void withNonExistentResource() {
doTestURI("gonna:/freak/in/the/morning/freak/in/the.evening");
}
@Test
public void standardURL() throws Exception {
public void standardURL() {
doTestURI("https://www.springframework.org");
}
@Test
public void standardURLWithFragment() throws Exception {
public void standardURLWithFragment() {
doTestURI("https://www.springframework.org#1");
}
@Test
public void standardURLWithWhitespace() throws Exception {
public void standardURLWithWhitespace() {
PropertyEditor uriEditor = new URIEditor();
uriEditor.setAsText(" https://www.springframework.org ");
Object value = uriEditor.getValue();
boolean condition = value instanceof URI;
assertThat(condition).isTrue();
assertThat(value instanceof URI).isTrue();
URI uri = (URI) value;
assertThat(uri.toString()).isEqualTo("https://www.springframework.org");
}
@Test
public void classpathURL() throws Exception {
public void classpathURL() {
PropertyEditor uriEditor = new URIEditor(getClass().getClassLoader());
uriEditor.setAsText("classpath:" + ClassUtils.classPackageAsResourcePath(getClass()) +
"/" + ClassUtils.getShortName(getClass()) + ".class");
Object value = uriEditor.getValue();
boolean condition1 = value instanceof URI;
assertThat(condition1).isTrue();
assertThat(value instanceof URI).isTrue();
URI uri = (URI) value;
assertThat(uriEditor.getAsText()).isEqualTo(uri.toString());
boolean condition = !uri.getScheme().startsWith("classpath");
assertThat(condition).isTrue();
assertThat(uri.getScheme()).doesNotStartWith("classpath");
}
@Test
public void classpathURLWithWhitespace() throws Exception {
public void classpathURLWithWhitespace() {
PropertyEditor uriEditor = new URIEditor(getClass().getClassLoader());
uriEditor.setAsText(" classpath:" + ClassUtils.classPackageAsResourcePath(getClass()) +
"/" + ClassUtils.getShortName(getClass()) + ".class ");
Object value = uriEditor.getValue();
boolean condition1 = value instanceof URI;
assertThat(condition1).isTrue();
assertThat(value instanceof URI).isTrue();
URI uri = (URI) value;
assertThat(uriEditor.getAsText()).isEqualTo(uri.toString());
boolean condition = !uri.getScheme().startsWith("classpath");
assertThat(condition).isTrue();
assertThat(uri.getScheme()).doesNotStartWith("classpath");
}
@Test
public void classpathURLAsIs() throws Exception {
public void classpathURLAsIs() {
PropertyEditor uriEditor = new URIEditor();
uriEditor.setAsText("classpath:test.txt");
Object value = uriEditor.getValue();
boolean condition = value instanceof URI;
assertThat(condition).isTrue();
assertThat(value instanceof URI).isTrue();
URI uri = (URI) value;
assertThat(uriEditor.getAsText()).isEqualTo(uri.toString());
assertThat(uri.getScheme()).startsWith("classpath");
}
@Test
public void setAsTextWithNull() throws Exception {
public void setAsTextWithNull() {
PropertyEditor uriEditor = new URIEditor();
uriEditor.setAsText(null);
assertThat(uriEditor.getValue()).isNull();
@@ -111,30 +105,28 @@ public class URIEditorTests {
}
@Test
public void getAsTextReturnsEmptyStringIfValueNotSet() throws Exception {
public void getAsTextReturnsEmptyStringIfValueNotSet() {
PropertyEditor uriEditor = new URIEditor();
assertThat(uriEditor.getAsText()).isEmpty();
}
@Test
public void encodeURI() throws Exception {
public void encodeURI() {
PropertyEditor uriEditor = new URIEditor();
uriEditor.setAsText("https://example.com/spaces and \u20AC");
Object value = uriEditor.getValue();
boolean condition = value instanceof URI;
assertThat(condition).isTrue();
assertThat(value instanceof URI).isTrue();
URI uri = (URI) value;
assertThat(uriEditor.getAsText()).isEqualTo(uri.toString());
assertThat(uri.toASCIIString()).isEqualTo("https://example.com/spaces%20and%20%E2%82%AC");
}
@Test
public void encodeAlreadyEncodedURI() throws Exception {
public void encodeAlreadyEncodedURI() {
PropertyEditor uriEditor = new URIEditor(false);
uriEditor.setAsText("https://example.com/spaces%20and%20%E2%82%AC");
Object value = uriEditor.getValue();
boolean condition = value instanceof URI;
assertThat(condition).isTrue();
assertThat(value instanceof URI).isTrue();
URI uri = (URI) value;
assertThat(uriEditor.getAsText()).isEqualTo(uri.toString());
assertThat(uri.toASCIIString()).isEqualTo("https://example.com/spaces%20and%20%E2%82%AC");
@@ -145,8 +137,7 @@ public class URIEditorTests {
PropertyEditor uriEditor = new URIEditor();
uriEditor.setAsText(uriSpec);
Object value = uriEditor.getValue();
boolean condition = value instanceof URI;
assertThat(condition).isTrue();
assertThat(value instanceof URI).isTrue();
URI uri = (URI) value;
assertThat(uri.toString()).isEqualTo(uriSpec);
}
@@ -33,56 +33,52 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
public class URLEditorTests {
@Test
public void testCtorWithNullResourceEditor() throws Exception {
public void testCtorWithNullResourceEditor() {
assertThatIllegalArgumentException().isThrownBy(() ->
new URLEditor(null));
}
@Test
public void testStandardURI() throws Exception {
public void testStandardURI() {
PropertyEditor urlEditor = new URLEditor();
urlEditor.setAsText("mailto:juergen.hoeller@interface21.com");
Object value = urlEditor.getValue();
boolean condition = value instanceof URL;
assertThat(condition).isTrue();
assertThat(value instanceof URL).isTrue();
URL url = (URL) value;
assertThat(urlEditor.getAsText()).isEqualTo(url.toExternalForm());
}
@Test
public void testStandardURL() throws Exception {
public void testStandardURL() {
PropertyEditor urlEditor = new URLEditor();
urlEditor.setAsText("https://www.springframework.org");
Object value = urlEditor.getValue();
boolean condition = value instanceof URL;
assertThat(condition).isTrue();
assertThat(value instanceof URL).isTrue();
URL url = (URL) value;
assertThat(urlEditor.getAsText()).isEqualTo(url.toExternalForm());
}
@Test
public void testClasspathURL() throws Exception {
public void testClasspathURL() {
PropertyEditor urlEditor = new URLEditor();
urlEditor.setAsText("classpath:" + ClassUtils.classPackageAsResourcePath(getClass()) +
"/" + ClassUtils.getShortName(getClass()) + ".class");
Object value = urlEditor.getValue();
boolean condition1 = value instanceof URL;
assertThat(condition1).isTrue();
assertThat(value instanceof URL).isTrue();
URL url = (URL) value;
assertThat(urlEditor.getAsText()).isEqualTo(url.toExternalForm());
boolean condition = !url.getProtocol().startsWith("classpath");
assertThat(condition).isTrue();
assertThat(url.getProtocol()).doesNotStartWith("classpath");
}
@Test
public void testWithNonExistentResource() throws Exception {
public void testWithNonExistentResource() {
PropertyEditor urlEditor = new URLEditor();
assertThatIllegalArgumentException().isThrownBy(() ->
urlEditor.setAsText("gonna:/freak/in/the/morning/freak/in/the.evening"));
}
@Test
public void testSetAsTextWithNull() throws Exception {
public void testSetAsTextWithNull() {
PropertyEditor urlEditor = new URLEditor();
urlEditor.setAsText(null);
assertThat(urlEditor.getValue()).isNull();
@@ -90,7 +86,7 @@ public class URLEditorTests {
}
@Test
public void testGetAsTextReturnsEmptyStringIfValueNotSet() throws Exception {
public void testGetAsTextReturnsEmptyStringIfValueNotSet() {
PropertyEditor urlEditor = new URLEditor();
assertThat(urlEditor.getAsText()).isEmpty();
}
@@ -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.
@@ -20,12 +20,12 @@ import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
/**
* Kotlin tests for {@link BeanUtils}.
* Kotlin tests for [BeanUtils].
*
* @author Sebastien Deleuze
*/
@Suppress("unused", "UNUSED_PARAMETER")
class KotlinBeanUtilsTests {
class BeanUtilsKotlinTests {
@Test
fun `Instantiate immutable class`() {
@@ -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.
@@ -31,7 +31,7 @@ import org.springframework.beans.testfixture.beans.TestBean
* @author Juergen Hoeller
* @author Sebastien Deleuze
*/
class KotlinAutowiredTests {
class AutowiredKotlinTests {
@Test
fun `Autowiring with target`() {
@@ -0,0 +1,39 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.testfixture.beans.factory.aot;
/**
* A hierarchy where the exposed type of a bean is a partial signature.
*
* @author Stephane Nicoll
*/
public class TestHierarchy {
public interface One {
}
public interface Two {
}
public static class Implementation implements One, Two {
}
public static One oneBean() {
return new Implementation();
}
}
@@ -189,13 +189,11 @@ public class CaffeineCacheManager implements CacheManager {
@Override
@Nullable
public Cache getCache(String name) {
if (this.dynamic) {
Cache cache = this.cacheMap.get(name);
return (cache != null) ? cache : this.cacheMap.computeIfAbsent(name, this::createCaffeineCache);
}
else {
return this.cacheMap.get(name);
Cache cache = this.cacheMap.get(name);
if (cache == null && this.dynamic) {
cache = this.cacheMap.computeIfAbsent(name, this::createCaffeineCache);
}
return cache;
}
+2 -1
View File
@@ -18,6 +18,7 @@ dependencies {
optional("jakarta.inject:jakarta.inject-api")
optional("jakarta.interceptor:jakarta.interceptor-api")
optional("jakarta.validation:jakarta.validation-api")
optional("javax.annotation:javax.annotation-api")
optional("javax.money:money-api")
optional("org.aspectj:aspectjweaver")
optional("org.apache.groovy:groovy")
@@ -31,11 +32,11 @@ dependencies {
testImplementation(testFixtures(project(":spring-beans")))
testImplementation(testFixtures(project(":spring-core")))
testImplementation("io.projectreactor:reactor-core")
testImplementation("jakarta.inject:jakarta.inject-tck")
testImplementation("org.apache.groovy:groovy-jsr223")
testImplementation("org.apache.groovy:groovy-xml")
testImplementation("org.apache.commons:commons-pool2")
testImplementation("org.awaitility:awaitility")
testImplementation("jakarta.inject:jakarta.inject-tck")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
testRuntimeOnly("jakarta.xml.bind:jakarta.xml.bind-api")
testRuntimeOnly("org.glassfish:jakarta.el")
@@ -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,7 @@ public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderA
public Cache getCache(String name) {
Cache cache = this.cacheMap.get(name);
if (cache == null && this.dynamic) {
synchronized (this.cacheMap) {
cache = this.cacheMap.get(name);
if (cache == null) {
cache = createConcurrentMapCache(name);
this.cacheMap.put(name, cache);
}
}
cache = this.cacheMap.computeIfAbsent(name, this::createConcurrentMapCache);
}
return cache;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-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.
@@ -23,7 +23,6 @@ import java.util.Set;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
@@ -89,12 +88,6 @@ public abstract class AnnotationConfigUtils {
public static final String COMMON_ANNOTATION_PROCESSOR_BEAN_NAME =
"org.springframework.context.annotation.internalCommonAnnotationProcessor";
/**
* The bean name of the internally managed JSR-250 annotation processor.
*/
private static final String JSR250_ANNOTATION_PROCESSOR_BEAN_NAME =
"org.springframework.context.annotation.internalJsr250AnnotationProcessor";
/**
* The bean name of the internally managed JPA annotation processor.
*/
@@ -174,27 +167,13 @@ public abstract class AnnotationConfigUtils {
}
// Check for Jakarta Annotations support, and if present add the CommonAnnotationBeanPostProcessor.
if (jakartaAnnotationsPresent && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
if ((jakartaAnnotationsPresent || jsr250Present) &&
!registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
}
// Check for JSR-250 support, and if present add an InitDestroyAnnotationBeanPostProcessor
// for the javax variant of PostConstruct/PreDestroy.
if (jsr250Present && !registry.containsBeanDefinition(JSR250_ANNOTATION_PROCESSOR_BEAN_NAME)) {
try {
RootBeanDefinition def = new RootBeanDefinition(InitDestroyAnnotationBeanPostProcessor.class);
def.getPropertyValues().add("initAnnotationType", classLoader.loadClass("javax.annotation.PostConstruct"));
def.getPropertyValues().add("destroyAnnotationType", classLoader.loadClass("javax.annotation.PreDestroy"));
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, JSR250_ANNOTATION_PROCESSOR_BEAN_NAME));
}
catch (ClassNotFoundException ex) {
// Failed to load javax variants of the annotation types -> ignore.
}
}
// Check for JPA support, and if present add the PersistenceAnnotationBeanPostProcessor.
if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -33,11 +33,6 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.annotation.Resource;
import jakarta.ejb.EJB;
import org.springframework.aop.TargetSource;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.BeanUtils;
@@ -85,10 +80,15 @@ import org.springframework.util.StringValueResolver;
* and default names as well. The target beans can be simple POJOs, with no special
* requirements other than the type having to match.
*
* <p>This post-processor also supports the EJB 3 {@link jakarta.ejb.EJB} annotation,
* <p>Additionally, the original {@code javax.annotation} variants of the annotations
* dating back to the JSR-250 specification (Java EE 5-8, also included in JDK 6-8)
* are still supported as well. Note that this is primarily for a smooth upgrade path,
* not for adoption in new applications.
*
* <p>This post-processor also supports the EJB {@link jakarta.ejb.EJB} annotation,
* analogous to {@link jakarta.annotation.Resource}, with the capability to
* specify both a local bean name and a global JNDI name for fallback retrieval.
* The target beans can be plain POJOs as well as EJB 3 Session Beans in this case.
* The target beans can be plain POJOs as well as EJB Session Beans in this case.
*
* <p>For default usage, resolving resource names as Spring bean names,
* simply define the following in your application context:
@@ -113,8 +113,8 @@ import org.springframework.util.StringValueResolver;
* by the "context:annotation-config" and "context:component-scan" XML tags.
* Remove or turn off the default annotation configuration there if you intend
* to specify a custom CommonAnnotationBeanPostProcessor bean definition!
* <p><b>NOTE:</b> Annotation injection will be performed <i>before</i> XML injection; thus
* the latter configuration will override the former for properties wired through
* <p><b>NOTE:</b> Annotation injection will be performed <i>before</i> XML injection;
* thus the latter configuration will override the former for properties wired through
* both approaches.
*
* @author Juergen Hoeller
@@ -136,14 +136,28 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
private static final Set<Class<? extends Annotation>> resourceAnnotationTypes = new LinkedHashSet<>(4);
@Nullable
private static final Class<? extends Annotation> ejbClass;
private static final Class<? extends Annotation> jakartaResourceType;
@Nullable
private static final Class<? extends Annotation> javaxResourceType;
@Nullable
private static final Class<? extends Annotation> ejbAnnotationType;
static {
resourceAnnotationTypes.add(Resource.class);
jakartaResourceType = loadAnnotationType("jakarta.annotation.Resource");
if (jakartaResourceType != null) {
resourceAnnotationTypes.add(jakartaResourceType);
}
ejbClass = loadAnnotationType("jakarta.ejb.EJB");
if (ejbClass != null) {
resourceAnnotationTypes.add(ejbClass);
javaxResourceType = loadAnnotationType("javax.annotation.Resource");
if (javaxResourceType != null) {
resourceAnnotationTypes.add(javaxResourceType);
}
ejbAnnotationType = loadAnnotationType("jakarta.ejb.EJB");
if (ejbAnnotationType != null) {
resourceAnnotationTypes.add(ejbAnnotationType);
}
}
@@ -177,8 +191,14 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
*/
public CommonAnnotationBeanPostProcessor() {
setOrder(Ordered.LOWEST_PRECEDENCE - 3);
setInitAnnotationType(PostConstruct.class);
setDestroyAnnotationType(PreDestroy.class);
// Jakarta EE 9 set of annotations in jakarta.annotation package
addInitAnnotationType(loadAnnotationType("jakarta.annotation.PostConstruct"));
addDestroyAnnotationType(loadAnnotationType("jakarta.annotation.PreDestroy"));
// Tolerate legacy JSR-250 annotations in javax.annotation package
addInitAnnotationType(loadAnnotationType("javax.annotation.PostConstruct"));
addDestroyAnnotationType(loadAnnotationType("javax.annotation.PreDestroy"));
// java.naming module present on JDK 9+?
if (jndiPresent) {
@@ -338,13 +358,13 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
ReflectionUtils.doWithLocalFields(targetClass, field -> {
if (ejbClass != null && field.isAnnotationPresent(ejbClass)) {
if (ejbAnnotationType != null && field.isAnnotationPresent(ejbAnnotationType)) {
if (Modifier.isStatic(field.getModifiers())) {
throw new IllegalStateException("@EJB annotation is not supported on static fields");
}
currElements.add(new EjbRefElement(field, field, null));
}
else if (field.isAnnotationPresent(Resource.class)) {
else if (jakartaResourceType != null && field.isAnnotationPresent(jakartaResourceType)) {
if (Modifier.isStatic(field.getModifiers())) {
throw new IllegalStateException("@Resource annotation is not supported on static fields");
}
@@ -352,6 +372,14 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
currElements.add(new ResourceElement(field, field, null));
}
}
else if (javaxResourceType != null && field.isAnnotationPresent(javaxResourceType)) {
if (Modifier.isStatic(field.getModifiers())) {
throw new IllegalStateException("@Resource annotation is not supported on static fields");
}
if (!this.ignoredResourceTypes.contains(field.getType().getName())) {
currElements.add(new LegacyResourceElement(field, field, null));
}
}
});
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
@@ -360,7 +388,7 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
return;
}
if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (ejbClass != null && bridgedMethod.isAnnotationPresent(ejbClass)) {
if (ejbAnnotationType != null && bridgedMethod.isAnnotationPresent(ejbAnnotationType)) {
if (Modifier.isStatic(method.getModifiers())) {
throw new IllegalStateException("@EJB annotation is not supported on static methods");
}
@@ -370,7 +398,7 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new EjbRefElement(method, bridgedMethod, pd));
}
else if (bridgedMethod.isAnnotationPresent(Resource.class)) {
else if (jakartaResourceType != null && bridgedMethod.isAnnotationPresent(jakartaResourceType)) {
if (Modifier.isStatic(method.getModifiers())) {
throw new IllegalStateException("@Resource annotation is not supported on static methods");
}
@@ -383,6 +411,19 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
currElements.add(new ResourceElement(method, bridgedMethod, pd));
}
}
else if (javaxResourceType != null && bridgedMethod.isAnnotationPresent(javaxResourceType)) {
if (Modifier.isStatic(method.getModifiers())) {
throw new IllegalStateException("@Resource annotation is not supported on static methods");
}
Class<?>[] paramTypes = method.getParameterTypes();
if (paramTypes.length != 1) {
throw new IllegalStateException("@Resource annotation requires a single-arg method: " + method);
}
if (!this.ignoredResourceTypes.contains(paramTypes[0].getName())) {
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new LegacyResourceElement(method, bridgedMethod, pd));
}
}
}
});
@@ -584,7 +625,55 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
public ResourceElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor pd) {
super(member, pd);
Resource resource = ae.getAnnotation(Resource.class);
jakarta.annotation.Resource resource = ae.getAnnotation(jakarta.annotation.Resource.class);
String resourceName = resource.name();
Class<?> resourceType = resource.type();
this.isDefaultName = !StringUtils.hasLength(resourceName);
if (this.isDefaultName) {
resourceName = this.member.getName();
if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) {
resourceName = StringUtils.uncapitalizeAsProperty(resourceName.substring(3));
}
}
else if (embeddedValueResolver != null) {
resourceName = embeddedValueResolver.resolveStringValue(resourceName);
}
if (Object.class != resourceType) {
checkResourceType(resourceType);
}
else {
// No resource type specified... check field/method.
resourceType = getResourceType();
}
this.name = (resourceName != null ? resourceName : "");
this.lookupType = resourceType;
String lookupValue = resource.lookup();
this.mappedName = (StringUtils.hasLength(lookupValue) ? lookupValue : resource.mappedName());
Lazy lazy = ae.getAnnotation(Lazy.class);
this.lazyLookup = (lazy != null && lazy.value());
}
@Override
protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) {
return (this.lazyLookup ? buildLazyResourceProxy(this, requestingBeanName) :
getResource(this, requestingBeanName));
}
}
/**
* Class representing injection information about an annotated field
* or setter method, supporting the @Resource annotation.
*/
private class LegacyResourceElement extends LookupElement {
private final boolean lazyLookup;
public LegacyResourceElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor pd) {
super(member, pd);
javax.annotation.Resource resource = ae.getAnnotation(javax.annotation.Resource.class);
String resourceName = resource.name();
Class<?> resourceType = resource.type();
this.isDefaultName = !StringUtils.hasLength(resourceName);
@@ -630,7 +719,7 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
public EjbRefElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor pd) {
super(member, pd);
EJB resource = ae.getAnnotation(EJB.class);
jakarta.ejb.EJB resource = ae.getAnnotation(jakarta.ejb.EJB.class);
String resourceBeanName = resource.beanName();
String resourceName = resource.name();
this.isDefaultName = !StringUtils.hasLength(resourceName);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,8 +21,8 @@ import org.springframework.instrument.classloading.LoadTimeWeaver;
/**
* Interface to be implemented by
* {@link org.springframework.context.annotation.Configuration @Configuration}
* classes annotated with {@link EnableLoadTimeWeaving @EnableLoadTimeWeaving} that wish to
* customize the {@link LoadTimeWeaver} instance to be used.
* classes annotated with {@link EnableLoadTimeWeaving @EnableLoadTimeWeaving}
* that wish to customize the {@link LoadTimeWeaver} instance to be used.
*
* <p>See {@link org.springframework.scheduling.annotation.EnableAsync @EnableAsync}
* for usage examples and information on how a default {@code LoadTimeWeaver}
@@ -36,9 +36,9 @@ import org.springframework.instrument.classloading.LoadTimeWeaver;
public interface LoadTimeWeavingConfigurer {
/**
* Create, configure and return the {@code LoadTimeWeaver} instance to be used. Note
* that it is unnecessary to annotate this method with {@code @Bean}, because the
* object returned will automatically be registered as a bean by
* Create, configure and return the {@code LoadTimeWeaver} instance to be used.
* Note that it is unnecessary to annotate this method with {@code @Bean}
* because the object returned will automatically be registered as a bean by
* {@link LoadTimeWeavingConfiguration#loadTimeWeaver()}
*/
LoadTimeWeaver getLoadTimeWeaver();
@@ -168,12 +168,17 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
@Override
public boolean supportsEventType(ResolvableType eventType) {
for (ResolvableType declaredEventType : this.declaredEventTypes) {
if (declaredEventType.isAssignableFrom(eventType)) {
if (eventType.hasUnresolvableGenerics() ?
declaredEventType.toClass().isAssignableFrom(eventType.toClass()) :
declaredEventType.isAssignableFrom(eventType)) {
return true;
}
if (PayloadApplicationEvent.class.isAssignableFrom(eventType.toClass())) {
if (eventType.hasUnresolvableGenerics()) {
return true;
}
ResolvableType payloadType = eventType.as(PayloadApplicationEvent.class).getGeneric();
if (declaredEventType.isAssignableFrom(payloadType) || eventType.hasUnresolvableGenerics()) {
if (declaredEventType.isAssignableFrom(payloadType)) {
return true;
}
}
@@ -366,7 +371,7 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
* Return the target bean instance to use.
*/
protected Object getTargetBean() {
Assert.notNull(this.applicationContext, "ApplicationContext must no be null");
Assert.notNull(this.applicationContext, "ApplicationContext must not be null");
return this.applicationContext.getBean(this.beanName);
}
@@ -772,7 +772,8 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
// Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
// (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
if (!NativeDetector.inNativeImage() && beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
if (!NativeDetector.inNativeImage() && beanFactory.getTempClassLoader() == null &&
beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,8 @@ import java.security.ProtectionDomain;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.DecoratingClassLoader;
import org.springframework.core.OverridingClassLoader;
import org.springframework.core.SmartClassLoader;
@@ -45,15 +47,26 @@ class ContextTypeMatchClassLoader extends DecoratingClassLoader implements Smart
}
private static Method findLoadedClassMethod;
@Nullable
private static final Method findLoadedClassMethod;
static {
// Try to enable findLoadedClass optimization which allows us to selectively
// override classes that have not been loaded yet. If not accessible, we will
// always override requested classes, even when the classes have been loaded
// by the parent ClassLoader already and cannot be transformed anymore anyway.
Method method = null;
try {
findLoadedClassMethod = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class);
method = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class);
ReflectionUtils.makeAccessible(method);
}
catch (NoSuchMethodException ex) {
throw new IllegalStateException("Invalid [java.lang.ClassLoader] class: no 'findLoadedClass' method defined!");
catch (Throwable ex) {
// Typically a JDK 9+ InaccessibleObjectException...
// Avoid through JVM startup with --add-opens=java.base/java.lang=ALL-UNNAMED
LogFactory.getLog(ContextTypeMatchClassLoader.class).debug(
"ClassLoader.findLoadedClass not accessible -> will always override requested class", ex);
}
findLoadedClassMethod = method;
}
@@ -96,13 +109,14 @@ class ContextTypeMatchClassLoader extends DecoratingClassLoader implements Smart
if (isExcluded(className) || ContextTypeMatchClassLoader.this.isExcluded(className)) {
return false;
}
ReflectionUtils.makeAccessible(findLoadedClassMethod);
ClassLoader parent = getParent();
while (parent != null) {
if (ReflectionUtils.invokeMethod(findLoadedClassMethod, parent, className) != null) {
return false;
if (findLoadedClassMethod != null) {
ClassLoader parent = getParent();
while (parent != null) {
if (ReflectionUtils.invokeMethod(findLoadedClassMethod, parent, className) != null) {
return false;
}
parent = parent.getParent();
}
parent = parent.getParent();
}
return true;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-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.
@@ -64,9 +64,10 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor
/**
* Specify the maximum time allotted in milliseconds for the shutdown of
* any phase (group of SmartLifecycle beans with the same 'phase' value).
* <p>The default value is 30 seconds.
* Specify the maximum time allotted in milliseconds for the shutdown of any
* phase (group of {@link SmartLifecycle} beans with the same 'phase' value).
* <p>The default value is 30000 milliseconds (30 seconds).
* @see SmartLifecycle#getPhase()
*/
public void setTimeoutPerShutdownPhase(long timeoutPerShutdownPhase) {
this.timeoutPerShutdownPhase = timeoutPerShutdownPhase;
@@ -53,7 +53,6 @@ import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.Constants;
import org.springframework.jmx.export.assembler.AutodetectCapableMBeanInfoAssembler;
import org.springframework.jmx.export.assembler.MBeanInfoAssembler;
import org.springframework.jmx.export.assembler.SimpleReflectiveMBeanInfoAssembler;
@@ -92,6 +91,7 @@ import org.springframework.util.ObjectUtils;
* @author Rick Evans
* @author Mark Fisher
* @author Stephane Nicoll
* @author Sam Brannen
* @since 1.2
* @see #setBeans
* @see #setAutodetect
@@ -134,20 +134,25 @@ public class MBeanExporter extends MBeanRegistrationSupport implements MBeanExpo
/** Constant for the JMX {@code mr_type} "ObjectReference". */
private static final String MR_TYPE_OBJECT_REFERENCE = "ObjectReference";
/** Prefix for the autodetect constants defined in this class. */
private static final String CONSTANT_PREFIX_AUTODETECT = "AUTODETECT_";
/**
* Map of constant names to constant values for the autodetect constants defined
* in this class.
*/
private static final Map<String, Integer> constants = Map.of(
"AUTODETECT_NONE", AUTODETECT_NONE,
"AUTODETECT_MBEAN", AUTODETECT_MBEAN,
"AUTODETECT_ASSEMBLER", AUTODETECT_ASSEMBLER,
"AUTODETECT_ALL", AUTODETECT_ALL
);
/** Constants instance for this class. */
private static final Constants constants = new Constants(MBeanExporter.class);
/** The beans to be exposed as JMX managed resources, with JMX names as keys. */
@Nullable
private Map<String, Object> beans;
/** The autodetect mode to use for this MBeanExporter. */
@Nullable
private Integer autodetectMode;
Integer autodetectMode;
/** Whether to eagerly initialize candidate beans when autodetecting MBeans. */
private boolean allowEagerInit = false;
@@ -222,23 +227,6 @@ public class MBeanExporter extends MBeanRegistrationSupport implements MBeanExpo
this.autodetectMode = (autodetect ? AUTODETECT_ALL : AUTODETECT_NONE);
}
/**
* Set the autodetection mode to use.
* @throws IllegalArgumentException if the supplied value is not
* one of the {@code AUTODETECT_} constants
* @see #setAutodetectModeName(String)
* @see #AUTODETECT_ALL
* @see #AUTODETECT_ASSEMBLER
* @see #AUTODETECT_MBEAN
* @see #AUTODETECT_NONE
*/
public void setAutodetectMode(int autodetectMode) {
if (!constants.getValues(CONSTANT_PREFIX_AUTODETECT).contains(autodetectMode)) {
throw new IllegalArgumentException("Only values of autodetect constants allowed");
}
this.autodetectMode = autodetectMode;
}
/**
* Set the autodetection mode to use by name.
* @throws IllegalArgumentException if the supplied value is not resolvable
@@ -250,10 +238,26 @@ public class MBeanExporter extends MBeanRegistrationSupport implements MBeanExpo
* @see #AUTODETECT_NONE
*/
public void setAutodetectModeName(String constantName) {
if (!constantName.startsWith(CONSTANT_PREFIX_AUTODETECT)) {
throw new IllegalArgumentException("Only autodetect constants allowed");
}
this.autodetectMode = (Integer) constants.asNumber(constantName);
Assert.hasText(constantName, "'constantName' must not be null or blank");
Integer mode = constants.get(constantName);
Assert.notNull(mode, "Only autodetect constants allowed");
this.autodetectMode = mode;
}
/**
* Set the autodetection mode to use.
* @throws IllegalArgumentException if the supplied value is not
* one of the {@code AUTODETECT_} constants
* @see #setAutodetectModeName(String)
* @see #AUTODETECT_ALL
* @see #AUTODETECT_ASSEMBLER
* @see #AUTODETECT_MBEAN
* @see #AUTODETECT_NONE
*/
public void setAutodetectMode(int autodetectMode) {
Assert.isTrue(constants.containsValue(autodetectMode),
"Only values of autodetect constants allowed");
this.autodetectMode = autodetectMode;
}
/**
@@ -70,6 +70,9 @@ import org.springframework.util.ErrorHandler;
*/
public class ConcurrentTaskScheduler extends ConcurrentTaskExecutor implements TaskScheduler {
private static final TimeUnit NANO = TimeUnit.NANOSECONDS;
@Nullable
private static Class<?> managedScheduledExecutorServiceClass;
@@ -209,9 +212,9 @@ public class ConcurrentTaskScheduler extends ConcurrentTaskExecutor implements T
@Override
public ScheduledFuture<?> schedule(Runnable task, Instant startTime) {
Duration initialDelay = Duration.between(this.clock.instant(), startTime);
Duration delay = Duration.between(this.clock.instant(), startTime);
try {
return this.scheduledExecutor.schedule(decorateTask(task, false), initialDelay.toNanos(), TimeUnit.NANOSECONDS);
return this.scheduledExecutor.schedule(decorateTask(task, false), NANO.convert(delay), NANO);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
@@ -222,7 +225,8 @@ public class ConcurrentTaskScheduler extends ConcurrentTaskExecutor implements T
public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Instant startTime, Duration period) {
Duration initialDelay = Duration.between(this.clock.instant(), startTime);
try {
return this.scheduledExecutor.scheduleAtFixedRate(decorateTask(task, true), initialDelay.toNanos(), period.toNanos(), TimeUnit.NANOSECONDS);
return this.scheduledExecutor.scheduleAtFixedRate(decorateTask(task, true),
NANO.convert(initialDelay), NANO.convert(period), NANO);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
@@ -232,7 +236,8 @@ public class ConcurrentTaskScheduler extends ConcurrentTaskExecutor implements T
@Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Duration period) {
try {
return this.scheduledExecutor.scheduleAtFixedRate(decorateTask(task, true), 0, period.toNanos(), TimeUnit.NANOSECONDS);
return this.scheduledExecutor.scheduleAtFixedRate(decorateTask(task, true),
0, NANO.convert(period), NANO);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
@@ -243,7 +248,8 @@ public class ConcurrentTaskScheduler extends ConcurrentTaskExecutor implements T
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Instant startTime, Duration delay) {
Duration initialDelay = Duration.between(this.clock.instant(), startTime);
try {
return this.scheduledExecutor.scheduleWithFixedDelay(decorateTask(task, true), initialDelay.toNanos(), delay.toNanos(), TimeUnit.NANOSECONDS);
return this.scheduledExecutor.scheduleWithFixedDelay(decorateTask(task, true),
NANO.convert(initialDelay), NANO.convert(delay), NANO);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
@@ -253,7 +259,8 @@ public class ConcurrentTaskScheduler extends ConcurrentTaskExecutor implements T
@Override
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Duration delay) {
try {
return this.scheduledExecutor.scheduleWithFixedDelay(decorateTask(task, true), 0, delay.toNanos(), TimeUnit.NANOSECONDS);
return this.scheduledExecutor.scheduleWithFixedDelay(decorateTask(task, true),
0, NANO.convert(delay), NANO);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
@@ -108,9 +108,9 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
/**
* Set whether to wait for scheduled tasks to complete on shutdown,
* not interrupting running tasks and executing all tasks in the queue.
* <p>Default is "false", shutting down immediately through interrupting
* ongoing tasks and clearing the queue. Switch this flag to "true" if you
* prefer fully completed tasks at the expense of a longer shutdown phase.
* <p>Default is {@code false}, shutting down immediately through interrupting
* ongoing tasks and clearing the queue. Switch this flag to {@code true} if
* you prefer fully completed tasks at the expense of a longer shutdown phase.
* <p>Note that Spring's container shutdown continues while ongoing tasks
* are being completed. If you want this executor to block and wait for the
* termination of tasks before the rest of the container continues to shut
@@ -211,9 +211,13 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
}
/**
* Perform a shutdown on the underlying ExecutorService.
* Perform a full shutdown on the underlying ExecutorService,
* according to the corresponding configuration settings.
* @see #setWaitForTasksToCompleteOnShutdown
* @see #setAwaitTerminationMillis
* @see java.util.concurrent.ExecutorService#shutdown()
* @see java.util.concurrent.ExecutorService#shutdownNow()
* @see java.util.concurrent.ExecutorService#awaitTermination
*/
public void shutdown() {
if (logger.isDebugEnabled()) {
@@ -79,8 +79,8 @@ class ReschedulingRunnable extends DelegatingErrorHandlingRunnable implements Sc
if (this.scheduledExecutionTime == null) {
return null;
}
Duration initialDelay = Duration.between(this.triggerContext.getClock().instant(), this.scheduledExecutionTime);
this.currentFuture = this.executor.schedule(this, initialDelay.toNanos(), TimeUnit.NANOSECONDS);
Duration delay = Duration.between(this.triggerContext.getClock().instant(), this.scheduledExecutionTime);
this.currentFuture = this.executor.schedule(this, delay.toNanos(), TimeUnit.NANOSECONDS);
return this;
}
}
@@ -63,6 +63,9 @@ import org.springframework.util.concurrent.ListenableFutureTask;
public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport
implements AsyncListenableTaskExecutor, SchedulingTaskExecutor, TaskScheduler {
private static final TimeUnit NANO = TimeUnit.NANOSECONDS;
private volatile int poolSize = 1;
private volatile boolean removeOnCancelPolicy;
@@ -380,9 +383,9 @@ public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport
@Override
public ScheduledFuture<?> schedule(Runnable task, Instant startTime) {
ScheduledExecutorService executor = getScheduledExecutor();
Duration initialDelay = Duration.between(this.clock.instant(), startTime);
Duration delay = Duration.between(this.clock.instant(), startTime);
try {
return executor.schedule(errorHandlingTask(task, false), initialDelay.toNanos(), TimeUnit.NANOSECONDS);
return executor.schedule(errorHandlingTask(task, false), NANO.convert(delay), NANO);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
@@ -394,7 +397,8 @@ public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport
ScheduledExecutorService executor = getScheduledExecutor();
Duration initialDelay = Duration.between(this.clock.instant(), startTime);
try {
return executor.scheduleAtFixedRate(errorHandlingTask(task, true), initialDelay.toNanos(), period.toNanos(), TimeUnit.NANOSECONDS);
return executor.scheduleAtFixedRate(errorHandlingTask(task, true),
NANO.convert(initialDelay), NANO.convert(period), NANO);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
@@ -405,7 +409,8 @@ public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport
public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Duration period) {
ScheduledExecutorService executor = getScheduledExecutor();
try {
return executor.scheduleAtFixedRate(errorHandlingTask(task, true), 0, period.toNanos(), TimeUnit.NANOSECONDS);
return executor.scheduleAtFixedRate(errorHandlingTask(task, true),
0, NANO.convert(period), NANO);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
@@ -417,7 +422,8 @@ public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport
ScheduledExecutorService executor = getScheduledExecutor();
Duration initialDelay = Duration.between(this.clock.instant(), startTime);
try {
return executor.scheduleWithFixedDelay(errorHandlingTask(task, true), initialDelay.toNanos(), delay.toNanos(), TimeUnit.NANOSECONDS);
return executor.scheduleWithFixedDelay(errorHandlingTask(task, true),
NANO.convert(initialDelay), NANO.convert(delay), NANO);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
@@ -428,7 +434,8 @@ public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Duration delay) {
ScheduledExecutorService executor = getScheduledExecutor();
try {
return executor.scheduleWithFixedDelay(errorHandlingTask(task, true), 0, delay.toNanos(), TimeUnit.NANOSECONDS);
return executor.scheduleWithFixedDelay(errorHandlingTask(task, true),
0, NANO.convert(delay), NANO);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
@@ -124,8 +124,10 @@ public class FieldError extends ObjectError {
@Override
public String toString() {
// We would preferably use ObjectUtils.nullSafeConciseToString(rejectedValue) here but
// keep including the full nullSafeToString representation for backwards compatibility.
return "Field error in object '" + getObjectName() + "' on field '" + this.field +
"': rejected value [" + ObjectUtils.nullSafeConciseToString(this.rejectedValue) + "]; " +
"': rejected value [" + ObjectUtils.nullSafeToString(this.rejectedValue) + "]; " +
resolvableToString();
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -109,7 +109,7 @@ open class BeanDefinitionDsl internal constructor (private val init: BeanDefinit
SINGLETON,
/**
* Scope constant for the standard singleton scope
* Scope constant for the standard prototype scope
* @see org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE
*/
PROTOTYPE
@@ -1,5 +1,5 @@
/*
* Copyright 2002-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.
@@ -93,7 +93,6 @@ public abstract class FooServiceImpl implements FooService {
@Override
@SuppressWarnings("deprecation")
public Future<String> asyncFoo(int id) {
System.out.println(Thread.currentThread().getName());
Assert.state(ServiceInvocationCounter.getThreadLocalCount() != null, "Thread-local counter not exposed");
return new org.springframework.scheduling.annotation.AsyncResult<>(fooDao().findFoo(id));
}

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