Compare commits

..

136 Commits

Author SHA1 Message Date
Spring Builds f0088831a9 Release v5.3.35 2024-05-16 06:57:11 +00:00
Juergen Hoeller 7da43a80e4 Upgrade to Reactor 2020.0.44 and Netty 4.1.109
Closes gh-32788
2024-05-14 22:54:32 +02:00
Juergen Hoeller 8323c87ea2 Polishing 2024-05-14 13:31:30 +02:00
Juergen Hoeller 0c65a5e04a Accept ajc-compiled @Aspect classes for Spring AOP proxy usage
AspectJExpressionPointcut leniently ignores unsupported expression.

Closes gh-32793
2024-05-14 13:14:09 +02:00
rstoyanchev ca9eb57c94 Update MockMvc section on Streaming in the docs
Closes gh-32687
2024-05-13 12:40:46 +01:00
Stéphane Nicoll df8374693b Adapt docs deployment properties
This commit fixes the artifact properties we set for "framework-docs"
artifacts. These have a different name as of 6.1.x and were backported
as is.

Closes gh-32781
2024-05-08 15:33:40 +02:00
Juergen Hoeller 9f35debdc6 Unwrap raw target Query instance in case of proxy mismatch
Closes gh-32766

(cherry picked from commit 59a125d06f)
2024-05-06 20:38:02 +02:00
Sam Brannen 8fe545edcd Fix compilation error in test 2024-05-03 12:37:42 +03:00
Sam Brannen f5baa329f7 Include repeatable annotation container in MergedAnnotations results
A bug has existed in Spring's MergedAnnotations support since it was
introduced in Spring Framework 5.2. Specifically, if the
MergedAnnotations API is used to search for annotations with "standard
repeatable annotation" support enabled (which is the default), it's
possible to search for a repeatable annotation but not for the
repeatable annotation's container annotation.

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

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

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

See gh-29685
Closes gh-32731

(cherry picked from commit 4baad16437)
2024-05-03 12:21:32 +03:00
Juergen Hoeller 924b684345 Consistently propagate ApplicationStartup to BeanFactory
Closes gh-32747

(cherry picked from commit 25cedcfb99)
2024-05-01 18:15:20 +02:00
Juergen Hoeller a48e2f31a3 Polishing 2024-05-01 16:07:04 +02:00
Juergen Hoeller 1833aafc3f Ignore non-String keys in PropertiesPropertySource.getPropertyNames()
Closes gh-32742

(cherry picked from commit 610626aec6)
2024-05-01 16:04:42 +02:00
Brian Clozel b8bc780365 Fix build warnings
See gh-32725
2024-04-30 19:09:03 +02:00
Arjen Poutsma b3724ebf84 Fix guard against multiple subscriptions
This commit changes the guard against multiple subscriptions, as the
previously used doOnSubscribe hook could not function as guard in
certain scenarios.

See gh-32727
Closes gh-32728
2024-04-30 16:20:54 +02:00
Brian Clozel 2e74a4d878 Upgrade to gradle-enterprise-conventions 0.0.17
Closes gh-32725
2024-04-29 13:24:11 +02:00
Juergen Hoeller 0c307b513e Polishing 2024-04-23 16:41:52 +02:00
Juergen Hoeller 6b6beec4ee Skip close lock if acquired by other thread already
Closes gh-32445

(cherry picked from commit d151931f86)
2024-04-23 16:26:26 +02:00
Juergen Hoeller 520a1130b8 Polishing (aligned with 6.1.x) 2024-04-18 13:15:49 +02:00
yhao3 ceeb55291a Update links to HttpOnly documentation at OWASP in ResponseCookie
See gh-32663
Closes gh-32668

(cherry picked from commit 7f27ba3902)
2024-04-18 12:01:30 +02:00
Spring Builds cbc2ab6ab9 Next development version (v5.3.35-SNAPSHOT) 2024-04-11 07:16:36 +00:00
Brian Clozel 7678286fb3 Refine UriComponentsBuilder parsing
This commit refines the expressions for the scheme, user info, host and
port parts of the URL in UriComponentsBuilder to better conform to
RFC 3986.

Fixes gh-32618
2024-04-11 08:50:46 +02:00
Stéphane Nicoll 510ff87721 Upgrade to Reactor 2020.0.43
Closes gh-32594
2024-04-09 19:57:06 +02:00
Sam Brannen 7609727433 Detect bridge methods across ApplicationContexts in MethodIntrospector
Prior to this commit, MethodIntrospector failed to properly detect
bridge methods for subsequent invocations of selectMethods() with the
same targetType and MetadataLookup, if such subsequent invocations
occurred after the ApplicationContext had been refreshed.

The reason this occurs is due to the following.

- Class#getDeclaredMethods() always returns "child copies" of the
  underlying Method instances -- which means that `equals()` should be
  used instead of `==` whenever the compared Method instances can come
  from different sources (such as the static caches mentioned below).

- BridgeMethodResolver caches resolved bridge methods in a static cache
  -- which is never cleared.

- ReflectionUtils caches declared methods in a static cache
  -- which gets cleared when an ApplicationContext is refreshed.

Consequently, if you attempt to load an ApplicationContext twice in the
same ClassLoader, the second attempt uses the existing, populated cache
for bridged methods but a cleared, empty cache for declared methods.
This results in new invocations of Class#getDeclaredMethods(), and
identity checks with `==` then fail to detect equivalent bridge methods.

This commit addresses this by additionally comparing bridge methods
using `equals()` in MethodIntrospector.selectMethods().

Note that the `==` checks remain in place as an optimization for when
`equals()` is unnecessary.

Closes gh-32586

(cherry picked from commit e702733c7b)
2024-04-09 19:14:32 +02:00
Juergen Hoeller a0ae96da69 Upgrade to Netty 4.1.108 2024-04-09 17:02:52 +02:00
Juergen Hoeller 1d2daa5d6e Log column type for limited support message in getResultSetValue
Closes gh-32601

(cherry picked from commit c5590ae9e6)
2024-04-09 16:23:19 +02:00
Stéphane Nicoll 239594595c Upgrade actions that use deprecated features 2024-03-20 10:38:57 +01:00
Juergen Hoeller 76c0017180 Polishing 2024-03-18 16:34:06 +01:00
Juergen Hoeller 0628b479f1 Propagate JMS IllegalStateException from commit/rollbackIfNecessary
Closes gh-32473

(cherry picked from commit cd7ba1835c)
2024-03-18 16:25:33 +01:00
Sam Brannen f6205d4207 Avoid unnecessary Annotation array cloning in TypeDescriptor
Closes gh-32476

(cherry picked from commit 42a4f28962)
2024-03-18 15:19:40 +01:00
Juergen Hoeller d21100fea0 Restore original toString representation (revert accidental backport)
See gh-32405
2024-03-17 20:46:59 +01:00
Juergen Hoeller f7f1028428 Remove superfluous @NonNull declarations 2024-03-16 14:39:58 +01:00
Juergen Hoeller 1cb0c7c036 Avoid cloning empty Annotation array in TypeDescriptor (backport)
Closes gh-32405
2024-03-16 14:31:24 +01:00
Juergen Hoeller 51d70dcf34 Polishing 2024-03-15 21:30:07 +01:00
Juergen Hoeller 61f7087911 Consistently apply TaskDecorator to ManagedExecutorService as well
Closes gh-32455
2024-03-15 21:23:43 +01:00
Stéphane Nicoll 2ff8a00e9a Enable backport bot on 5.3.x
Closes gh-32451
2024-03-15 11:19:11 +01:00
Stéphane Nicoll a653b85378 Harmonize Concourse configuration 2024-03-15 09:45:52 +01:00
Stéphane Nicoll 17650e0741 Move CI to GitHub Actions
Closes gh-32449
2024-03-15 09:30:15 +01:00
Spring Builds 0c17d257fb Next development version (v5.3.34-SNAPSHOT) 2024-03-14 09:14:52 +00:00
rstoyanchev 297cbae299 Extract reusable checkSchemeAndPort method
Closes gh-32440
2024-03-14 08:49:55 +00:00
Juergen Hoeller 274fba47f3 Additional unit tests for operations on empty UriTemplate
See gh-32432

(cherry picked from commit 54a6d89da7)
2024-03-13 18:34:17 +01:00
Kasper Bisgaard 5dfec09edd Allow UriTemplate to be built with an empty template
Closes gh-32438
2024-03-13 17:38:27 +01:00
Juergen Hoeller 5056e8cbfb Upgrade to Reactor 2020.0.42
Closes gh-32422
2024-03-12 20:35:39 +01:00
Juergen Hoeller 4566e8685d Polishing
(cherry picked from commit 723c94e5ac)
2024-03-12 20:35:30 +01:00
Sam Brannen 1b84f970de Disable external Javadoc URLs not supported on JDK 8
This commit comments out (disables) 3 external Javadoc URLs, since the
javadoc tool on JDK 8 cannot access them.
2024-03-11 13:03:36 +01:00
Sébastien Deleuze 41bc43b033 Build KDoc against 5.3.x Spring Framework Javadoc
Closes gh-32414
2024-03-11 10:19:49 +01:00
Juergen Hoeller 915d5bddea Polishing 2024-03-08 19:49:28 +01:00
rstoyanchev dc86feaeb6 Remove IOException that's not thrown from Javadoc 2024-03-07 16:16:30 +00:00
rstoyanchev ff412de247 Use wrapped response in HandlerFunctionAdapter
webmvc.fn now also uses the StandardServletAsyncWebRequest wrapped response
to enforce lifecycle rules from Servlet spec (section 2.3.3.4).

See gh-32342
2024-03-07 15:00:08 +00:00
rstoyanchev 5d572f6490 Fix checkstyle violation 2024-03-05 12:39:48 +00:00
rstoyanchev b8b1f5b6be Add missed merge changes from last commit
See gh-32342
2024-03-05 12:28:54 +00:00
rstoyanchev 99e38ecf41 Backport tests for wrapping of response for async requests
This is a backport of commits 4b96cd and ef0717.

Closes gh-32342
2024-03-05 12:11:55 +00:00
Juergen Hoeller ed0c2ff37f Restore ability to return original method for proxy-derived method
Closes gh-32365
2024-03-04 23:42:04 +01:00
rstoyanchev e668e7767c Wrap PrintWriter in StandardServletAsyncWebRequest
Closes gh-32342
2024-03-03 18:38:40 +00:00
rstoyanchev 30c75ffe8e Avoid locking if not handling asynchronously
Avoid the overhead of locking in the ServletOutputStream wrapper
if it is not an asynchronous request.

See gh-32342
2024-03-03 18:38:05 +00:00
rstoyanchev dd5fe68522 Dispatch again after disconnected client error
Dispatching was prevented for disconnected client errors after
recent reports like #32042 when running on Tomcat, and the
async request was completed from the onError notification.
This has the side effect of not allowing exception resolvers
to take final action even if the response is not writeable.

After all updates for this issue, it appears the dispatch no
longer causes issues. Tomcat actually does not do the dispatch,
but it doesn't seem to cause any issues, and on other servers
like Jetty where the dispatch works, applications can have a
chance to handle the exception.

This change removes the disconnected client checks and allows
dispatching again. After the change DisconnectedClientHelper
is no longer needed in the 5.3.x branch.

See gh-32342
2024-03-03 18:37:21 +00:00
rstoyanchev 2aca714010 Add locking in StandardServletAsyncWebRequest
The lock protects against race between onError/onComplete notifications
and operations on the ServletOutputStream.

See gh-32342
2024-03-01 18:21:59 +00:00
rstoyanchev 3b7c435134 Polishing
See gh-32342
2024-03-01 15:57:16 +00:00
rstoyanchev 6432b13a4c Add state and response wrapping to StandardServletAsyncWebRequest
The wrapped response prevents use after AsyncListener onError or completion
to ensure compliance with Servlet Spec 2.3.3.4.

See gh-32342
2024-03-01 14:31:41 +00:00
rstoyanchev 3478a702e5 Improve concurrent handling of result in WebAsyncManager
1. Use state transitions
2. Increase synchronized scope in setConcurrentResultAndDispatch

See gh-32342
2024-02-29 20:43:31 +00:00
rstoyanchev b31550fd85 Align 5.3.x with 6.1.x
In preparation for a larger update, start by aligning with
6.1.x, which includes changes for gh-32042 and gh-30232.

See gh-32342
2024-02-29 17:48:09 +00:00
Juergen Hoeller 701e9e410f Polishing 2024-02-28 21:13:10 +01:00
Juergen Hoeller ce385d136d Consistent nullability for internal field access 2024-02-28 21:12:57 +01:00
Arjen Poutsma e44f84e8ee Restore Jetty 10 compatibility in JettyClientHttpResponse
Closes gh-32337
2024-02-28 13:33:18 +01:00
Sam Brannen 464fa7ea0e Do not cache Content-Type in ContentCachingResponseWrapper
Based on feedback from several members of the community, we have
decided to revert the caching of the Content-Type header that was
introduced in ContentCachingResponseWrapper in 375e0e6827.

This commit therefore completely removes Content-Type caching in
ContentCachingResponseWrapper and updates the existing tests
accordingly.

To provide guards against future regressions in this area, this commit
also introduces explicit tests for the 6 ways to set the content length
in ContentCachingResponseWrapper and modifies a test in
ShallowEtagHeaderFilterTests to ensure that a Content-Type header set
directly on ContentCachingResponseWrapper is propagated to the
underlying response even if content caching is disabled for the
ShallowEtagHeaderFilter.

See gh-32039
See gh-32317
Closes gh-32322
2024-02-28 11:13:59 +01:00
Sam Brannen 6e1f583c06 Polish ShallowEtagHeaderFilterTests 2024-02-28 11:06:55 +01:00
Sébastien Deleuze 57646a092e Refine *HttpMessageConverter#getContentLength null safety
Closes gh-32332
2024-02-27 15:50:06 +01:00
Sam Brannen 380f5d5ea4 Honor Content-[Type|Length] headers from wrapped response again
Commit 375e0e6827 introduced a regression in
ContentCachingResponseWrapper (CCRW). Specifically, CCRW no longer
honors Content-Type and Content-Length headers that have been set in
the wrapped response and now incorrectly returns null for those header
values if they have not been set directly in the CCRW.

This commit fixes this regression as follows.

- The Content-Type and Content-Length headers set in the wrapped
  response are honored in getContentType(), containsHeader(),
  getHeader(), and getHeaders() unless those headers have been set
  directly in the CCRW.

- In copyBodyToResponse(), the Content-Type in the wrapped response is
  only overridden if the Content-Type has been set directly in the CCRW.

Furthermore, prior to this commit, getHeaderNames() returned duplicates
for the Content-Type and Content-Length headers if they were set in the
wrapped response as well as in CCRW.

This commit fixes that by returning a unique set from getHeaderNames().

This commit also updates ContentCachingResponseWrapperTests to verify
the expected behavior for Content-Type and Content-Length headers that
are set in the wrapped response as well as in CCRW.

See gh-32039
See gh-32317
Closes gh-32322
2024-02-25 18:08:12 +01:00
Sam Brannen 1acc1c3a27 Polish ContentCachingResponseWrapper[Tests] 2024-02-25 18:02:59 +01:00
Spring Builds 6ad75bdb4a Next development version (v5.3.33-SNAPSHOT) 2024-02-15 13:40:21 +00:00
Juergen Hoeller 8e3ad4a488 Upgrade to Reactor 2020.0.41
Includes Jetty 9.4.54, Netty 4.1.107, Undertow 2.2.29
2024-02-15 10:40:25 +01:00
Juergen Hoeller 6f4cc40a52 Document ResourcePropertiesPersister as only intended for internal use 2024-02-13 11:32:18 +01:00
Juergen Hoeller 452973fbbd Consistent Lock field declaration (instead of ReentrantLock field type)
(cherry picked from commit b4153618a4)
2024-02-13 11:31:55 +01:00
rstoyanchev 7ec5c994c1 Update user info pattern
Closes gh-32211
2024-02-13 07:18:50 +00:00
Juergen Hoeller 60035b5e39 Polishing 2024-02-01 17:41:50 +01:00
Juergen Hoeller 43ecb0b94a Eagerly initialize ZERO_NANOS constant 2024-01-29 16:27:18 +01:00
Juergen Hoeller 0e5edc4474 Upgrade to Checkstyle 10.12.7 and spring-javaformat-checkstyle 0.0.41 2024-01-29 16:02:27 +01:00
Juergen Hoeller 70e2e89602 Explicit documentation note on cron-vs-quartz parsing convention
Closes gh-32128
2024-01-29 16:02:19 +01:00
Juergen Hoeller a2af34f9b6 Polishing 2024-01-24 22:48:27 +01:00
Juergen Hoeller c35e90c171 Compare qualifier value arrays with equality semantics
Closes gh-32106

(cherry picked from commit c5a75219ce)
2024-01-24 22:48:12 +01:00
Arjen Poutsma 186d6e370d Guard against multiple body subscriptions
Before this commit, the Jetty connector did not have any
safeguard against multiple body subscriptions. Such as check has now
been added.

See gh-32100
Closes gh-32101
2024-01-24 14:05:06 +01:00
Juergen Hoeller 732642d8ad Declare allowPrivateNetwork as available since 5.3.32
See gh-28546
See gh-31974
2024-01-24 12:22:51 +01:00
Juergen Hoeller d7649d088d Polishing 2024-01-24 12:20:35 +01:00
Juergen Hoeller 7ac66eff4d Consistent nullability for concurrent result
(cherry picked from commit b92877990d)
2024-01-24 12:05:33 +01:00
Juergen Hoeller f22bdf4734 Polishing 2024-01-19 17:29:16 +01:00
Arjen Poutsma a0f24cf485 Handle Content-Length in ShallowEtagHeaderFilter more robustly
This commit ensures that setting the Content-Length through
setHeader("Content-Length", x") has the same effect as calling
setContentLength in the ShallowEtagHeaderFilter. It also filters out
Content-Type headers similarly to Content-Length.

See gh-32039
Closes gh-32051
2024-01-18 16:07:02 +01:00
Brian Clozel 562fa4e1ad Upgrade CI image to JDK 8u402+7 and 17.0.10+13 2024-01-17 21:17:46 +01:00
Brian Clozel 9d10807f8b Upgrade CI image to Ubuntu Jammy 20240111 2024-01-17 21:17:05 +01:00
rstoyanchev fd240b3b86 Double-checked lock in ChannelSendOperator#request
Closes gh-31865
2024-01-12 17:18:19 +00:00
Sébastien Deleuze df1bec9e97 Polishing
See gh-31974
2024-01-08 12:23:23 +01:00
Sébastien Deleuze cfec88bfa8 Add CORS support for Private Network Access
This commit adds CORS support for Private Network Access
by adding an Access-Control-Allow-Private-Network response
header when the preflight request is sent with an
Access-Control-Request-Private-Network header and that
Private Network Access has been enabled in the CORS
configuration.

See https://developer.chrome.com/blog/private-network-access-preflight/
for more details.

Closes gh-31974

(cherry picked from commit 318d460256)
2024-01-08 11:49:00 +01:00
Juergen Hoeller dd9b6749d7 Avoid getMostSpecificMethod resolution for non-annotated methods
This is aligned with AutowiredAnnotationBeanPostProcessor now.

Closes gh-31967

(cherry picked from commit 9912a52bb8)
2024-01-07 16:46:50 +01:00
Juergen Hoeller 2b9cea618f Propagate arguments for dynamic prototype-scoped advice
Closes gh-28407

(cherry picked from commit 43107e7eb1)
2024-01-07 00:26:29 +01:00
Juergen Hoeller e16dd5f3e2 Refine exception handling for type not present versus access exception
Includes TypeVariable bypass for reflection-free annotation retrieval.
Includes info log message for annotation attribute retrieval failure.

Closes gh-27182

(cherry picked from commit 70247c4a94)
2024-01-06 23:20:39 +01:00
Juergen Hoeller c1db06af88 Default time zone resolution from scheduler-wide Clock
Closes gh-31948
2024-01-05 10:30:54 +01:00
Juergen Hoeller b2bdc7de30 Polishing 2023-12-30 15:51:05 +01:00
Sam Brannen 8f2bb4973d Scan annotations on method in interface hierarchy only once
Prior to this commit, the AnnotationsScanner used in the
MergedAnnotations infrastructure found duplicate annotations on methods
within multi-level interface hierarchies.

This commit addresses this issue by scanning methods at a given level
in the interface hierarchy using ReflectionUtils#getDeclaredMethods
instead of Class#getMethods, since the latter includes public methods
declared in super-interfaces which will anyway be scanned when
processing super-interfaces recursively.

Closes gh-31803

(cherry picked from commit 75da9c3c47)
(cherry picked from commit 1e742aae34)
2023-12-13 10:24:15 +01:00
Sam Brannen a45154c875 Polish MergedAnnotation tests
(cherry picked from commit 952223dcf9)
(cherry picked from commit 20dd585c93)
2023-12-13 10:24:15 +01:00
Juergen Hoeller d54e101f00 PathEditor considers single-letter URI scheme as NIO path candidate
Closes gh-29881

(cherry picked from commit c56c304536)
2023-11-30 14:25:16 +01:00
Sam Brannen 9ef2b5d908 Include scroll() in SharedEntityManagerCreator's queryTerminatingMethods
This commit supports the scroll() and scroll(ScrollMode) methods from
Hibernate's Query API in SharedEntityManagerCreator's query-terminating
methods set.

See gh-31682
Closes gh-31684
2023-11-26 12:22:56 +01:00
Juergen Hoeller 0ecbeef01d Consider generics in equals method (for ConversionService caching)
Closes gh-31672

(cherry picked from commit 710373d286)
2023-11-24 23:40:04 +01:00
Juergen Hoeller 5105fdf8c0 Filter candidate methods by name first (for more efficient sorting)
Closes gh-28377

(cherry picked from commit 0599320bd8)
2023-11-24 23:33:32 +01:00
Brian Clozel 687676ea47 Fix ordering of releasing resources in JSON Encoder
Prior to this commit, the Jackson 2.x encoders, in case of encoding a
stream of data, would first release the `ByteArrayBuilder` and then the
`JsonGenerator`. This order is inconsistent with the single value
variant (see `o.s.h.codec.json.AbstractJackson2Encoder#encodeValue`) and
invalid since the `JsonGenerator` uses internally the
`ByteArrayBuilder`.

In case of a CSV Encoder, the codec can buffer data to write the column
names of the CSV file. Writing an empty Flux with this Encoder would not
fail but still log a NullPointerException ignored by the reactive
pipeline.

This commit fixes the order and avoid such issues at runtime.

Fixes gh-31657
2023-11-22 21:35:16 +01:00
Juergen Hoeller b17714bb7b Polishing 2023-11-22 13:14:10 +01:00
rstoyanchev 61ac912f67 WebSocketMessageBrokerStats implements SmartInitializingSingleton
Closes gh-26536
2023-11-21 18:00:02 +00:00
Spring Builds 1077d5c5ea Next development version (v5.3.32-SNAPSHOT) 2023-11-16 08:11:41 +00:00
Juergen Hoeller b28a5f858c Upgrade to Reactor 2020.0.38 and Netty 4.1.101
Includes RxJava 3.1.8, SmallRye Mutiny 1.9.0, BlockHound 1.0.8, OpenPDF 1.3.33, Checkstyle 10.12.5

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

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

See gh-31598
Closes gh-31603

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

Closes gh-31582

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

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

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

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

Closes gh-28699

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

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

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

Closes gh-21265

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

Closes gh-31311
2023-09-25 15:26:42 +02:00
Juergen Hoeller 2b4c1e265c Reset findLoadedClassMethod in case of makeAccessible failing
Closes gh-31232
2023-09-14 16:46:17 +02:00
Spring Builds cc0cbc15aa Next development version (v5.3.31-SNAPSHOT) 2023-09-14 07:54:54 +00:00
268 changed files with 6130 additions and 3493 deletions
@@ -0,0 +1,33 @@
name: Send notification
description: Sends a Google Chat message as a notification of the job's outcome
inputs:
webhook-url:
description: 'Google Chat Webhook URL'
required: true
status:
description: 'Status of the job'
required: true
build-scan-url:
description: 'URL of the build scan to include in the notification'
run-name:
description: 'Name of the run to include in the notification'
default: ${{ format('{0} {1}', github.ref_name, github.job) }}
runs:
using: composite
steps:
- shell: bash
run: |
echo "BUILD_SCAN=${{ inputs.build-scan-url == '' && ' [build scan unavailable]' || format(' [<{0}|Build Scan>]', inputs.build-scan-url) }}" >> "$GITHUB_ENV"
echo "RUN_URL=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" >> "$GITHUB_ENV"
- shell: bash
if: ${{ inputs.status == 'success' }}
run: |
curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: "<${{ env.RUN_URL }}|${{ inputs.run-name }}> was successful ${{ env.BUILD_SCAN }}"}' || true
- shell: bash
if: ${{ inputs.status == 'failure' }}
run: |
curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: "<users/all> *<${{ env.RUN_URL }}|${{ inputs.run-name }}> failed* ${{ env.BUILD_SCAN }}"}' || true
- shell: bash
if: ${{ inputs.status == 'cancelled' }}
run: |
curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: "<${{ env.RUN_URL }}|${{ inputs.run-name }}> was cancelled"}' || true
+34
View File
@@ -0,0 +1,34 @@
name: Backport Bot
on:
issues:
types: [labeled]
pull_request:
types: [labeled]
push:
branches:
- '*.x'
permissions:
contents: read
jobs:
build:
permissions:
contents: read
issues: write
pull-requests: write
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: 'liberica'
java-version: 17
- name: Download BackportBot
run: wget https://github.com/spring-io/backport-bot/releases/download/latest/backport-bot-0.0.1-SNAPSHOT.jar
- name: Backport
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_EVENT: ${{ toJSON(github.event) }}
run: java -jar backport-bot-0.0.1-SNAPSHOT.jar --github.accessToken="$GITHUB_TOKEN" --github.event_name "$GITHUB_EVENT_NAME" --github.event "$GITHUB_EVENT"
@@ -0,0 +1,63 @@
name: Build and deploy snapshot
on:
push:
branches:
- 5.3.x
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
build-and-deploy-snapshot:
if: ${{ github.repository == 'spring-projects/spring-framework' }}
name: Build and deploy snapshot
runs-on: ubuntu-latest
steps:
- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: 'liberica'
java-version: 8
- name: Check out code
uses: actions/checkout@v4
- name: Set up Gradle
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5
with:
cache-read-only: false
- name: Configure Gradle properties
shell: bash
run: |
mkdir -p $HOME/.gradle
echo 'systemProp.user.name=spring-builds+github' >> $HOME/.gradle/gradle.properties
echo 'systemProp.org.gradle.internal.launcher.welcomeMessageEnabled=false' >> $HOME/.gradle/gradle.properties
echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties
echo 'org.gradle.daemon=4' >> $HOME/.gradle/gradle.properties
- name: Build and publish
id: build
env:
CI: 'true'
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
run: ./gradlew -PdeploymentRepository=$(pwd)/deployment-repository build publishAllPublicationsToDeploymentRepository
- name: Deploy
uses: spring-io/artifactory-deploy-action@v0.0.1
with:
uri: 'https://repo.spring.io'
username: ${{ secrets.ARTIFACTORY_USERNAME }}
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
build-name: ${{ format('spring-framework-{0}', github.ref_name)}}
repository: 'libs-snapshot-local'
folder: 'deployment-repository'
signing-key: ${{ secrets.GPG_PRIVATE_KEY }}
signing-passphrase: ${{ secrets.GPG_PASSPHRASE }}
artifact-properties: |
/**/spring-*.zip::zip.name=spring-framework,zip.deployed=false
/**/spring-*-docs.zip::zip.type=docs
/**/spring-*-dist.zip::zip.type=dist
/**/spring-*-schema.zip::zip.type=schema
- name: Send notification
uses: ./.github/actions/send-notification
if: always()
with:
webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
status: ${{ job.status }}
build-scan-url: ${{ steps.build.outputs.build-scan-url }}
run-name: ${{ format('{0} | Linux | Java 8', github.ref_name) }}
+80
View File
@@ -0,0 +1,80 @@
name: CI
on:
push:
branches:
- 5.3.x
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
ci:
if: ${{ github.repository == 'spring-projects/spring-framework' }}
strategy:
matrix:
os:
- id: ubuntu-latest
name: Linux
java:
- version: 8
toolchain: false
- version: 17
toolchain: true
- version: 21
toolchain: true
exclude:
- os:
name: Linux
java:
version: 8
name: '${{ matrix.os.name}} | Java ${{ matrix.java.version}}'
runs-on: ${{ matrix.os.id }}
steps:
- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: 'liberica'
java-version: |
${{ matrix.java.version }}
${{ matrix.java.toolchain && '8' || '' }}
- name: Prepare Windows runner
if: ${{ runner.os == 'Windows' }}
run: |
git config --global core.autocrlf true
git config --global core.longPaths true
Stop-Service -name Docker
- name: Check out code
uses: actions/checkout@v4
- name: Set up Gradle
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5
with:
cache-read-only: false
- name: Configure Gradle properties
shell: bash
run: |
mkdir -p $HOME/.gradle
echo 'systemProp.user.name=spring-builds+github' >> $HOME/.gradle/gradle.properties
echo 'systemProp.org.gradle.internal.launcher.welcomeMessageEnabled=false' >> $HOME/.gradle/gradle.properties
echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties
echo 'org.gradle.daemon=4' >> $HOME/.gradle/gradle.properties
- name: Configure toolchain properties
if: ${{ matrix.java.toolchain }}
shell: bash
run: |
echo toolchainVersion=${{ matrix.java.version }} >> $HOME/.gradle/gradle.properties
echo systemProp.org.gradle.java.installations.auto-detect=false >> $HOME/.gradle/gradle.properties
echo systemProp.org.gradle.java.installations.auto-download=false >> $HOME/.gradle/gradle.properties
echo systemProp.org.gradle.java.installations.paths=${{ format('$JAVA_HOME_{0}_X64', matrix.java.version) }} >> $HOME/.gradle/gradle.properties
- name: Build
id: build
env:
CI: 'true'
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
run: ./gradlew check
- name: Send notification
uses: ./.github/actions/send-notification
if: always()
with:
webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
status: ${{ job.status }}
build-scan-url: ${{ steps.build.outputs.build-scan-url }}
run-name: ${{ format('{0} | {1} | Java {2}', github.ref_name, matrix.os.name, matrix.java.version) }}
@@ -9,5 +9,5 @@ jobs:
name: "Validation"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: gradle/wrapper-validation-action@v1
- uses: actions/checkout@v4
- uses: gradle/wrapper-validation-action@v2
+1 -1
View File
@@ -1,3 +1,3 @@
# Enable auto-env through the sdkman_auto_env config
# Add key=value pairs of SDKs to use below
java=8.0.372-librca
java=8.0.382-librca
+1 -1
View File
@@ -1,4 +1,4 @@
# <img src="src/docs/spring-framework.png" width="80" height="80"> Spring Framework [![Build Status](https://ci.spring.io/api/v1/teams/spring-framework/pipelines/spring-framework-5.3.x/jobs/build/badge)](https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-5.3.x?groups=Build") [![Revved up by Gradle Enterprise](https://img.shields.io/badge/Revved%20up%20by-Gradle%20Enterprise-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.spring.io/scans?search.rootProjectNames=spring)
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [![Build Status](https://github.com/spring-projects/spring-framework/actions/workflows/build-and-deploy-snapshot.yml/badge.svg?branch=5.3.x)](https://github.com/spring-projects/spring-framework/actions/workflows/build-and-deploy-snapshot.yml?query=branch%3A5.3.x) [![Revved up by Develocity](https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.spring.io/scans?search.rootProjectNames=spring)
This is the home of the Spring Framework: the foundation for all [Spring projects](https://spring.io/projects). Collectively the Spring Framework and the family of Spring projects are often referred to simply as "Spring".
+18 -16
View File
@@ -28,18 +28,18 @@ configure(allprojects) { project ->
dependencyManagement {
imports {
mavenBom "com.fasterxml.jackson:jackson-bom:2.12.7"
mavenBom "io.netty:netty-bom:4.1.95.Final"
mavenBom "io.projectreactor:reactor-bom:2020.0.34"
mavenBom "io.netty:netty-bom:4.1.109.Final"
mavenBom "io.projectreactor:reactor-bom:2020.0.44"
mavenBom "io.r2dbc:r2dbc-bom:Arabba-SR13"
mavenBom "io.rsocket:rsocket-bom:1.1.3"
mavenBom "org.eclipse.jetty:jetty-bom:9.4.51.v20230217"
mavenBom "org.eclipse.jetty:jetty-bom:9.4.54.v20240208"
mavenBom "org.jetbrains.kotlin:kotlin-bom:1.5.32"
mavenBom "org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.5.2"
mavenBom "org.jetbrains.kotlinx:kotlinx-serialization-bom:1.2.2"
mavenBom "org.junit:junit-bom:5.8.2"
}
dependencies {
dependencySet(group: 'org.apache.logging.log4j', version: '2.20.0') {
dependencySet(group: 'org.apache.logging.log4j', version: '2.21.1') {
entry 'log4j-api'
entry 'log4j-core'
entry 'log4j-jul'
@@ -67,9 +67,9 @@ configure(allprojects) { project ->
dependency "io.reactivex:rxjava:1.3.8"
dependency "io.reactivex:rxjava-reactive-streams:1.2.1"
dependency "io.reactivex.rxjava2:rxjava:2.2.21"
dependency "io.reactivex.rxjava3:rxjava:3.1.5"
dependency "io.smallrye.reactive:mutiny:1.8.0"
dependency "io.projectreactor.tools:blockhound:1.0.6.RELEASE"
dependency "io.reactivex.rxjava3:rxjava:3.1.8"
dependency "io.smallrye.reactive:mutiny:1.9.0"
dependency "io.projectreactor.tools:blockhound:1.0.8.RELEASE"
dependency "com.caucho:hessian:4.0.63"
dependency "com.fasterxml:aalto-xml:1.3.1"
@@ -96,7 +96,7 @@ configure(allprojects) { project ->
dependency "com.h2database:h2:2.1.214"
dependency "com.github.ben-manes.caffeine:caffeine:2.9.3"
dependency "com.github.librepdf:openpdf:1.3.30"
dependency "com.github.librepdf:openpdf:1.3.33"
dependency "com.rometools:rome:1.18.0"
dependency "commons-io:commons-io:2.5"
dependency "io.vavr:vavr:0.10.4"
@@ -128,18 +128,18 @@ configure(allprojects) { project ->
dependency "org.webjars:webjars-locator-core:0.48"
dependency "org.webjars:underscorejs:1.8.3"
dependencySet(group: 'org.apache.tomcat', version: '9.0.78') {
dependencySet(group: 'org.apache.tomcat', version: '9.0.82') {
entry 'tomcat-util'
entry('tomcat-websocket') {
exclude group: "org.apache.tomcat", name: "tomcat-servlet-api"
exclude group: "org.apache.tomcat", name: "tomcat-websocket-api"
}
}
dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.78') {
dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.82') {
entry 'tomcat-embed-core'
entry 'tomcat-embed-websocket'
}
dependencySet(group: 'io.undertow', version: '2.2.25.Final') {
dependencySet(group: 'io.undertow', version: '2.2.29.Final') {
entry 'undertow-core'
entry('undertow-servlet') {
exclude group: "org.jboss.spec.javax.servlet", name: "jboss-servlet-api_4.0_spec"
@@ -340,7 +340,7 @@ configure([rootProject] + javaProjects) { project ->
}
checkstyle {
toolVersion = "10.12.1"
toolVersion = "10.12.7"
configDirectory.set(rootProject.file("src/checkstyle"))
}
@@ -362,7 +362,7 @@ configure([rootProject] + javaProjects) { project ->
// JSR-305 only used for non-required meta-annotations
compileOnly("com.google.code.findbugs:jsr305")
testCompileOnly("com.google.code.findbugs:jsr305")
checkstyle("io.spring.javaformat:spring-javaformat-checkstyle:0.0.31")
checkstyle("io.spring.javaformat:spring-javaformat-checkstyle:0.0.41")
}
ext.javadocLinks = [
@@ -375,8 +375,9 @@ configure([rootProject] + javaProjects) { project ->
"https://tiles.apache.org/tiles-request/apidocs/",
"https://tiles.apache.org/framework/apidocs/",
"https://www.eclipse.org/aspectj/doc/released/aspectj5rt-api/",
"https://www.ehcache.org/apidocs/2.10.4/",
"https://www.quartz-scheduler.org/api/2.3.0/",
// Temporarily commenting out Ehcache and Quartz since javadoc on JDK 8 cannot access them.
// "https://www.ehcache.org/apidocs/2.10.4/",
// "https://www.quartz-scheduler.org/api/2.3.0/",
"https://www.javadoc.io/doc/com.fasterxml.jackson.core/jackson-core/2.12.7/",
"https://www.javadoc.io/doc/com.fasterxml.jackson.core/jackson-databind/2.12.7/",
"https://www.javadoc.io/doc/com.fasterxml.jackson.dataformat/jackson-dataformat-xml/2.12.7/",
@@ -388,7 +389,8 @@ configure([rootProject] + javaProjects) { project ->
// "https://junit.org/junit5/docs/5.8.2/api/",
"https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/",
"https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/",
"https://r2dbc.io/spec/0.8.5.RELEASE/api/",
// Temporarily commenting out R2DBC since javadoc on JDK 8 cannot access it.
// "https://r2dbc.io/spec/0.8.5.RELEASE/api/",
// The external Javadoc link for JSR 305 must come last to ensure that types from
// JSR 250 (such as @PostConstruct) are still supported. This is due to the fact
// that JSR 250 and JSR 305 both define types in javax.annotation, which results
+2
View File
@@ -1,5 +1,7 @@
== Spring Framework Concourse pipeline
NOTE: CI is being migrated to GitHub Actions.
The Spring Framework uses https://concourse-ci.org/[Concourse] for its CI build and other automated tasks.
The Spring team has a dedicated Concourse instance available at https://ci.spring.io with a build pipeline
for https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-5.3.x[Spring Framework 5.3.x].
+1 -1
View File
@@ -17,4 +17,4 @@ changelog:
- "type: dependency-upgrade"
contributors:
exclude:
names: ["bclozel", "jhoeller", "poutsma", "rstoyanchev", "sbrannen", "sdeleuze", "snicoll"]
names: ["bclozel", "jhoeller", "poutsma", "rstoyanchev", "sbrannen", "sdeleuze", "snicoll", "simonbasle"]
+1 -1
View File
@@ -1,4 +1,4 @@
FROM ubuntu:jammy-20230624
FROM ubuntu:jammy-20240125
ADD setup.sh /setup.sh
ADD get-jdk-url.sh /get-jdk-url.sh
+2 -2
View File
@@ -3,10 +3,10 @@ set -e
case "$1" in
java8)
echo "https://github.com/bell-sw/Liberica/releases/download/8u372+7/bellsoft-jdk8u372+7-linux-amd64.tar.gz"
echo "https://github.com/bell-sw/Liberica/releases/download/8u402%2B7/bellsoft-jdk8u402+7-linux-amd64.tar.gz"
;;
java17)
echo "https://github.com/bell-sw/Liberica/releases/download/17.0.7+7/bellsoft-jdk17.0.7+7-linux-amd64.tar.gz"
echo "https://github.com/bell-sw/Liberica/releases/download/17.0.10%2B13/bellsoft-jdk17.0.10+13-linux-amd64.tar.gz"
;;
*)
echo $"Unknown java version"
-1
View File
@@ -8,4 +8,3 @@ milestone: "5.3.x"
build-name: "spring-framework"
pipeline-name: "spring-framework"
concourse-url: "https://ci.spring.io"
task-timeout: 1h00m
+14 -122
View File
@@ -5,9 +5,7 @@ anchors:
password: ((github-ci-release-token))
branch: ((branch))
gradle-enterprise-task-params: &gradle-enterprise-task-params
GRADLE_ENTERPRISE_ACCESS_KEY: ((gradle_enterprise_secret_access_key))
GRADLE_ENTERPRISE_CACHE_USERNAME: ((gradle_enterprise_cache_user.username))
GRADLE_ENTERPRISE_CACHE_PASSWORD: ((gradle_enterprise_cache_user.password))
DEVELOCITY_ACCESS_KEY: ((gradle_enterprise_secret_access_key))
sonatype-task-params: &sonatype-task-params
SONATYPE_USERNAME: ((sonatype-username))
SONATYPE_PASSWORD: ((sonatype-password))
@@ -23,14 +21,6 @@ anchors:
docker-resource-source: &docker-resource-source
username: ((docker-hub-username))
password: ((docker-hub-password))
slack-fail-params: &slack-fail-params
text: >
:concourse-failed: <https://ci.spring.io/teams/${BUILD_TEAM_NAME}/pipelines/${BUILD_PIPELINE_NAME}/jobs/${BUILD_JOB_NAME}/builds/${BUILD_NAME}|${BUILD_PIPELINE_NAME} ${BUILD_JOB_NAME} failed!>
[$TEXT_FILE_CONTENT]
text_file: git-repo/build/build-scan-uri.txt
silent: true
icon_emoji: ":concourse:"
username: concourse-ci
changelog-task-params: &changelog-task-params
name: generated-changelog/tag
tag: generated-changelog/tag
@@ -45,7 +35,7 @@ resource_types:
source:
<<: *docker-resource-source
repository: concourse/registry-image-resource
tag: 1.7.1
tag: 1.8.0
- name: artifactory-resource
type: registry-image
source:
@@ -64,25 +54,12 @@ resource_types:
<<: *docker-resource-source
repository: dpb587/github-status-resource
tag: master
- name: slack-notification
type: registry-image
source:
<<: *docker-resource-source
repository: cfcommunity/slack-notification-resource
tag: latest
resources:
- name: git-repo
type: git
icon: github
source:
<<: *git-repo-resource-source
- name: every-morning
type: time
icon: alarm
source:
start: 8:00 AM
stop: 9:00 AM
location: Europe/Vienna
- name: ci-images-git-repo
type: git
icon: github
@@ -105,27 +82,6 @@ resources:
username: ((artifactory-username))
password: ((artifactory-password))
build_name: ((build-name))
- name: repo-status-build
type: github-status-resource
icon: eye-check-outline
source:
repository: ((github-repo-name))
access_token: ((github-ci-status-token))
branch: ((branch))
context: build
- name: repo-status-jdk17-build
type: github-status-resource
icon: eye-check-outline
source:
repository: ((github-repo-name))
access_token: ((github-ci-status-token))
branch: ((branch))
context: jdk17-build
- name: slack-alert
type: slack-notification
icon: slack
source:
url: ((slack-webhook-url))
- name: github-pre-release
type: github-release
icon: briefcase-download-outline
@@ -160,37 +116,23 @@ jobs:
- put: ci-image
params:
image: ci-image/image.tar
- name: build
- name: stage-milestone
serial: true
public: true
plan:
- get: ci-image
- get: git-repo
trigger: true
- put: repo-status-build
params: { state: "pending", commit: "git-repo" }
- do:
- task: build-project
image: ci-image
file: git-repo/ci/tasks/build-project.yml
privileged: true
timeout: ((task-timeout))
params:
<<: *build-project-task-params
on_failure:
do:
- put: repo-status-build
params: { state: "failure", commit: "git-repo" }
- put: slack-alert
params:
<<: *slack-fail-params
- put: repo-status-build
params: { state: "success", commit: "git-repo" }
trigger: false
- task: stage
image: ci-image
file: git-repo/ci/tasks/stage-version.yml
params:
RELEASE_TYPE: M
<<: *gradle-enterprise-task-params
- put: artifactory-repo
params: &artifactory-params
signing_key: ((signing-key))
signing_passphrase: ((signing-passphrase))
repo: libs-snapshot-local
repo: libs-staging-local
folder: distribution-repository
build_uri: "https://ci.spring.io/teams/${BUILD_TEAM_NAME}/pipelines/${BUILD_PIPELINE_NAME}/jobs/${BUILD_JOB_NAME}/builds/${BUILD_NAME}"
build_number: "${BUILD_PIPELINE_NAME}-${BUILD_JOB_NAME}-${BUILD_NAME}"
@@ -215,55 +157,9 @@ jobs:
- "/**/spring-*-schema.zip"
properties:
"zip.type": "schema"
get_params:
threads: 8
- name: jdk17-build
serial: true
public: true
plan:
- get: ci-image
- get: git-repo
- get: every-morning
trigger: true
- put: repo-status-jdk17-build
params: { state: "pending", commit: "git-repo" }
- do:
- task: check-project
image: ci-image
file: git-repo/ci/tasks/check-project.yml
privileged: true
timeout: ((task-timeout))
params:
TEST_TOOLCHAIN: 17
<<: *build-project-task-params
on_failure:
do:
- put: repo-status-jdk17-build
params: { state: "failure", commit: "git-repo" }
- put: slack-alert
params:
<<: *slack-fail-params
- put: repo-status-jdk17-build
params: { state: "success", commit: "git-repo" }
- name: stage-milestone
serial: true
plan:
- get: ci-image
- get: git-repo
trigger: false
- task: stage
image: ci-image
file: git-repo/ci/tasks/stage-version.yml
params:
RELEASE_TYPE: M
<<: *gradle-enterprise-task-params
- put: artifactory-repo
params:
<<: *artifactory-params
repo: libs-staging-local
- put: git-repo
params:
repository: stage-git-repo
- put: git-repo
params:
repository: stage-git-repo
- name: promote-milestone
serial: true
plan:
@@ -304,7 +200,6 @@ jobs:
- put: artifactory-repo
params:
<<: *artifactory-params
repo: libs-staging-local
- put: git-repo
params:
repository: stage-git-repo
@@ -348,7 +243,6 @@ jobs:
- put: artifactory-repo
params:
<<: *artifactory-params
repo: libs-staging-local
- put: git-repo
params:
repository: stage-git-repo
@@ -391,8 +285,6 @@ jobs:
<<: *changelog-task-params
groups:
- name: "builds"
jobs: ["build", "jdk17-build"]
- name: "releases"
jobs: ["stage-milestone", "stage-rc", "stage-release", "promote-milestone", "promote-rc", "promote-release", "create-github-release"]
- name: "ci-images"
-8
View File
@@ -1,8 +0,0 @@
#!/bin/bash
set -e
source $(dirname $0)/common.sh
pushd git-repo > /dev/null
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false --no-daemon --max-workers=4 check
popd > /dev/null
-9
View File
@@ -1,9 +0,0 @@
#!/bin/bash
set -e
source $(dirname $0)/common.sh
repository=$(pwd)/distribution-repository
pushd git-repo > /dev/null
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false --no-daemon --max-workers=4 -PdeploymentRepository=${repository} build publishAllPublicationsToDeploymentRepository
popd > /dev/null
-9
View File
@@ -1,9 +0,0 @@
#!/bin/bash
set -e
source $(dirname $0)/common.sh
pushd git-repo > /dev/null
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Porg.gradle.java.installations.fromEnv=JDK17 \
-PmainToolchain=${MAIN_TOOLCHAIN} -PtestToolchain=${TEST_TOOLCHAIN} --no-daemon --max-workers=4 check
popd > /dev/null
-1
View File
@@ -1,6 +1,5 @@
#!/bin/bash
source $(dirname $0)/common.sh
CONFIG_DIR=git-repo/ci/config
version=$( cat artifactory-repo/build-info.json | jq -r '.buildInfo.modules[0].id' | sed 's/.*:.*:\(.*\)/\1/' )
+2 -1
View File
@@ -26,4 +26,5 @@ run:
cat > /root/.docker/config.json <<EOF
{ "auths": { "https://index.docker.io/v1/": { "auth": "$DOCKER_HUB_AUTH" }}}
EOF
build
build
-19
View File
@@ -1,19 +0,0 @@
---
platform: linux
inputs:
- name: git-repo
caches:
- path: gradle
params:
BRANCH:
CI: true
GRADLE_ENTERPRISE_ACCESS_KEY:
GRADLE_ENTERPRISE_CACHE_USERNAME:
GRADLE_ENTERPRISE_CACHE_PASSWORD:
GRADLE_ENTERPRISE_URL: https://ge.spring.io
run:
path: bash
args:
- -ec
- |
${PWD}/git-repo/ci/scripts/build-pr.sh
-22
View File
@@ -1,22 +0,0 @@
---
platform: linux
inputs:
- name: git-repo
outputs:
- name: distribution-repository
- name: git-repo
caches:
- path: gradle
params:
BRANCH:
CI: true
GRADLE_ENTERPRISE_ACCESS_KEY:
GRADLE_ENTERPRISE_CACHE_USERNAME:
GRADLE_ENTERPRISE_CACHE_PASSWORD:
GRADLE_ENTERPRISE_URL: https://ge.spring.io
run:
path: bash
args:
- -ec
- |
${PWD}/git-repo/ci/scripts/build-project.sh
-24
View File
@@ -1,24 +0,0 @@
---
platform: linux
inputs:
- name: git-repo
outputs:
- name: distribution-repository
- name: git-repo
caches:
- path: gradle
params:
BRANCH:
CI: true
MAIN_TOOLCHAIN:
TEST_TOOLCHAIN:
GRADLE_ENTERPRISE_ACCESS_KEY:
GRADLE_ENTERPRISE_CACHE_USERNAME:
GRADLE_ENTERPRISE_CACHE_PASSWORD:
GRADLE_ENTERPRISE_URL: https://ge.spring.io
run:
path: bash
args:
- -ec
- |
${PWD}/git-repo/ci/scripts/check-project.sh
+1 -1
View File
@@ -4,7 +4,7 @@ image_resource:
type: registry-image
source:
repository: springio/github-changelog-generator
tag: '0.0.7'
tag: '0.0.8'
username: ((docker-hub-username))
password: ((docker-hub-password))
inputs:
+1 -1
View File
@@ -4,7 +4,7 @@ image_resource:
type: registry-image
source:
repository: springio/concourse-release-scripts
tag: '0.3.4'
tag: '0.4.0'
username: ((docker-hub-username))
password: ((docker-hub-password))
inputs:
+1 -1
View File
@@ -1,4 +1,4 @@
version=5.3.30
version=5.3.35
org.gradle.jvmargs=-Xmx2048m
org.gradle.caching=true
org.gradle.parallel=true
+1 -1
View File
@@ -5,7 +5,7 @@ tasks.findByName("dokkaHtmlPartial")?.configure {
sourceRoots.setFrom(file("src/main/kotlin"))
classpath.from(sourceSets["main"].runtimeClasspath)
externalDocumentationLink {
url.set(new URL("https://docs.spring.io/spring-framework/docs/current/javadoc-api/"))
url.set(new URL("https://docs.spring.io/spring-framework/docs/5.3.x/javadoc-api/"))
}
externalDocumentationLink {
url.set(new URL("https://projectreactor.io/docs/core/release/api/"))
+3 -3
View File
@@ -7,8 +7,8 @@ pluginManagement {
}
plugins {
id "com.gradle.enterprise" version "3.12.1"
id "io.spring.ge.conventions" version "0.0.13"
id "com.gradle.develocity" version "3.17.2"
id "io.spring.ge.conventions" version "0.0.17"
}
include "spring-aop"
@@ -42,7 +42,7 @@ rootProject.children.each {project ->
}
settings.gradle.projectsLoaded {
gradleEnterprise {
develocity {
buildScan {
File buildDir = settings.gradle.rootProject.getBuildDir()
buildDir.mkdirs()
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -174,25 +174,30 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
@Override
public ClassFilter getClassFilter() {
obtainPointcutExpression();
checkExpression();
return this;
}
@Override
public MethodMatcher getMethodMatcher() {
obtainPointcutExpression();
checkExpression();
return this;
}
/**
* Check whether this pointcut is ready to match,
* lazily building the underlying AspectJ pointcut expression.
* Check whether this pointcut is ready to match.
*/
private PointcutExpression obtainPointcutExpression() {
private void checkExpression() {
if (getExpression() == null) {
throw new IllegalStateException("Must set property 'expression' before attempting to match");
}
}
/**
* Lazily build the underlying AspectJ pointcut expression.
*/
private PointcutExpression obtainPointcutExpression() {
if (this.pointcutExpression == null) {
this.pointcutClassLoader = determinePointcutClassLoader();
this.pointcutExpression = buildPointcutExpression(this.pointcutClassLoader);
@@ -269,10 +274,9 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
@Override
public boolean matches(Class<?> targetClass) {
PointcutExpression pointcutExpression = obtainPointcutExpression();
try {
try {
return pointcutExpression.couldMatchJoinPointsInType(targetClass);
return obtainPointcutExpression().couldMatchJoinPointsInType(targetClass);
}
catch (ReflectionWorldException ex) {
logger.debug("PointcutExpression matching rejected target class - trying fallback expression", ex);
@@ -283,6 +287,9 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
}
}
}
catch (IllegalArgumentException | IllegalStateException ex) {
throw ex;
}
catch (Throwable ex) {
logger.debug("PointcutExpression matching rejected target class", ex);
}
@@ -291,7 +298,6 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
@Override
public boolean matches(Method method, Class<?> targetClass, boolean hasIntroductions) {
obtainPointcutExpression();
ShadowMatch shadowMatch = getTargetShadowMatch(method, targetClass);
// Special handling for this, target, @this, @target, @annotation
@@ -329,7 +335,6 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
obtainPointcutExpression();
ShadowMatch shadowMatch = getTargetShadowMatch(method, targetClass);
// Bind Spring AOP proxy to AspectJ "this" and Spring AOP target to AspectJ target,
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@ package org.springframework.aop.aspectj.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
@@ -57,8 +56,6 @@ import org.springframework.lang.Nullable;
*/
public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFactory {
private static final String AJC_MAGIC = "ajc$";
private static final Class<?>[] ASPECTJ_ANNOTATION_CLASSES = new Class<?>[] {
Pointcut.class, Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class};
@@ -69,37 +66,11 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
protected final ParameterNameDiscoverer parameterNameDiscoverer = new AspectJAnnotationParameterNameDiscoverer();
/**
* We consider something to be an AspectJ aspect suitable for use by the Spring AOP system
* if it has the @Aspect annotation, and was not compiled by ajc. The reason for this latter test
* is that aspects written in the code-style (AspectJ language) also have the annotation present
* when compiled by ajc with the -1.5 flag, yet they cannot be consumed by Spring AOP.
*/
@Override
public boolean isAspect(Class<?> clazz) {
return (hasAspectAnnotation(clazz) && !compiledByAjc(clazz));
}
private boolean hasAspectAnnotation(Class<?> clazz) {
return (AnnotationUtils.findAnnotation(clazz, Aspect.class) != null);
}
/**
* We need to detect this as "code-style" AspectJ aspects should not be
* interpreted by Spring AOP.
*/
private boolean compiledByAjc(Class<?> clazz) {
// The AJTypeSystem goes to great lengths to provide a uniform appearance between code-style and
// annotation-style aspects. Therefore there is no 'clean' way to tell them apart. Here we rely on
// an implementation detail of the AspectJ compiler.
for (Field field : clazz.getDeclaredFields()) {
if (field.getName().startsWith(AJC_MAGIC)) {
return true;
}
}
return false;
}
@Override
public void validate(Class<?> aspectClass) throws AopConfigException {
// If the parent has the annotation and isn't abstract it's an error
@@ -124,6 +95,7 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
}
}
/**
* Find and return the first AspectJ annotation on the given method
* (there <i>should</i> only be one anyway...).
@@ -163,7 +135,7 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
/**
* Class modelling an AspectJ annotation, exposing its type enumeration and
* Class modeling an AspectJ annotation, exposing its type enumeration and
* pointcut String.
* @param <A> the annotation type
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -126,10 +126,16 @@ public class AspectMetadata implements Serializable {
* Extract contents from String of form {@code pertarget(contents)}.
*/
private String findPerClause(Class<?> aspectClass) {
String str = aspectClass.getAnnotation(Aspect.class).value();
int beginIndex = str.indexOf('(') + 1;
int endIndex = str.length() - 1;
return str.substring(beginIndex, endIndex);
Aspect ann = aspectClass.getAnnotation(Aspect.class);
if (ann == null) {
return "";
}
String value = ann.value();
int beginIndex = value.indexOf('(');
if (beginIndex < 0) {
return "";
}
return value.substring(beginIndex + 1, value.length() - 1);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,7 +35,8 @@ import org.springframework.lang.Nullable;
/**
* Internal implementation of AspectJPointcutAdvisor.
* Note that there will be one instance of this advisor for each target method.
*
* <p>Note that there will be one instance of this advisor for each target method.
*
* @author Rod Johnson
* @author Juergen Hoeller
@@ -293,7 +294,7 @@ final class InstantiationModelAwarePointcutAdvisorImpl
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
// This can match only on declared pointcut.
return (isAspectMaterialized() && this.declaredPointcut.matches(method, targetClass));
return (isAspectMaterialized() && this.declaredPointcut.matches(method, targetClass, args));
}
private boolean isAspectMaterialized() {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -44,7 +44,8 @@ import org.springframework.util.CollectionUtils;
/**
* Base class for AOP proxy configuration managers.
* These are not themselves AOP proxies, but subclasses of this class are
*
* <p>These are not themselves AOP proxies, but subclasses of this class are
* normally factories from which AOP proxy instances are obtained directly.
*
* <p>This class frees subclasses of the housekeeping of Advices
@@ -52,7 +53,8 @@ import org.springframework.util.CollectionUtils;
* methods, which are provided by subclasses.
*
* <p>This class is serializable; subclasses need not be.
* This class is used to hold snapshots of proxies.
*
* <p>This class is used to hold snapshots of proxies.
*
* @author Rod Johnson
* @author Juergen Hoeller
@@ -104,7 +106,7 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
}
/**
* Create a AdvisedSupport instance with the given parameters.
* Create an {@code AdvisedSupport} instance with the given parameters.
* @param interfaces the proxied interfaces
*/
public AdvisedSupport(Class<?>... interfaces) {
@@ -115,7 +117,7 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
/**
* Set the given object as target.
* Will create a SingletonTargetSource for the object.
* <p>Will create a SingletonTargetSource for the object.
* @see #setTargetSource
* @see org.springframework.aop.target.SingletonTargetSource
*/
@@ -344,8 +346,7 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
private void validateIntroductionAdvisor(IntroductionAdvisor advisor) {
advisor.validateInterfaces();
// If the advisor passed validation, we can make the change.
Class<?>[] ifcs = advisor.getInterfaces();
for (Class<?> ifc : ifcs) {
for (Class<?> ifc : advisor.getInterfaces()) {
addInterface(ifc);
}
}
@@ -491,9 +492,9 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
}
/**
* Copy the AOP configuration from the given AdvisedSupport object,
* but allow substitution of a fresh TargetSource and a given interceptor chain.
* @param other the AdvisedSupport object to take proxy configuration from
* Copy the AOP configuration from the given {@link AdvisedSupport} object,
* but allow substitution of a fresh {@link TargetSource} and a given interceptor chain.
* @param other the {@code AdvisedSupport} object to take proxy configuration from
* @param targetSource the new TargetSource
* @param advisors the Advisors for the chain
*/
@@ -513,8 +514,8 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
}
/**
* Build a configuration-only copy of this AdvisedSupport,
* replacing the TargetSource.
* Build a configuration-only copy of this {@link AdvisedSupport},
* replacing the {@link TargetSource}.
*/
AdvisedSupport getConfigurationOnlyCopy() {
AdvisedSupport copy = new AdvisedSupport();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -186,10 +186,10 @@ public abstract class AopUtils {
* this method resolves bridge methods in order to retrieve attributes from
* the <i>original</i> method definition.
* @param method the method to be invoked, which may come from an interface
* @param targetClass the target class for the current invocation.
* May be {@code null} or may not even implement the method.
* @param targetClass the target class for the current invocation
* (can be {@code null} or may not even implement the method)
* @return the specific target method, or the original method if the
* {@code targetClass} doesn't implement it or is {@code null}
* {@code targetClass} does not implement it
* @see org.springframework.util.ClassUtils#getMostSpecificMethod
*/
public static Method getMostSpecificMethod(Method method, @Nullable Class<?> targetClass) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,9 +23,6 @@ import java.util.Map;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.aspectj.weaver.tools.PointcutExpression;
import org.aspectj.weaver.tools.PointcutPrimitive;
import org.aspectj.weaver.tools.UnsupportedPointcutPrimitiveException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import test.annotation.EmptySpringAnnotation;
@@ -42,7 +39,6 @@ import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.beans.testfixture.beans.subpkg.DeepBean;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
@@ -65,7 +61,7 @@ public class AspectJExpressionPointcutTests {
@BeforeEach
public void setUp() throws NoSuchMethodException {
public void setup() throws NoSuchMethodException {
getAge = TestBean.class.getMethod("getAge");
setAge = TestBean.class.getMethod("setAge", int.class);
setSomeNumber = TestBean.class.getMethod("setSomeNumber", Number.class);
@@ -175,25 +171,25 @@ public class AspectJExpressionPointcutTests {
@Test
public void testFriendlyErrorOnNoLocationClassMatching() {
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
assertThatIllegalStateException().isThrownBy(() ->
pc.matches(ITestBean.class))
.withMessageContaining("expression");
assertThatIllegalStateException()
.isThrownBy(() -> pc.getClassFilter().matches(ITestBean.class))
.withMessageContaining("expression");
}
@Test
public void testFriendlyErrorOnNoLocation2ArgMatching() {
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
assertThatIllegalStateException().isThrownBy(() ->
pc.matches(getAge, ITestBean.class))
.withMessageContaining("expression");
assertThatIllegalStateException()
.isThrownBy(() -> pc.getMethodMatcher().matches(getAge, ITestBean.class))
.withMessageContaining("expression");
}
@Test
public void testFriendlyErrorOnNoLocation3ArgMatching() {
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
assertThatIllegalStateException().isThrownBy(() ->
pc.matches(getAge, ITestBean.class, (Object[]) null))
.withMessageContaining("expression");
assertThatIllegalStateException()
.isThrownBy(() -> pc.getMethodMatcher().matches(getAge, ITestBean.class, (Object[]) null))
.withMessageContaining("expression");
}
@@ -210,8 +206,10 @@ public class AspectJExpressionPointcutTests {
// not currently testable in a reliable fashion
//assertDoesNotMatchStringClass(classFilter);
assertThat(methodMatcher.matches(setSomeNumber, TestBean.class, 12D)).as("Should match with setSomeNumber with Double input").isTrue();
assertThat(methodMatcher.matches(setSomeNumber, TestBean.class, 11)).as("Should not match setSomeNumber with Integer input").isFalse();
assertThat(methodMatcher.matches(setSomeNumber, TestBean.class, 12D))
.as("Should match with setSomeNumber with Double input").isTrue();
assertThat(methodMatcher.matches(setSomeNumber, TestBean.class, 11))
.as("Should not match setSomeNumber with Integer input").isFalse();
assertThat(methodMatcher.matches(getAge, TestBean.class)).as("Should not match getAge").isFalse();
assertThat(methodMatcher.isRuntime()).as("Should be a runtime match").isTrue();
}
@@ -246,14 +244,13 @@ public class AspectJExpressionPointcutTests {
@Test
public void testInvalidExpression() {
String expression = "execution(void org.springframework.beans.testfixture.beans.TestBean.setSomeNumber(Number) && args(Double)";
assertThatIllegalArgumentException().isThrownBy(
getPointcut(expression)::getClassFilter); // call to getClassFilter forces resolution
assertThatIllegalArgumentException().isThrownBy(() -> getPointcut(expression).getClassFilter().matches(Object.class));
}
private TestBean getAdvisedProxy(String pointcutExpression, CallCountingInterceptor interceptor) {
TestBean target = new TestBean();
Pointcut pointcut = getPointcut(pointcutExpression);
AspectJExpressionPointcut pointcut = getPointcut(pointcutExpression);
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
advisor.setAdvice(interceptor);
@@ -277,40 +274,29 @@ public class AspectJExpressionPointcutTests {
@Test
public void testWithUnsupportedPointcutPrimitive() {
String expression = "call(int org.springframework.beans.testfixture.beans.TestBean.getAge())";
assertThatExceptionOfType(UnsupportedPointcutPrimitiveException.class).isThrownBy(() ->
getPointcut(expression).getClassFilter()) // call to getClassFilter forces resolution...
.satisfies(ex -> assertThat(ex.getUnsupportedPrimitive()).isEqualTo(PointcutPrimitive.CALL));
assertThat(getPointcut(expression).getClassFilter().matches(Object.class)).isFalse();
}
@Test
public void testAndSubstitution() {
Pointcut pc = getPointcut("execution(* *(..)) and args(String)");
PointcutExpression expr = ((AspectJExpressionPointcut) pc).getPointcutExpression();
assertThat(expr.getPointcutExpression()).isEqualTo("execution(* *(..)) && args(String)");
AspectJExpressionPointcut pc = getPointcut("execution(* *(..)) and args(String)");
String expr = pc.getPointcutExpression().getPointcutExpression();
assertThat(expr).isEqualTo("execution(* *(..)) && args(String)");
}
@Test
public void testMultipleAndSubstitutions() {
Pointcut pc = getPointcut("execution(* *(..)) and args(String) and this(Object)");
PointcutExpression expr = ((AspectJExpressionPointcut) pc).getPointcutExpression();
assertThat(expr.getPointcutExpression()).isEqualTo("execution(* *(..)) && args(String) && this(Object)");
AspectJExpressionPointcut pc = getPointcut("execution(* *(..)) and args(String) and this(Object)");
String expr = pc.getPointcutExpression().getPointcutExpression();
assertThat(expr).isEqualTo("execution(* *(..)) && args(String) && this(Object)");
}
private Pointcut getPointcut(String expression) {
private AspectJExpressionPointcut getPointcut(String expression) {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(expression);
return pointcut;
}
public static class OtherIOther implements IOther {
@Override
public void absquatulate() {
// Empty
}
}
@Test
public void testMatchGenericArgument() {
String expression = "execution(* set*(java.util.List<org.springframework.beans.testfixture.beans.TestBean>) )";
@@ -505,6 +491,15 @@ public class AspectJExpressionPointcutTests {
}
public static class OtherIOther implements IOther {
@Override
public void absquatulate() {
// Empty
}
}
public static class HasGeneric {
public void setFriends(List<TestBean> friends) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -38,56 +38,67 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
* @author Adrian Colyer
* @author Juergen Hoeller
* @author Chris Beams
* @author Sam Brannen
*/
public class ArgumentBindingTests {
class ArgumentBindingTests {
@Test
public void testBindingInPointcutUsedByAdvice() {
TestBean tb = new TestBean();
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(tb);
proxyFactory.addAspect(NamedPointcutWithArgs.class);
ITestBean proxiedTestBean = proxyFactory.getProxy();
assertThatIllegalArgumentException().isThrownBy(() ->
proxiedTestBean.setName("Supercalifragalisticexpialidocious"));
}
@Test
public void testAnnotationArgumentNameBinding() {
TransactionalBean tb = new TransactionalBean();
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(tb);
void annotationArgumentNameBinding() {
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new TransactionalBean());
proxyFactory.addAspect(PointcutWithAnnotationArgument.class);
ITransactionalBean proxiedTestBean = proxyFactory.getProxy();
assertThatIllegalStateException().isThrownBy(
proxiedTestBean::doInTransaction);
assertThatIllegalStateException()
.isThrownBy(proxiedTestBean::doInTransaction)
.withMessage("Invoked with @Transactional");
}
@Test
public void testParameterNameDiscoverWithReferencePointcut() throws Exception {
void bindingInPointcutUsedByAdvice() {
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new TestBean());
proxyFactory.addAspect(NamedPointcutWithArgs.class);
ITestBean proxiedTestBean = proxyFactory.getProxy();
assertThatIllegalArgumentException()
.isThrownBy(() -> proxiedTestBean.setName("enigma"))
.withMessage("enigma");
}
@Test
void bindingWithDynamicAdvice() {
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new TestBean());
proxyFactory.addAspect(DynamicPointcutWithArgs.class);
ITestBean proxiedTestBean = proxyFactory.getProxy();
proxiedTestBean.applyName(1);
assertThatIllegalArgumentException()
.isThrownBy(() -> proxiedTestBean.applyName("enigma"))
.withMessage("enigma");
}
@Test
void parameterNameDiscoverWithReferencePointcut() throws Exception {
AspectJAdviceParameterNameDiscoverer discoverer =
new AspectJAdviceParameterNameDiscoverer("somepc(formal) && set(* *)");
discoverer.setRaiseExceptions(true);
Method methodUsedForParameterTypeDiscovery =
getClass().getMethod("methodWithOneParam", String.class);
String[] pnames = discoverer.getParameterNames(methodUsedForParameterTypeDiscovery);
assertThat(pnames.length).as("one parameter name").isEqualTo(1);
assertThat(pnames[0]).isEqualTo("formal");
Method method = getClass().getDeclaredMethod("methodWithOneParam", String.class);
assertThat(discoverer.getParameterNames(method)).containsExactly("formal");
}
public void methodWithOneParam(String aParam) {
@SuppressWarnings("unused")
private void methodWithOneParam(String aParam) {
}
public interface ITransactionalBean {
interface ITransactionalBean {
@Transactional
void doInTransaction();
}
public static class TransactionalBean implements ITransactionalBean {
static class TransactionalBean implements ITransactionalBean {
@Override
@Transactional
@@ -95,38 +106,46 @@ public class ArgumentBindingTests {
}
}
}
/**
* Represents Spring's Transactional annotation without actually introducing the dependency
*/
@Retention(RetentionPolicy.RUNTIME)
@interface Transactional {
}
/**
* Mimics Spring's @Transactional annotation without actually introducing the dependency.
*/
@Retention(RetentionPolicy.RUNTIME)
@interface Transactional {
}
@Aspect
class PointcutWithAnnotationArgument {
@Aspect
static class PointcutWithAnnotationArgument {
@Around(value = "execution(* org.springframework..*.*(..)) && @annotation(transaction)")
public Object around(ProceedingJoinPoint pjp, Transactional transaction) throws Throwable {
System.out.println("Invoked with transaction " + transaction);
throw new IllegalStateException();
@Around("execution(* org.springframework..*.*(..)) && @annotation(transactional)")
public Object around(ProceedingJoinPoint pjp, Transactional transactional) throws Throwable {
throw new IllegalStateException("Invoked with @Transactional");
}
}
@Aspect
static class NamedPointcutWithArgs {
@Pointcut("execution(* *(..)) && args(s,..)")
public void pointcutWithArgs(String s) {}
@Around("pointcutWithArgs(aString)")
public Object doAround(ProceedingJoinPoint pjp, String aString) throws Throwable {
throw new IllegalArgumentException(aString);
}
}
@Aspect("pertarget(execution(* *(..)))")
static class DynamicPointcutWithArgs {
@Around("execution(* *(..)) && args(java.lang.String)")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
throw new IllegalArgumentException(String.valueOf(pjp.getArgs()[0]));
}
}
}
@Aspect
class NamedPointcutWithArgs {
@Pointcut("execution(* *(..)) && args(s,..)")
public void pointcutWithArgs(String s) {}
@Around("pointcutWithArgs(aString)")
public Object doAround(ProceedingJoinPoint pjp, String aString) throws Throwable {
System.out.println("got '" + aString + "' at '" + pjp + "'");
throw new IllegalArgumentException(aString);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,29 +17,35 @@
package org.springframework.aop.support;
import java.lang.reflect.Method;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.interceptor.ExposeInvocationInterceptor;
import org.springframework.aop.target.EmptyTargetSource;
import org.springframework.aop.testfixture.interceptor.NopInterceptor;
import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.core.ResolvableType;
import org.springframework.core.testfixture.io.SerializationTestUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Rod Johnson
* @author Chris Beams
* @author Sebastien Deleuze
* @author Juergen Hoeller
*/
public class AopUtilsTests {
class AopUtilsTests {
@Test
public void testPointcutCanNeverApply() {
void testPointcutCanNeverApply() {
class TestPointcut extends StaticMethodMatcherPointcut {
@Override
public boolean matches(Method method, @Nullable Class<?> clazzy) {
@@ -52,13 +58,13 @@ public class AopUtilsTests {
}
@Test
public void testPointcutAlwaysApplies() {
void testPointcutAlwaysApplies() {
assertThat(AopUtils.canApply(new DefaultPointcutAdvisor(new NopInterceptor()), Object.class)).isTrue();
assertThat(AopUtils.canApply(new DefaultPointcutAdvisor(new NopInterceptor()), TestBean.class)).isTrue();
}
@Test
public void testPointcutAppliesToOneMethodOnObject() {
void testPointcutAppliesToOneMethodOnObject() {
class TestPointcut extends StaticMethodMatcherPointcut {
@Override
public boolean matches(Method method, @Nullable Class<?> clazz) {
@@ -78,7 +84,7 @@ public class AopUtilsTests {
* that's subverted the singleton construction limitation.
*/
@Test
public void testCanonicalFrameworkClassesStillCanonicalOnDeserialization() throws Exception {
void testCanonicalFrameworkClassesStillCanonicalOnDeserialization() throws Exception {
assertThat(SerializationTestUtils.serializeAndDeserialize(MethodMatcher.TRUE)).isSameAs(MethodMatcher.TRUE);
assertThat(SerializationTestUtils.serializeAndDeserialize(ClassFilter.TRUE)).isSameAs(ClassFilter.TRUE);
assertThat(SerializationTestUtils.serializeAndDeserialize(Pointcut.TRUE)).isSameAs(Pointcut.TRUE);
@@ -88,4 +94,45 @@ public class AopUtilsTests {
assertThat(SerializationTestUtils.serializeAndDeserialize(ExposeInvocationInterceptor.INSTANCE)).isSameAs(ExposeInvocationInterceptor.INSTANCE);
}
@Test
void testInvokeJoinpointUsingReflection() throws Throwable {
String name = "foo";
TestBean testBean = new TestBean(name);
Method method = ReflectionUtils.findMethod(TestBean.class, "getName");
Object result = AopUtils.invokeJoinpointUsingReflection(testBean, method, new Object[0]);
assertThat(result).isEqualTo(name);
}
@Test // gh-32365
void mostSpecificMethodBetweenJdkProxyAndTarget() throws Exception {
Class<?> proxyClass = new ProxyFactory(new WithInterface()).getProxy(getClass().getClassLoader()).getClass();
Method specificMethod = AopUtils.getMostSpecificMethod(proxyClass.getMethod("handle", List.class), WithInterface.class);
assertThat(ResolvableType.forMethodParameter(specificMethod, 0).getGeneric().toClass()).isEqualTo(String.class);
}
@Test // gh-32365
void mostSpecificMethodBetweenCglibProxyAndTarget() throws Exception {
Class<?> proxyClass = new ProxyFactory(new WithoutInterface()).getProxy(getClass().getClassLoader()).getClass();
Method specificMethod = AopUtils.getMostSpecificMethod(proxyClass.getMethod("handle", List.class), WithoutInterface.class);
assertThat(ResolvableType.forMethodParameter(specificMethod, 0).getGeneric().toClass()).isEqualTo(String.class);
}
interface ProxyInterface {
void handle(List<String> list);
}
static class WithInterface implements ProxyInterface {
public void handle(List<String> list) {
}
}
static class WithoutInterface {
public void handle(List<String> list) {
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1004,18 +1004,20 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
*/
protected abstract static class PropertyHandler {
@Nullable
private final Class<?> propertyType;
private final boolean readable;
private final boolean writable;
public PropertyHandler(Class<?> propertyType, boolean readable, boolean writable) {
public PropertyHandler(@Nullable Class<?> propertyType, boolean readable, boolean writable) {
this.propertyType = propertyType;
this.readable = readable;
this.writable = writable;
}
@Nullable
public Class<?> getPropertyType() {
return this.propertyType;
}
@@ -23,6 +23,7 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.net.URI;
import java.net.URL;
import java.time.temporal.Temporal;
@@ -30,6 +31,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -787,38 +789,28 @@ public abstract class BeanUtils {
if (editable != null) {
if (!editable.isInstance(target)) {
throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
"] not assignable to Editable class [" + editable.getName() + "]");
"] not assignable to editable class [" + editable.getName() + "]");
}
actualEditable = editable;
}
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
Set<String> ignoredProps = (ignoreProperties != null ? new HashSet<>(Arrays.asList(ignoreProperties)) : null);
CachedIntrospectionResults sourceResults = (actualEditable != source.getClass() ?
CachedIntrospectionResults.forClass(source.getClass()) : null);
for (PropertyDescriptor targetPd : targetPds) {
Method writeMethod = targetPd.getWriteMethod();
if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
if (writeMethod != null && (ignoredProps == null || !ignoredProps.contains(targetPd.getName()))) {
PropertyDescriptor sourcePd = (sourceResults != null ?
sourceResults.getPropertyDescriptor(targetPd.getName()) : targetPd);
if (sourcePd != null) {
Method readMethod = sourcePd.getReadMethod();
if (readMethod != null) {
ResolvableType sourceResolvableType = ResolvableType.forMethodReturnType(readMethod);
ResolvableType targetResolvableType = ResolvableType.forMethodParameter(writeMethod, 0);
// Ignore generic types in assignable check if either ResolvableType has unresolvable generics.
boolean isAssignable =
(sourceResolvableType.hasUnresolvableGenerics() || targetResolvableType.hasUnresolvableGenerics() ?
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()) :
targetResolvableType.isAssignableFrom(sourceResolvableType));
if (isAssignable) {
if (isAssignable(writeMethod, readMethod)) {
try {
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
readMethod.setAccessible(true);
}
ReflectionUtils.makeAccessible(readMethod);
Object value = readMethod.invoke(source);
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
ReflectionUtils.makeAccessible(writeMethod);
writeMethod.invoke(target, value);
}
catch (Throwable ex) {
@@ -832,6 +824,24 @@ public abstract class BeanUtils {
}
}
private static boolean isAssignable(Method writeMethod, Method readMethod) {
Type paramType = writeMethod.getGenericParameterTypes()[0];
if (paramType instanceof Class) {
return ClassUtils.isAssignable((Class<?>) paramType, readMethod.getReturnType());
}
else if (paramType.equals(readMethod.getGenericReturnType())) {
return true;
}
else {
ResolvableType sourceType = ResolvableType.forMethodReturnType(readMethod);
ResolvableType targetType = ResolvableType.forMethodParameter(writeMethod, 0);
// Ignore generic types in assignable check if either ResolvableType has unresolvable generics.
return (sourceType.hasUnresolvableGenerics() || targetType.hasUnresolvableGenerics() ?
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()) :
targetType.isAssignableFrom(sourceType));
}
}
/**
* Inner class to avoid a hard dependency on Kotlin at runtime.
@@ -892,7 +902,6 @@ public abstract class BeanUtils {
}
return kotlinConstructor.callBy(argParameters);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -129,7 +129,6 @@ public class DirectFieldAccessor extends AbstractNestablePropertyAccessor {
ReflectionUtils.makeAccessible(this.field);
return this.field.get(getWrappedInstance());
}
catch (IllegalAccessException ex) {
throw new InvalidPropertyException(getWrappedClass(),
this.field.getName(), "Field is not accessible", ex);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -292,7 +292,7 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
if (actualValue != null) {
actualValue = typeConverter.convertIfNecessary(actualValue, expectedValue.getClass());
}
if (!expectedValue.equals(actualValue)) {
if (!ObjectUtils.nullSafeEquals(expectedValue, actualValue)) {
return false;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -97,10 +97,10 @@ import org.springframework.util.StringUtils;
* Supports autowiring constructors, properties by name, and properties by type.
*
* <p>The main template method to be implemented by subclasses is
* {@link #resolveDependency(DependencyDescriptor, String, Set, TypeConverter)},
* used for autowiring by type. In case of a factory which is capable of searching
* its bean definitions, matching beans will typically be implemented through such
* a search. For other factory styles, simplified matching algorithms can be implemented.
* {@link #resolveDependency(DependencyDescriptor, String, Set, TypeConverter)}, used for
* autowiring. In case of a {@link org.springframework.beans.factory.ListableBeanFactory}
* which is capable of searching its bean definitions, matching beans will typically be
* implemented through such a search. Otherwise, simplified matching can be implemented.
*
* <p>Note that this class does <i>not</i> assume or implement bean definition
* registry capabilities. See {@link DefaultListableBeanFactory} for an implementation
@@ -675,7 +675,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
// Apply SmartInstantiationAwareBeanPostProcessors to predict the
// eventual type after a before-instantiation shortcut.
if (targetType != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
boolean matchingOnlyFactoryBean = typesToMatch.length == 1 && typesToMatch[0] == FactoryBean.class;
boolean matchingOnlyFactoryBean = (typesToMatch.length == 1 && typesToMatch[0] == FactoryBean.class);
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
Class<?> predicted = bp.predictBeanType(targetType, beanName);
if (predicted != null &&
@@ -835,11 +835,11 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
/**
* This implementation attempts to query the FactoryBean's generic parameter metadata
* if present to determine the object type. If not present, i.e. the FactoryBean is
* declared as a raw type, checks the FactoryBean's {@code getObjectType} method
* declared as a raw type, it checks the FactoryBean's {@code getObjectType} method
* on a plain instance of the FactoryBean, without bean properties applied yet.
* If this doesn't return a type yet, and {@code allowInit} is {@code true} a
* full creation of the FactoryBean is used as fallback (through delegation to the
* superclass's implementation).
* If this doesn't return a type yet and {@code allowInit} is {@code true}, full
* creation of the FactoryBean is attempted as fallback (through delegation to the
* superclass implementation).
* <p>The shortcut check for a FactoryBean is only applied in case of a singleton
* FactoryBean. If the FactoryBean instance itself is not kept as singleton,
* it will be fully created to check the type of its exposed object.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -166,6 +166,9 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
/** Map from scope identifier String to corresponding Scope. */
private final Map<String, Scope> scopes = new LinkedHashMap<>(8);
/** Application startup metrics. **/
private ApplicationStartup applicationStartup = ApplicationStartup.DEFAULT;
/** Security context used when running with a SecurityManager. */
@Nullable
private SecurityContextProvider securityContextProvider;
@@ -180,8 +183,6 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
private final ThreadLocal<Object> prototypesCurrentlyInCreation =
new NamedThreadLocal<>("Prototype beans currently in creation");
/** Application startup metrics. **/
private ApplicationStartup applicationStartup = ApplicationStartup.DEFAULT;
/**
* Create a new AbstractBeanFactory.
@@ -749,7 +750,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
aliases.add(fullBeanName);
}
String[] retrievedAliases = super.getAliases(beanName);
String prefix = factoryPrefix ? FACTORY_BEAN_PREFIX : "";
String prefix = (factoryPrefix ? FACTORY_BEAN_PREFIX : "");
for (String retrievedAlias : retrievedAliases) {
String alias = prefix + retrievedAlias;
if (!alias.equals(name)) {
@@ -1079,7 +1080,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
@Override
public void setApplicationStartup(ApplicationStartup applicationStartup) {
Assert.notNull(applicationStartup, "applicationStartup should not be null");
Assert.notNull(applicationStartup, "ApplicationStartup must not be null");
this.applicationStartup = applicationStartup;
}
@@ -1694,7 +1695,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
* already. The implementation is allowed to instantiate the target factory bean if
* {@code allowInit} is {@code true} and the type cannot be determined another way;
* otherwise it is restricted to introspecting signatures and related metadata.
* <p>If no {@link FactoryBean#OBJECT_TYPE_ATTRIBUTE} if set on the bean definition
* <p>If no {@link FactoryBean#OBJECT_TYPE_ATTRIBUTE} is set on the bean definition
* and {@code allowInit} is {@code true}, the default implementation will create
* the FactoryBean via {@code getBean} to call its {@code getObjectType} method.
* Subclasses are encouraged to optimize this, typically by inspecting the generic
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -107,11 +107,7 @@ class ConstructorResolver {
/**
* "autowire constructor" (with constructor arguments by type) behavior.
* Also applied if explicit constructor argument values are specified,
* matching all remaining arguments with beans from the bean factory.
* <p>This corresponds to constructor injection: In this mode, a Spring
* bean factory is able to host components that expect constructor-based
* dependency resolution.
* Also applied if explicit constructor argument values are specified.
* @param beanName the name of the bean
* @param mbd the merged bean definition for the bean
* @param chosenCtors chosen candidate constructors (or {@code null} if none)
@@ -604,13 +600,10 @@ class ConstructorResolver {
String argDesc = StringUtils.collectionToCommaDelimitedString(argTypes);
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"No matching factory method found on class [" + factoryClass.getName() + "]: " +
(mbd.getFactoryBeanName() != null ?
"factory bean '" + mbd.getFactoryBeanName() + "'; " : "") +
(mbd.getFactoryBeanName() != null ? "factory bean '" + mbd.getFactoryBeanName() + "'; " : "") +
"factory method '" + mbd.getFactoryMethodName() + "(" + argDesc + ")'. " +
"Check that a method with the specified name " +
(minNrOfArgs > 0 ? "and arguments " : "") +
"exists and that it is " +
(isStatic ? "static" : "non-static") + ".");
"Check that a method with the specified name " + (minNrOfArgs > 0 ? "and arguments " : "") +
"exists and that it is " + (isStatic ? "static" : "non-static") + ".");
}
else if (void.class == factoryMethodToUse.getReturnType()) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -567,16 +567,16 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
*/
protected void destroyBean(String beanName, @Nullable DisposableBean bean) {
// Trigger destruction of dependent beans first...
Set<String> dependencies;
Set<String> dependentBeanNames;
synchronized (this.dependentBeanMap) {
// Within full synchronization in order to guarantee a disconnected Set
dependencies = this.dependentBeanMap.remove(beanName);
dependentBeanNames = this.dependentBeanMap.remove(beanName);
}
if (dependencies != null) {
if (dependentBeanNames != null) {
if (logger.isTraceEnabled()) {
logger.trace("Retrieved dependent beans for bean '" + beanName + "': " + dependencies);
logger.trace("Retrieved dependent beans for bean '" + beanName + "': " + dependentBeanNames);
}
for (String dependentBeanName : dependencies) {
for (String dependentBeanName : dependentBeanNames) {
destroySingleton(dependentBeanName);
}
}
@@ -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.
@@ -78,8 +78,10 @@ public class PathEditor extends PropertyEditorSupport {
if (nioPathCandidate && !text.startsWith("/")) {
try {
URI uri = new URI(text);
if (uri.getScheme() != null) {
nioPathCandidate = false;
String scheme = uri.getScheme();
if (scheme != null) {
// No NIO candidate except for "C:" style drive letters
nioPathCandidate = (scheme.length() == 1);
// Let's try NIO file system providers via Paths.get(URI)
setValue(Paths.get(uri).normalize());
return;
@@ -109,7 +111,8 @@ public class PathEditor extends PropertyEditorSupport {
setValue(resource.getFile().toPath());
}
catch (IOException ex) {
throw new IllegalArgumentException("Failed to retrieve file for " + resource, ex);
throw new IllegalArgumentException(
"Could not retrieve file for " + resource + ": " + ex.getMessage());
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -33,6 +33,10 @@ public interface ITestBean extends AgeHolder {
void setName(String name);
default void applyName(Object name) {
setName(String.valueOf(name));
}
ITestBean getSpouse();
void setSpouse(ITestBean spouse);
@@ -236,7 +236,7 @@ public class CaffeineCacheManager implements CacheManager {
* Build a common {@link CaffeineCache} instance for the specified cache name,
* using the common Caffeine configuration specified on this cache manager.
* <p>Delegates to {@link #adaptCaffeineCache} as the adaptation method to
* Spring's cache abstraction (allowing for centralized decoration etc),
* Spring's cache abstraction (allowing for centralized decoration etc.),
* passing in a freshly built native Caffeine Cache instance.
* @param name the name of the cache
* @return the Spring CaffeineCache adapter (or a decorator thereof)
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,6 +32,7 @@ import java.lang.annotation.Target;
* @author Stephane Nicoll
* @author Sam Brannen
* @since 4.1
* @see Cacheable
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@@ -42,8 +43,10 @@ public @interface CacheConfig {
* Names of the default caches to consider for caching operations defined
* in the annotated class.
* <p>If none is set at the operation level, these are used instead of the default.
* <p>May be used to determine the target cache (or caches), matching the
* qualifier value or the bean names of a specific bean definition.
* <p>Names may be used to determine the target cache(s), to be resolved via the
* configured {@link #cacheResolver()} which typically delegates to
* {@link org.springframework.cache.CacheManager#getCache}.
* For further details see {@link Cacheable#cacheNames()}.
*/
String[] cacheNames() default {};
@@ -68,8 +68,12 @@ public @interface Cacheable {
/**
* Names of the caches in which method invocation results are stored.
* <p>Names may be used to determine the target cache (or caches), matching
* the qualifier value or bean name of a specific bean definition.
* <p>Names may be used to determine the target cache(s), to be resolved via the
* configured {@link #cacheResolver()} which typically delegates to
* {@link org.springframework.cache.CacheManager#getCache}.
* <p>This will usually be a single cache name. If multiple names are specified,
* they will be consulted for a cache hit in the order of definition, and they
* will all receive a put/evict request for the same newly cached value.
* @since 4.2
* @see #value
* @see CacheConfig#cacheNames
@@ -547,10 +547,10 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
}
/**
* Collect the {@link CachePutRequest} for all {@link CacheOperation} using
* the specified result value.
* Collect a {@link CachePutRequest} for every {@link CacheOperation}
* using the specified result value.
* @param contexts the contexts to handle
* @param result the result value (never {@code null})
* @param result the result value
* @param putRequests the collection to update
*/
private void collectPutRequests(Collection<CacheOperationContext> contexts,
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,7 +22,7 @@ import org.springframework.lang.Nullable;
* Abstract the invocation of a cache operation.
*
* <p>Does not provide a way to transmit checked exceptions but
* provide a special exception that should be used to wrap any
* provides a special exception that should be used to wrap any
* exception that was thrown by the underlying invocation.
* Callers are expected to handle this issue type specifically.
*
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -121,7 +121,7 @@ public interface SmartLifecycle extends Lifecycle, Phased {
/**
* Return the phase that this lifecycle object is supposed to run in.
* <p>The default implementation returns {@link #DEFAULT_PHASE} in order to
* let {@code stop()} callbacks execute after regular {@code Lifecycle}
* let {@code stop()} callbacks execute before regular {@code Lifecycle}
* implementations.
* @see #isAutoStartup()
* @see #start()
@@ -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.
@@ -62,11 +62,13 @@ import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* A component provider that provides candidate components from a base package. Can
* use {@link CandidateComponentsIndex the index} if it is available of scans the
* classpath otherwise. Candidate components are identified by applying exclude and
* include filters. {@link AnnotationTypeFilter}, {@link AssignableTypeFilter} include
* filters on an annotation/superclass that are annotated with {@link Indexed} are
* A component provider that scans for candidate components starting from a
* specified base package. Can use the {@linkplain CandidateComponentsIndex component
* index}, if it is available, and scans the classpath otherwise.
*
* <p>Candidate components are identified by applying exclude and include filters.
* {@link AnnotationTypeFilter} and {@link AssignableTypeFilter} include filters
* for an annotation/target-type that is annotated with {@link Indexed} are
* supported: if any other include filter is specified, the index is ignored and
* classpath scanning is used instead.
*
@@ -304,7 +306,7 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
/**
* Scan the class path for candidate components.
* Scan the component index or class path for candidate components.
* @param basePackage the package to check for annotated classes
* @return a corresponding Set of autodetected bean definitions
*/
@@ -318,7 +320,7 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
}
/**
* Determine if the index can be used by this instance.
* Determine if the component index can be used by this instance.
* @return {@code true} if the index is available and the configuration of this
* instance is supported by it, {@code false} otherwise
* @since 5.0
@@ -454,8 +456,7 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
}
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
throw new BeanDefinitionStoreException("Failed to read candidate component class: " + resource, ex);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -402,8 +402,8 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
return;
}
if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (webServiceRefClass != null && bridgedMethod.isAnnotationPresent(webServiceRefClass)) {
if (webServiceRefClass != null && bridgedMethod.isAnnotationPresent(webServiceRefClass)) {
if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (Modifier.isStatic(method.getModifiers())) {
throw new IllegalStateException("@WebServiceRef annotation is not supported on static methods");
}
@@ -413,7 +413,9 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new WebServiceRefElement(method, bridgedMethod, pd));
}
else if (ejbClass != null && bridgedMethod.isAnnotationPresent(ejbClass)) {
}
else if (ejbClass != null && bridgedMethod.isAnnotationPresent(ejbClass)) {
if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (Modifier.isStatic(method.getModifiers())) {
throw new IllegalStateException("@EJB annotation is not supported on static methods");
}
@@ -423,7 +425,9 @@ 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 (bridgedMethod.isAnnotationPresent(Resource.class)) {
if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (Modifier.isStatic(method.getModifiers())) {
throw new IllegalStateException("@Resource annotation is not supported on static methods");
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -104,8 +104,8 @@ import org.springframework.stereotype.Component;
*
* }</pre>
*
* <p>{@code @Configuration} classes may not only be bootstrapped using
* component scanning, but may also themselves <em>configure</em> component scanning using
* <p>{@code @Configuration} classes may not only be bootstrapped using component
* scanning, but may also themselves <em>configure</em> component scanning using
* the {@link ComponentScan @ComponentScan} annotation:
*
* <pre class="code">
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -72,7 +72,6 @@ final class ConfigurationClass {
* Create a new {@link ConfigurationClass} with the given name.
* @param metadataReader reader used to parse the underlying {@link Class}
* @param beanName must not be {@code null}
* @see ConfigurationClass#ConfigurationClass(Class, ConfigurationClass)
*/
ConfigurationClass(MetadataReader metadataReader, String beanName) {
Assert.notNull(beanName, "Bean name must not be null");
@@ -86,10 +85,10 @@ final class ConfigurationClass {
* using the {@link Import} annotation or automatically processed as a nested
* configuration class (if importedBy is not {@code null}).
* @param metadataReader reader used to parse the underlying {@link Class}
* @param importedBy the configuration class importing this one or {@code null}
* @param importedBy the configuration class importing this one
* @since 3.1.1
*/
ConfigurationClass(MetadataReader metadataReader, @Nullable ConfigurationClass importedBy) {
ConfigurationClass(MetadataReader metadataReader, ConfigurationClass importedBy) {
this.metadata = metadataReader.getAnnotationMetadata();
this.resource = metadataReader.getResource();
this.importedBy.add(importedBy);
@@ -99,7 +98,6 @@ final class ConfigurationClass {
* Create a new {@link ConfigurationClass} with the given name.
* @param clazz the underlying {@link Class} to represent
* @param beanName name of the {@code @Configuration} class bean
* @see ConfigurationClass#ConfigurationClass(Class, ConfigurationClass)
*/
ConfigurationClass(Class<?> clazz, String beanName) {
Assert.notNull(beanName, "Bean name must not be null");
@@ -113,10 +111,10 @@ final class ConfigurationClass {
* using the {@link Import} annotation or automatically processed as a nested
* configuration class (if imported is {@code true}).
* @param clazz the underlying {@link Class} to represent
* @param importedBy the configuration class importing this one (or {@code null})
* @param importedBy the configuration class importing this one
* @since 3.1.1
*/
ConfigurationClass(Class<?> clazz, @Nullable ConfigurationClass importedBy) {
ConfigurationClass(Class<?> clazz, ConfigurationClass importedBy) {
this.metadata = AnnotationMetadata.introspect(clazz);
this.resource = new DescriptiveResource(clazz.getName());
this.importedBy.add(importedBy);
@@ -126,7 +124,6 @@ final class ConfigurationClass {
* Create a new {@link ConfigurationClass} with the given name.
* @param metadata the metadata for the underlying class to represent
* @param beanName name of the {@code @Configuration} class bean
* @see ConfigurationClass#ConfigurationClass(Class, ConfigurationClass)
*/
ConfigurationClass(AnnotationMetadata metadata, String beanName) {
Assert.notNull(beanName, "Bean name must not be null");
@@ -148,12 +145,12 @@ final class ConfigurationClass {
return ClassUtils.getShortName(getMetadata().getClassName());
}
void setBeanName(String beanName) {
void setBeanName(@Nullable String beanName) {
this.beanName = beanName;
}
@Nullable
public String getBeanName() {
String getBeanName() {
return this.beanName;
}
@@ -163,7 +160,7 @@ final class ConfigurationClass {
* @since 3.1.1
* @see #getImportedBy()
*/
public boolean isImported() {
boolean isImported() {
return !this.importedBy.isEmpty();
}
@@ -197,6 +194,10 @@ final class ConfigurationClass {
this.importedResources.put(importedResource, readerClass);
}
Map<String, Class<? extends BeanDefinitionReader>> getImportedResources() {
return this.importedResources;
}
void addImportBeanDefinitionRegistrar(ImportBeanDefinitionRegistrar registrar, AnnotationMetadata importingClassMetadata) {
this.importBeanDefinitionRegistrars.put(registrar, importingClassMetadata);
}
@@ -205,10 +206,6 @@ final class ConfigurationClass {
return this.importBeanDefinitionRegistrars;
}
Map<String, Class<? extends BeanDefinitionReader>> getImportedResources() {
return this.importedResources;
}
void validate(ProblemReporter problemReporter) {
// A configuration class may not be final (CGLIB limitation) unless it declares proxyBeanMethods=false
Map<String, Object> attributes = this.metadata.getAnnotationAttributes(Configuration.class.getName());
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -137,31 +137,40 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext {
/**
* Name of the MessageSource bean in the factory.
* The name of the {@link MessageSource} bean in the context.
* If none is supplied, message resolution is delegated to the parent.
* @see MessageSource
* @see org.springframework.context.MessageSource
* @see org.springframework.context.support.ResourceBundleMessageSource
* @see org.springframework.context.support.ReloadableResourceBundleMessageSource
* @see #getMessage(MessageSourceResolvable, Locale)
*/
public static final String MESSAGE_SOURCE_BEAN_NAME = "messageSource";
/**
* Name of the LifecycleProcessor bean in the factory.
* If none is supplied, a DefaultLifecycleProcessor is used.
* @see org.springframework.context.LifecycleProcessor
* @see org.springframework.context.support.DefaultLifecycleProcessor
*/
public static final String LIFECYCLE_PROCESSOR_BEAN_NAME = "lifecycleProcessor";
/**
* Name of the ApplicationEventMulticaster bean in the factory.
* If none is supplied, a default SimpleApplicationEventMulticaster is used.
* The name of the {@link ApplicationEventMulticaster} bean in the context.
* If none is supplied, a {@link SimpleApplicationEventMulticaster} is used.
* @see org.springframework.context.event.ApplicationEventMulticaster
* @see org.springframework.context.event.SimpleApplicationEventMulticaster
* @see #publishEvent(ApplicationEvent)
* @see #addApplicationListener(ApplicationListener)
*/
public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";
/**
* Boolean flag controlled by a {@code spring.spel.ignore} system property that instructs Spring to
* ignore SpEL, i.e. to not initialize the SpEL infrastructure.
* The name of the {@link LifecycleProcessor} bean in the context.
* If none is supplied, a {@link DefaultLifecycleProcessor} is used.
* @since 3.0
* @see org.springframework.context.LifecycleProcessor
* @see org.springframework.context.support.DefaultLifecycleProcessor
* @see #start()
* @see #stop()
*/
public static final String LIFECYCLE_PROCESSOR_BEAN_NAME = "lifecycleProcessor";
/**
* Boolean flag controlled by a {@code spring.spel.ignore} system property that
* instructs Spring to ignore SpEL, i.e. to not initialize the SpEL infrastructure.
* <p>The default is "false".
*/
private static final boolean shouldIgnoreSpel = SpringProperties.getFlag("spring.spel.ignore");
@@ -203,7 +212,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
/** Flag that indicates whether this context has been closed already. */
private final AtomicBoolean closed = new AtomicBoolean();
/** Synchronization monitor for the "refresh" and "destroy". */
/** Synchronization monitor for "refresh" and "close". */
private final Object startupShutdownMonitor = new Object();
/** Reference to the JVM shutdown hook, if registered. */
@@ -211,7 +220,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
private Thread shutdownHook;
/** ResourcePatternResolver used by this context. */
private ResourcePatternResolver resourcePatternResolver;
private final ResourcePatternResolver resourcePatternResolver;
/** LifecycleProcessor for managing the lifecycle of beans within this context. */
@Nullable
@@ -562,7 +571,6 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
@@ -766,8 +774,9 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
}
/**
* Initialize the MessageSource.
* Use parent's if none defined in this context.
* Initialize the {@link MessageSource}.
* <p>Uses parent's {@code MessageSource} if none defined in this context.
* @see #MESSAGE_SOURCE_BEAN_NAME
*/
protected void initMessageSource() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
@@ -799,8 +808,9 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
}
/**
* Initialize the ApplicationEventMulticaster.
* Uses SimpleApplicationEventMulticaster if none defined in the context.
* Initialize the {@link ApplicationEventMulticaster}.
* <p>Uses {@link SimpleApplicationEventMulticaster} if none defined in the context.
* @see #APPLICATION_EVENT_MULTICASTER_BEAN_NAME
* @see org.springframework.context.event.SimpleApplicationEventMulticaster
*/
protected void initApplicationEventMulticaster() {
@@ -823,15 +833,16 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
}
/**
* Initialize the LifecycleProcessor.
* Uses DefaultLifecycleProcessor if none defined in the context.
* Initialize the {@link LifecycleProcessor}.
* <p>Uses {@link DefaultLifecycleProcessor} if none defined in the context.
* @since 3.0
* @see #LIFECYCLE_PROCESSOR_BEAN_NAME
* @see org.springframework.context.support.DefaultLifecycleProcessor
*/
protected void initLifecycleProcessor() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (beanFactory.containsLocalBean(LIFECYCLE_PROCESSOR_BEAN_NAME)) {
this.lifecycleProcessor =
beanFactory.getBean(LIFECYCLE_PROCESSOR_BEAN_NAME, LifecycleProcessor.class);
this.lifecycleProcessor = beanFactory.getBean(LIFECYCLE_PROCESSOR_BEAN_NAME, LifecycleProcessor.class);
if (logger.isTraceEnabled()) {
logger.trace("Using LifecycleProcessor [" + this.lifecycleProcessor + "]");
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -126,6 +126,7 @@ public abstract class AbstractRefreshableApplicationContext extends AbstractAppl
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
beanFactory.setApplicationStartup(getApplicationStartup());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
this.beanFactory = beanFactory;
@@ -55,7 +55,7 @@ class ContextTypeMatchClassLoader extends DecoratingClassLoader implements Smart
// 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;
Method method;
try {
method = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class);
ReflectionUtils.makeAccessible(method);
@@ -63,6 +63,7 @@ class ContextTypeMatchClassLoader extends DecoratingClassLoader implements Smart
catch (Throwable ex) {
// Typically a JDK 9+ InaccessibleObjectException...
// Avoid through JVM startup with --add-opens=java.base/java.lang=ALL-UNNAMED
method = null;
LogFactory.getLog(ContextTypeMatchClassLoader.class).debug(
"ClassLoader.findLoadedClass not accessible -> will always override requested class", ex);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@ import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.springframework.context.ResourceLoaderAware;
@@ -143,7 +144,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractResourceBased
/**
* Set the PropertiesPersister to use for parsing properties files.
* <p>The default is ResourcePropertiesPersister.
* <p>The default is {@code ResourcePropertiesPersister}.
* @see ResourcePropertiesPersister#INSTANCE
*/
public void setPropertiesPersister(@Nullable PropertiesPersister propertiesPersister) {
@@ -401,7 +402,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractResourceBased
/**
* Refresh the PropertiesHolder for the given bundle filename.
* The holder can be {@code null} if not cached before, or a timed-out cache entry
* <p>The holder can be {@code null} if not cached before, or a timed-out cache entry
* (potentially getting re-validated against the current last-modified timestamp).
* @param filename the bundle filename (basename + Locale)
* @param propHolder the current PropertiesHolder for the bundle
@@ -562,7 +563,7 @@ public class ReloadableResourceBundleMessageSource extends AbstractResourceBased
private volatile long refreshTimestamp = -2;
private final ReentrantLock refreshLock = new ReentrantLock();
private final Lock refreshLock = new ReentrantLock();
/** Cache to hold already generated MessageFormats per message code. */
private final ConcurrentMap<String, Map<Locale, MessageFormat>> cachedMessageFormats =
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@ import org.springframework.lang.Nullable;
* {@link Runnable Runnables} based on different kinds of triggers.
*
* <p>This interface is separate from {@link SchedulingTaskExecutor} since it
* usually represents for a different kind of backend, i.e. a thread pool with
* usually represents a different kind of backend, i.e. a thread pool with
* different characteristics and capabilities. Implementations may implement
* both interfaces if they can handle both kinds of execution characteristics.
*
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -100,9 +100,9 @@ public @interface Scheduled {
/**
* A time zone for which the cron expression will be resolved. By default, this
* attribute is the empty String (i.e. the server's local time zone will be used).
* attribute is the empty String (i.e. the scheduler's time zone will be used).
* @return a zone id accepted by {@link java.util.TimeZone#getTimeZone(String)},
* or an empty String to indicate the server's default time zone
* or an empty String to indicate the scheduler's default time zone
* @since 4.0
* @see org.springframework.scheduling.support.CronTrigger#CronTrigger(String, java.util.TimeZone)
* @see java.util.TimeZone
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,7 +27,6 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@@ -84,7 +83,7 @@ import org.springframework.util.StringValueResolver;
* "fixedRate", "fixedDelay", or "cron" expression provided via the annotation.
*
* <p>This post-processor is automatically registered by Spring's
* {@code <task:annotation-driven>} XML element, and also by the
* {@code <task:annotation-driven>} XML element and also by the
* {@link EnableScheduling @EnableScheduling} annotation.
*
* <p>Autodetects any {@link SchedulingConfigurer} instances in the container,
@@ -434,14 +433,14 @@ public class ScheduledAnnotationBeanPostProcessor
Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");
processedSchedule = true;
if (!Scheduled.CRON_DISABLED.equals(cron)) {
TimeZone timeZone;
CronTrigger trigger;
if (StringUtils.hasText(zone)) {
timeZone = StringUtils.parseTimeZoneString(zone);
trigger = new CronTrigger(cron, StringUtils.parseTimeZoneString(zone));
}
else {
timeZone = TimeZone.getDefault();
trigger = new CronTrigger(cron);
}
tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, trigger)));
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -133,11 +133,6 @@ public class ConcurrentTaskExecutor implements AsyncListenableTaskExecutor, Sche
* execution callback (which may be a wrapper around the user-supplied task).
* <p>The primary use case is to set some execution context around the task's
* invocation, or to provide some monitoring/statistics for task execution.
* <p><b>NOTE:</b> Exception handling in {@code TaskDecorator} implementations
* is limited to plain {@code Runnable} execution via {@code execute} calls.
* In case of {@code #submit} calls, the exposed {@code Runnable} will be a
* {@code FutureTask} which does not propagate any exceptions; you might
* have to cast it and call {@code Future#get} to evaluate exceptions.
* @since 4.3
*/
public final void setTaskDecorator(TaskDecorator taskDecorator) {
@@ -178,11 +173,10 @@ public class ConcurrentTaskExecutor implements AsyncListenableTaskExecutor, Sche
}
private TaskExecutorAdapter getAdaptedExecutor(Executor concurrentExecutor) {
if (managedExecutorServiceClass != null && managedExecutorServiceClass.isInstance(concurrentExecutor)) {
return new ManagedTaskExecutorAdapter(concurrentExecutor);
}
TaskExecutorAdapter adapter = new TaskExecutorAdapter(concurrentExecutor);
private TaskExecutorAdapter getAdaptedExecutor(Executor originalExecutor) {
TaskExecutorAdapter adapter =
(managedExecutorServiceClass != null && managedExecutorServiceClass.isInstance(originalExecutor) ?
new ManagedTaskExecutorAdapter(originalExecutor) : new TaskExecutorAdapter(originalExecutor));
if (this.taskDecorator != null) {
adapter.setTaskDecorator(this.taskDecorator);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -177,6 +177,7 @@ public class ConcurrentTaskScheduler extends ConcurrentTaskExecutor implements T
* @see Clock#systemDefaultZone()
*/
public void setClock(Clock clock) {
Assert.notNull(clock, "Clock must not be null");
this.clock = clock;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,8 +45,9 @@ import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureTask;
/**
* Implementation of Spring's {@link TaskScheduler} interface, wrapping
* a native {@link java.util.concurrent.ScheduledThreadPoolExecutor}.
* A standard implementation of Spring's {@link TaskScheduler} interface, wrapping
* a native {@link java.util.concurrent.ScheduledThreadPoolExecutor} and providing
* all applicable configuration options for it.
*
* @author Juergen Hoeller
* @author Mark Fisher
@@ -154,6 +155,7 @@ public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport
* @see Clock#systemDefaultZone()
*/
public void setClock(Clock clock) {
Assert.notNull(clock, "Clock must not be null");
this.clock = clock;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,17 +29,15 @@ import org.springframework.util.StringUtils;
* Created using the {@code parse*} methods.
*
* @author Arjen Poutsma
* @author Juergen Hoeller
* @since 5.3
*/
final class BitsCronField extends CronField {
public static final BitsCronField ZERO_NANOS = forZeroNanos();
private static final long MASK = 0xFFFFFFFFFFFFFFFFL;
@Nullable
private static BitsCronField zeroNanos = null;
// we store at most 60 bits, for seconds and minutes, so a 64-bit long suffices
private long bits;
@@ -48,16 +46,14 @@ final class BitsCronField extends CronField {
super(type);
}
/**
* Return a {@code BitsCronField} enabled for 0 nanoseconds.
*/
public static BitsCronField zeroNanos() {
if (zeroNanos == null) {
BitsCronField field = new BitsCronField(Type.NANO);
field.setBit(0);
zeroNanos = field;
}
return zeroNanos;
private static BitsCronField forZeroNanos() {
BitsCronField field = new BitsCronField(Type.NANO);
field.setBit(0);
return field;
}
/**
@@ -108,7 +104,6 @@ final class BitsCronField extends CronField {
return result;
}
private static BitsCronField parseDate(String value, BitsCronField.Type type) {
if (value.equals("?")) {
value = "*";
@@ -174,6 +169,7 @@ final class BitsCronField extends CronField {
}
}
@Nullable
@Override
public <T extends Temporal & Comparable<? super T>> T nextOrSame(T temporal) {
@@ -217,7 +213,6 @@ final class BitsCronField extends CronField {
else {
return -1;
}
}
private void setBits(ValueRange range) {
@@ -247,7 +242,20 @@ final class BitsCronField extends CronField {
}
private void clearBit(int index) {
this.bits &= ~(1L << index);
this.bits &= ~(1L << index);
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof BitsCronField)) {
return false;
}
BitsCronField otherField = (BitsCronField) other;
return (type() == otherField.type() && this.bits == otherField.bits);
}
@Override
@@ -255,18 +263,6 @@ final class BitsCronField extends CronField {
return Long.hashCode(this.bits);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof BitsCronField)) {
return false;
}
BitsCronField other = (BitsCronField) o;
return type() == other.type() && this.bits == other.bits;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder(type().toString());
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,9 +29,14 @@ import org.springframework.util.StringUtils;
* <a href="https://www.manpagez.com/man/5/crontab/">crontab expression</a>
* that can calculate the next time it matches.
*
* <p>{@code CronExpression} instances are created through
* {@link #parse(String)}; the next match is determined with
* {@link #next(Temporal)}.
* <p>{@code CronExpression} instances are created through {@link #parse(String)};
* the next match is determined with {@link #next(Temporal)}.
*
* <p>Supports a Quartz day-of-month/week field with an L/# expression. Follows
* common cron conventions in every other respect, including 0-6 for SUN-SAT
* (plus 7 for SUN as well). Note that Quartz deviates from the day-of-week
* convention in cron through 1-7 for SUN-SAT whereas Spring strictly follows
* cron even in combination with the optional Quartz-specific L/# expressions.
*
* @author Arjen Poutsma
* @since 5.3
@@ -57,18 +62,12 @@ public final class CronExpression {
private final String expression;
private CronExpression(
CronField seconds,
CronField minutes,
CronField hours,
CronField daysOfMonth,
CronField months,
CronField daysOfWeek,
String expression) {
private CronExpression(CronField seconds, CronField minutes, CronField hours,
CronField daysOfMonth, CronField months, CronField daysOfWeek, String expression) {
// reverse order, to make big changes first
// to make sure we end up at 0 nanos, we add an extra field
this.fields = new CronField[]{daysOfWeek, months, daysOfMonth, hours, minutes, seconds, CronField.zeroNanos()};
// Reverse order, to make big changes first.
// To make sure we end up at 0 nanos, we add an extra field.
this.fields = new CronField[] {daysOfWeek, months, daysOfMonth, hours, minutes, seconds, CronField.zeroNanos()};
this.expression = expression;
}
@@ -121,11 +120,8 @@ public final class CronExpression {
* {@code LW}), it means "the last weekday of the month".
* </li>
* <li>
* In the "day of week" field, {@code L} stands for "the last day of the
* week".
* If prefixed by a number or three-letter name (i.e. {@code dL} or
* {@code DDDL}), it means "the last day of week {@code d} (or {@code DDD})
* in the month".
* In the "day of week" field, {@code dL} or {@code DDDL} stands for
* "the last day of week {@code d} (or {@code DDD}) in the month".
* </li>
* </ul>
* </li>
@@ -177,7 +173,7 @@ public final class CronExpression {
* the cron format
*/
public static CronExpression parse(String expression) {
Assert.hasLength(expression, "Expression string must not be empty");
Assert.hasLength(expression, "Expression must not be empty");
expression = resolveMacros(expression);
@@ -270,28 +266,19 @@ public final class CronExpression {
}
@Override
public boolean equals(@Nullable Object other) {
return (this == other || (other instanceof CronExpression &&
Arrays.equals(this.fields, ((CronExpression) other).fields)));
}
@Override
public int hashCode() {
return Arrays.hashCode(this.fields);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o instanceof CronExpression) {
CronExpression other = (CronExpression) o;
return Arrays.equals(this.fields, other.fields);
}
else {
return false;
}
}
/**
* Return the expression string used to create this {@code CronExpression}.
* @return the expression string
*/
@Override
public String toString() {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,17 +29,24 @@ import org.springframework.util.StringUtils;
/**
* Single field in a cron pattern. Created using the {@code parse*} methods,
* main and only entry point is {@link #nextOrSame(Temporal)}.
* the main and only entry point is {@link #nextOrSame(Temporal)}.
*
* <p>Supports a Quartz day-of-month/week field with an L/# expression. Follows
* common cron conventions in every other respect, including 0-6 for SUN-SAT
* (plus 7 for SUN as well). Note that Quartz deviates from the day-of-week
* convention in cron through 1-7 for SUN-SAT whereas Spring strictly follows
* cron even in combination with the optional Quartz-specific L/# expressions.
*
* @author Arjen Poutsma
* @since 5.3
*/
abstract class CronField {
private static final String[] MONTHS = new String[]{"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP",
"OCT", "NOV", "DEC"};
private static final String[] MONTHS = new String[]
{"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
private static final String[] DAYS = new String[]{"MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"};
private static final String[] DAYS = new String[]
{"MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"};
private final Type type;
@@ -48,11 +55,12 @@ abstract class CronField {
this.type = type;
}
/**
* Return a {@code CronField} enabled for 0 nanoseconds.
*/
public static CronField zeroNanos() {
return BitsCronField.zeroNanos();
return BitsCronField.ZERO_NANOS;
}
/**
@@ -169,6 +177,7 @@ abstract class CronField {
* day-of-month, month, day-of-week.
*/
protected enum Type {
NANO(ChronoField.NANO_OF_SECOND, ChronoUnit.SECONDS),
SECOND(ChronoField.SECOND_OF_MINUTE, ChronoUnit.MINUTES, ChronoField.NANO_OF_SECOND),
MINUTE(ChronoField.MINUTE_OF_HOUR, ChronoUnit.HOURS, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND),
@@ -177,21 +186,18 @@ abstract class CronField {
MONTH(ChronoField.MONTH_OF_YEAR, ChronoUnit.YEARS, ChronoField.DAY_OF_MONTH, ChronoField.HOUR_OF_DAY, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND),
DAY_OF_WEEK(ChronoField.DAY_OF_WEEK, ChronoUnit.WEEKS, ChronoField.HOUR_OF_DAY, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE, ChronoField.NANO_OF_SECOND);
private final ChronoField field;
private final ChronoUnit higherOrder;
private final ChronoField[] lowerOrders;
Type(ChronoField field, ChronoUnit higherOrder, ChronoField... lowerOrders) {
this.field = field;
this.higherOrder = higherOrder;
this.lowerOrders = lowerOrders;
}
/**
* Return the value of this type for the given temporal.
* @return the value of this type
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,8 +27,14 @@ import org.springframework.scheduling.TriggerContext;
import org.springframework.util.Assert;
/**
* {@link Trigger} implementation for cron expressions.
* Wraps a {@link CronExpression}.
* {@link Trigger} implementation for cron expressions. Wraps a
* {@link CronExpression} which parses according to common crontab conventions.
*
* <p>Supports a Quartz day-of-month/week field with an L/# expression. Follows
* common cron conventions in every other respect, including 0-6 for SUN-SAT
* (plus 7 for SUN as well). Note that Quartz deviates from the day-of-week
* convention in cron through 1-7 for SUN-SAT whereas Spring strictly follows
* cron even in combination with the optional Quartz-specific L/# expressions.
*
* @author Juergen Hoeller
* @author Arjen Poutsma
@@ -39,6 +45,7 @@ public class CronTrigger implements Trigger {
private final CronExpression expression;
@Nullable
private final ZoneId zoneId;
@@ -48,7 +55,8 @@ public class CronTrigger implements Trigger {
* expression conventions
*/
public CronTrigger(String expression) {
this(expression, ZoneId.systemDefault());
this.expression = CronExpression.parse(expression);
this.zoneId = null;
}
/**
@@ -58,7 +66,9 @@ public class CronTrigger implements Trigger {
* @param timeZone a time zone in which the trigger times will be generated
*/
public CronTrigger(String expression, TimeZone timeZone) {
this(expression, timeZone.toZoneId());
this.expression = CronExpression.parse(expression);
Assert.notNull(timeZone, "TimeZone must not be null");
this.zoneId = timeZone.toZoneId();
}
/**
@@ -70,10 +80,8 @@ public class CronTrigger implements Trigger {
* @see CronExpression#parse(String)
*/
public CronTrigger(String expression, ZoneId zoneId) {
Assert.hasLength(expression, "Expression must not be empty");
Assert.notNull(zoneId, "ZoneId must not be null");
this.expression = CronExpression.parse(expression);
Assert.notNull(zoneId, "ZoneId must not be null");
this.zoneId = zoneId;
}
@@ -94,22 +102,23 @@ public class CronTrigger implements Trigger {
*/
@Override
public Date nextExecutionTime(TriggerContext triggerContext) {
Date date = triggerContext.lastCompletionTime();
if (date != null) {
Date timestamp = triggerContext.lastCompletionTime();
if (timestamp != null) {
Date scheduled = triggerContext.lastScheduledExecutionTime();
if (scheduled != null && date.before(scheduled)) {
if (scheduled != null && timestamp.before(scheduled)) {
// Previous task apparently executed too early...
// Let's simply use the last calculated execution time then,
// in order to prevent accidental re-fires in the same second.
date = scheduled;
timestamp = scheduled;
}
}
else {
date = new Date(triggerContext.getClock().millis());
timestamp = new Date(triggerContext.getClock().millis());
}
ZonedDateTime dateTime = ZonedDateTime.ofInstant(date.toInstant(), this.zoneId);
ZonedDateTime next = this.expression.next(dateTime);
return (next != null ? Date.from(next.toInstant()) : null);
ZoneId zone = (this.zoneId != null ? this.zoneId : triggerContext.getClock().getZone());
ZonedDateTime zonedTimestamp = ZonedDateTime.ofInstant(timestamp.toInstant(), zone);
ZonedDateTime nextTimestamp = this.expression.next(zonedTimestamp);
return (nextTimestamp != null ? Date.from(nextTimestamp.toInstant()) : null);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,10 +29,16 @@ import org.springframework.util.Assert;
/**
* Extension of {@link CronField} for
* <a href="https://www.quartz-scheduler.org>Quartz</a> -specific fields.
* <a href="https://www.quartz-scheduler.org">Quartz</a>-specific fields.
* Created using the {@code parse*} methods, uses a {@link TemporalAdjuster}
* internally.
*
* <p>Supports a Quartz day-of-month/week field with an L/# expression. Follows
* common cron conventions in every other respect, including 0-6 for SUN-SAT
* (plus 7 for SUN as well). Note that Quartz deviates from the day-of-week
* convention in cron through 1-7 for SUN-SAT whereas Spring strictly follows
* cron even in combination with the optional Quartz-specific L/# expressions.
*
* @author Arjen Poutsma
* @since 5.3
*/
@@ -60,16 +66,18 @@ final class QuartzCronField extends CronField {
this.rollForwardType = rollForwardType;
}
/**
* Returns whether the given value is a Quartz day-of-month field.
* Determine whether the given value is a Quartz day-of-month field.
*/
public static boolean isQuartzDaysOfMonthField(String value) {
return value.contains("L") || value.contains("W");
}
/**
* Parse the given value into a days of months {@code QuartzCronField}, the fourth entry of a cron expression.
* Expects a "L" or "W" in the given value.
* Parse the given value into a days of months {@code QuartzCronField},
* the fourth entry of a cron expression.
* <p>Expects a "L" or "W" in the given value.
*/
public static QuartzCronField parseDaysOfMonth(String value) {
int idx = value.lastIndexOf('L');
@@ -78,14 +86,14 @@ final class QuartzCronField extends CronField {
if (idx != 0) {
throw new IllegalArgumentException("Unrecognized characters before 'L' in '" + value + "'");
}
else if (value.length() == 2 && value.charAt(1) == 'W') { // "LW"
else if (value.length() == 2 && value.charAt(1) == 'W') { // "LW"
adjuster = lastWeekdayOfMonth();
}
else {
if (value.length() == 1) { // "L"
if (value.length() == 1) { // "L"
adjuster = lastDayOfMonth();
}
else { // "L-[0-9]+"
else { // "L-[0-9]+"
int offset = Integer.parseInt(value.substring(idx + 1));
if (offset >= 0) {
throw new IllegalArgumentException("Offset '" + offset + " should be < 0 '" + value + "'");
@@ -103,7 +111,7 @@ final class QuartzCronField extends CronField {
else if (idx != value.length() - 1) {
throw new IllegalArgumentException("Unrecognized characters after 'W' in '" + value + "'");
}
else { // "[0-9]+W"
else { // "[0-9]+W"
int dayOfMonth = Integer.parseInt(value.substring(0, idx));
dayOfMonth = Type.DAY_OF_MONTH.checkValidValue(dayOfMonth);
TemporalAdjuster adjuster = weekdayNearestTo(dayOfMonth);
@@ -114,15 +122,16 @@ final class QuartzCronField extends CronField {
}
/**
* Returns whether the given value is a Quartz day-of-week field.
* Determine whether the given value is a Quartz day-of-week field.
*/
public static boolean isQuartzDaysOfWeekField(String value) {
return value.contains("L") || value.contains("#");
}
/**
* Parse the given value into a days of week {@code QuartzCronField}, the sixth entry of a cron expression.
* Expects a "L" or "#" in the given value.
* Parse the given value into a days of week {@code QuartzCronField},
* the sixth entry of a cron expression.
* <p>Expects a "L" or "#" in the given value.
*/
public static QuartzCronField parseDaysOfWeek(String value) {
int idx = value.lastIndexOf('L');
@@ -135,7 +144,7 @@ final class QuartzCronField extends CronField {
if (idx == 0) {
throw new IllegalArgumentException("No day-of-week before 'L' in '" + value + "'");
}
else { // "[0-7]L"
else { // "[0-7]L"
DayOfWeek dayOfWeek = parseDayOfWeek(value.substring(0, idx));
adjuster = lastInMonth(dayOfWeek);
}
@@ -157,7 +166,6 @@ final class QuartzCronField extends CronField {
throw new IllegalArgumentException("Ordinal '" + ordinal + "' in '" + value +
"' must be positive number ");
}
TemporalAdjuster adjuster = dayOfWeekInMonth(ordinal, dayOfWeek);
return new QuartzCronField(Type.DAY_OF_WEEK, Type.DAY_OF_MONTH, adjuster, value);
}
@@ -167,14 +175,13 @@ final class QuartzCronField extends CronField {
private static DayOfWeek parseDayOfWeek(String value) {
int dayOfWeek = Integer.parseInt(value);
if (dayOfWeek == 0) {
dayOfWeek = 7; // cron is 0 based; java.time 1 based
dayOfWeek = 7; // cron is 0 based; java.time 1 based
}
try {
return DayOfWeek.of(dayOfWeek);
}
catch (DateTimeException ex) {
String msg = ex.getMessage() + " '" + value + "'";
throw new IllegalArgumentException(msg, ex);
throw new IllegalArgumentException(ex.getMessage() + " '" + value + "'", ex);
}
}
@@ -213,10 +220,10 @@ final class QuartzCronField extends CronField {
Temporal lastDom = adjuster.adjustInto(temporal);
Temporal result;
int dow = lastDom.get(ChronoField.DAY_OF_WEEK);
if (dow == 6) { // Saturday
if (dow == 6) { // Saturday
result = lastDom.minus(1, ChronoUnit.DAYS);
}
else if (dow == 7) { // Sunday
else if (dow == 7) { // Sunday
result = lastDom.minus(2, ChronoUnit.DAYS);
}
else {
@@ -227,7 +234,7 @@ final class QuartzCronField extends CronField {
}
/**
* Return a temporal adjuster that finds the nth-to-last day of the month.
* Returns a temporal adjuster that finds the nth-to-last day of the month.
* @param offset the negative offset, i.e. -3 means third-to-last
* @return a nth-to-last day-of-month adjuster
*/
@@ -241,7 +248,7 @@ final class QuartzCronField extends CronField {
}
/**
* Return a temporal adjuster that finds the weekday nearest to the given
* Returns a temporal adjuster that finds the weekday nearest to the given
* day-of-month. If {@code dayOfMonth} falls on a Saturday, the date is
* moved back to Friday; if it falls on a Sunday (or if {@code dayOfMonth}
* is 1 and it falls on a Saturday), it is moved forward to Monday.
@@ -253,10 +260,10 @@ final class QuartzCronField extends CronField {
int current = Type.DAY_OF_MONTH.get(temporal);
DayOfWeek dayOfWeek = DayOfWeek.from(temporal);
if ((current == dayOfMonth && isWeekday(dayOfWeek)) || // dayOfMonth is a weekday
(dayOfWeek == DayOfWeek.FRIDAY && current == dayOfMonth - 1) || // dayOfMonth is a Saturday, so Friday before
(dayOfWeek == DayOfWeek.MONDAY && current == dayOfMonth + 1) || // dayOfMonth is a Sunday, so Monday after
(dayOfWeek == DayOfWeek.MONDAY && dayOfMonth == 1 && current == 3)) { // dayOfMonth is Saturday 1st, so Monday 3rd
if ((current == dayOfMonth && isWeekday(dayOfWeek)) || // dayOfMonth is a weekday
(dayOfWeek == DayOfWeek.FRIDAY && current == dayOfMonth - 1) || // dayOfMonth is a Saturday, so Friday before
(dayOfWeek == DayOfWeek.MONDAY && current == dayOfMonth + 1) || // dayOfMonth is a Sunday, so Monday after
(dayOfWeek == DayOfWeek.MONDAY && dayOfMonth == 1 && current == 3)) { // dayOfMonth is Saturday 1st, so Monday 3rd
return temporal;
}
int count = 0;
@@ -292,7 +299,7 @@ final class QuartzCronField extends CronField {
}
/**
* Return a temporal adjuster that finds the last of the given doy-of-week
* Returns a temporal adjuster that finds the last of the given day-of-week
* in a month.
*/
private static TemporalAdjuster lastInMonth(DayOfWeek dayOfWeek) {
@@ -329,6 +336,7 @@ final class QuartzCronField extends CronField {
}
}
@Override
public <T extends Temporal & Comparable<? super T>> T nextOrSame(T temporal) {
T result = adjust(temporal);
@@ -345,7 +353,6 @@ final class QuartzCronField extends CronField {
return result;
}
@Nullable
@SuppressWarnings("unchecked")
private <T extends Temporal & Comparable<? super T>> T adjust(T temporal) {
@@ -353,28 +360,26 @@ final class QuartzCronField extends CronField {
}
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof QuartzCronField)) {
return false;
}
QuartzCronField otherField = (QuartzCronField) other;
return (type() == otherField.type() && this.value.equals(otherField.value));
}
@Override
public int hashCode() {
return this.value.hashCode();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof QuartzCronField)) {
return false;
}
QuartzCronField other = (QuartzCronField) o;
return type() == other.type() &&
this.value.equals(other.value);
}
@Override
public String toString() {
return type() + " '" + this.value + "'";
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -92,7 +92,7 @@ public class ProxyFactoryBeanTests {
@BeforeEach
public void setUp() throws Exception {
public void setup() throws Exception {
DefaultListableBeanFactory parent = new DefaultListableBeanFactory();
parent.registerBeanDefinition("target2", new RootBeanDefinition(TestApplicationListener.class));
this.factory = new DefaultListableBeanFactory(parent);
@@ -139,22 +139,24 @@ public class ProxyFactoryBeanTests {
private void testDoubleTargetSourceIsRejected(String name) {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(new ClassPathResource(DBL_TARGETSOURCE_CONTEXT, CLASS));
assertThatExceptionOfType(BeanCreationException.class).as("Should not allow TargetSource to be specified in interceptorNames as well as targetSource property")
.isThrownBy(() -> bf.getBean(name))
.havingCause()
.isInstanceOf(AopConfigException.class)
.withMessageContaining("TargetSource");
.isThrownBy(() -> bf.getBean(name))
.havingCause()
.isInstanceOf(AopConfigException.class)
.withMessageContaining("TargetSource");
}
@Test
public void testTargetSourceNotAtEndOfInterceptorNamesIsRejected() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(new ClassPathResource(NOTLAST_TARGETSOURCE_CONTEXT, CLASS));
assertThatExceptionOfType(BeanCreationException.class).as("TargetSource or non-advised object must be last in interceptorNames")
.isThrownBy(() -> bf.getBean("targetSourceNotLast"))
.havingCause()
.isInstanceOf(AopConfigException.class)
.withMessageContaining("interceptorNames");
.isThrownBy(() -> bf.getBean("targetSourceNotLast"))
.havingCause()
.isInstanceOf(AopConfigException.class)
.withMessageContaining("interceptorNames");
}
@Test
@@ -167,11 +169,11 @@ public class ProxyFactoryBeanTests {
assertThat(cba.getCalls()).isEqualTo(0);
ITestBean tb = (ITestBean) bf.getBean("directTarget");
assertThat(tb.getName().equals("Adam")).isTrue();
assertThat(tb.getName()).isEqualTo("Adam");
assertThat(cba.getCalls()).isEqualTo(1);
ProxyFactoryBean pfb = (ProxyFactoryBean) bf.getBean("&directTarget");
assertThat(TestBean.class.isAssignableFrom(pfb.getObjectType())).as("Has correct object type").isTrue();
assertThat(pfb.getObjectType()).isAssignableTo(TestBean.class);
}
@Test
@@ -179,9 +181,9 @@ public class ProxyFactoryBeanTests {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(new ClassPathResource(TARGETSOURCE_CONTEXT, CLASS));
ITestBean tb = (ITestBean) bf.getBean("viaTargetSource");
assertThat(tb.getName().equals("Adam")).isTrue();
assertThat(tb.getName()).isEqualTo("Adam");
ProxyFactoryBean pfb = (ProxyFactoryBean) bf.getBean("&viaTargetSource");
assertThat(TestBean.class.isAssignableFrom(pfb.getObjectType())).as("Has correct object type").isTrue();
assertThat(pfb.getObjectType()).isAssignableTo(TestBean.class);
}
@Test
@@ -190,11 +192,15 @@ public class ProxyFactoryBeanTests {
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(new ClassPathResource(TARGETSOURCE_CONTEXT, CLASS));
ITestBean tb = (ITestBean) bf.getBean("noTarget");
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() ->
tb.getName())
.withMessage("getName");
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(tb::getName).withMessage("getName");
FactoryBean<?> pfb = (ProxyFactoryBean) bf.getBean("&noTarget");
assertThat(ITestBean.class.isAssignableFrom(pfb.getObjectType())).as("Has correct object type").isTrue();
assertThat(pfb.getObjectType()).isAssignableTo(ITestBean.class);
}
@Test
public void testGetObjectTypeOnUninitializedFactoryBean() {
ProxyFactoryBean pfb = new ProxyFactoryBean();
assertThat(pfb.getObjectType()).isNull();
}
/**
@@ -219,20 +225,20 @@ public class ProxyFactoryBeanTests {
pc1.addAdvice(1, di);
assertThat(pc2.getAdvisors()).isEqualTo(pc1.getAdvisors());
assertThat(pc2.getAdvisors().length).as("Now have one more advisor").isEqualTo((oldLength + 1));
assertThat(0).isEqualTo(di.getCount());
assertThat(di.getCount()).isEqualTo(0);
test1.setAge(5);
assertThat(test1.getAge()).isEqualTo(test1_1.getAge());
assertThat(3).isEqualTo(di.getCount());
assertThat(di.getCount()).isEqualTo(3);
}
@Test
public void testPrototypeInstancesAreNotEqual() {
assertThat(ITestBean.class.isAssignableFrom(factory.getType("prototype"))).as("Has correct object type").isTrue();
assertThat(factory.getType("prototype")).isAssignableTo(ITestBean.class);
ITestBean test2 = (ITestBean) factory.getBean("prototype");
ITestBean test2_1 = (ITestBean) factory.getBean("prototype");
assertThat(test2 != test2_1).as("Prototype instances !=").isTrue();
assertThat(test2.equals(test2_1)).as("Prototype instances equal").isTrue();
assertThat(ITestBean.class.isAssignableFrom(factory.getType("prototype"))).as("Has correct object type").isTrue();
assertThat(test2).as("Prototype instances !=").isNotSameAs(test2_1);
assertThat(test2).as("Prototype instances equal").isEqualTo(test2_1);
assertThat(factory.getType("prototype")).isAssignableTo(ITestBean.class);
}
/**
@@ -262,7 +268,7 @@ public class ProxyFactoryBeanTests {
assertThat(prototype2FirstInstance.getCount()).isEqualTo(INITIAL_COUNT + 1);
SideEffectBean prototype2SecondInstance = (SideEffectBean) bf.getBean(beanName);
assertThat(prototype2FirstInstance == prototype2SecondInstance).as("Prototypes are not ==").isFalse();
assertThat(prototype2FirstInstance).as("Prototypes are not ==").isNotSameAs(prototype2SecondInstance);
assertThat(prototype2SecondInstance.getCount()).isEqualTo(INITIAL_COUNT);
assertThat(prototype2FirstInstance.getCount()).isEqualTo(INITIAL_COUNT + 1);
@@ -285,19 +291,19 @@ public class ProxyFactoryBeanTests {
TestBean target = (TestBean) factory.getBean("test");
target.setName(name);
ITestBean autoInvoker = (ITestBean) factory.getBean("autoInvoker");
assertThat(autoInvoker.getName().equals(name)).isTrue();
assertThat(autoInvoker.getName()).isEqualTo(name);
}
@Test
public void testCanGetFactoryReferenceAndManipulate() {
ProxyFactoryBean config = (ProxyFactoryBean) factory.getBean("&test1");
assertThat(ITestBean.class.isAssignableFrom(config.getObjectType())).as("Has correct object type").isTrue();
assertThat(ITestBean.class.isAssignableFrom(factory.getType("test1"))).as("Has correct object type").isTrue();
assertThat(config.getObjectType()).isAssignableTo(ITestBean.class);
assertThat(factory.getType("test1")).isAssignableTo(ITestBean.class);
// Trigger lazy initialization.
config.getObject();
assertThat(config.getAdvisors().length).as("Have one advisors").isEqualTo(1);
assertThat(ITestBean.class.isAssignableFrom(config.getObjectType())).as("Has correct object type").isTrue();
assertThat(ITestBean.class.isAssignableFrom(factory.getType("test1"))).as("Has correct object type").isTrue();
assertThat(config.getObjectType()).isAssignableTo(ITestBean.class);
assertThat(factory.getType("test1")).isAssignableTo(ITestBean.class);
ITestBean tb = (ITestBean) factory.getBean("test1");
// no exception
@@ -308,7 +314,7 @@ public class ProxyFactoryBeanTests {
config.addAdvice(0, (MethodInterceptor) invocation -> {
throw ex;
});
assertThat(config.getAdvisors().length).as("Have correct advisor count").isEqualTo(2);
assertThat(config.getAdvisors()).as("Have correct advisor count").hasSize(2);
ITestBean tb1 = (ITestBean) factory.getBean("test1");
assertThatException()
@@ -348,31 +354,31 @@ public class ProxyFactoryBeanTests {
// Add to head of interceptor chain
int oldCount = config.getAdvisors().length;
config.addAdvisor(0, new DefaultIntroductionAdvisor(ti, TimeStamped.class));
assertThat(config.getAdvisors().length == oldCount + 1).isTrue();
assertThat(config.getAdvisors()).hasSize(oldCount + 1);
TimeStamped ts = (TimeStamped) factory.getBean("test2");
assertThat(ts.getTimeStamp()).isEqualTo(time);
// Can remove
config.removeAdvice(ti);
assertThat(config.getAdvisors().length == oldCount).isTrue();
assertThat(config.getAdvisors()).hasSize(oldCount);
// Check no change on existing object reference
assertThat(ts.getTimeStamp() == time).isTrue();
assertThat(ts.getTimeStamp()).isEqualTo(time);
assertThat(factory.getBean("test2")).as("Should no longer implement TimeStamped")
.isNotInstanceOf(TimeStamped.class);
// Now check non-effect of removing interceptor that isn't there
config.removeAdvice(new DebugInterceptor());
assertThat(config.getAdvisors().length == oldCount).isTrue();
assertThat(config.getAdvisors()).hasSize(oldCount);
ITestBean it = (ITestBean) ts;
DebugInterceptor debugInterceptor = new DebugInterceptor();
config.addAdvice(0, debugInterceptor);
it.getSpouse();
// Won't affect existing reference
assertThat(debugInterceptor.getCount() == 0).isTrue();
assertThat(debugInterceptor.getCount()).isEqualTo(0);
it = (ITestBean) factory.getBean("test2");
it.getSpouse();
assertThat(debugInterceptor.getCount()).isEqualTo(1);
@@ -412,16 +418,16 @@ public class ProxyFactoryBeanTests {
public void testMethodPointcuts() {
ITestBean tb = (ITestBean) factory.getBean("pointcuts");
PointcutForVoid.reset();
assertThat(PointcutForVoid.methodNames.isEmpty()).as("No methods intercepted").isTrue();
assertThat(PointcutForVoid.methodNames).as("No methods intercepted").isEmpty();
tb.getAge();
assertThat(PointcutForVoid.methodNames.isEmpty()).as("Not void: shouldn't have intercepted").isTrue();
assertThat(PointcutForVoid.methodNames).as("Not void: shouldn't have intercepted").isEmpty();
tb.setAge(1);
tb.getAge();
tb.setName("Tristan");
tb.toString();
assertThat(PointcutForVoid.methodNames.size()).as("Recorded wrong number of invocations").isEqualTo(2);
assertThat(PointcutForVoid.methodNames.get(0).equals("setAge")).isTrue();
assertThat(PointcutForVoid.methodNames.get(1).equals("setName")).isTrue();
assertThat(PointcutForVoid.methodNames).as("Recorded wrong number of invocations").hasSize(2);
assertThat(PointcutForVoid.methodNames.get(0)).isEqualTo("setAge");
assertThat(PointcutForVoid.methodNames.get(1)).isEqualTo("setName");
}
@Test
@@ -498,17 +504,17 @@ public class ProxyFactoryBeanTests {
@Test
public void testGlobalsCanAddAspectInterfaces() {
AddedGlobalInterface agi = (AddedGlobalInterface) factory.getBean("autoInvoker");
assertThat(agi.globalsAdded() == -1).isTrue();
assertThat(agi.globalsAdded()).isEqualTo(-1);
ProxyFactoryBean pfb = (ProxyFactoryBean) factory.getBean("&validGlobals");
// Trigger lazy initialization.
pfb.getObject();
// 2 globals + 2 explicit
assertThat(pfb.getAdvisors().length).as("Have 2 globals and 2 explicit advisors").isEqualTo(3);
assertThat(pfb.getAdvisors()).as("Have 2 globals and 2 explicit advisors").hasSize(3);
ApplicationListener<?> l = (ApplicationListener<?>) factory.getBean("validGlobals");
agi = (AddedGlobalInterface) l;
assertThat(agi.globalsAdded() == -1).isTrue();
assertThat(agi.globalsAdded()).isEqualTo(-1);
assertThat(factory.getBean("test1")).as("Aspect interface shouldn't be implemented without globals")
.isNotInstanceOf(AddedGlobalInterface.class);
@@ -709,6 +715,7 @@ public class ProxyFactoryBeanTests {
}
}
/**
* Aspect interface
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,7 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Juergen Hoeller
* @author Chris Beams
*/
public class ScopedProxyTests {
class ScopedProxyTests {
private static final Class<?> CLASS = ScopedProxyTests.class;
private static final String CLASSNAME = CLASS.getSimpleName();
@@ -51,27 +51,24 @@ public class ScopedProxyTests {
@Test // SPR-2108
public void testProxyAssignable() throws Exception {
void testProxyAssignable() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(MAP_CONTEXT);
Object baseMap = bf.getBean("singletonMap");
boolean condition = baseMap instanceof Map;
assertThat(condition).isTrue();
assertThat(baseMap instanceof Map).isTrue();
}
@Test
public void testSimpleProxy() throws Exception {
void testSimpleProxy() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(MAP_CONTEXT);
Object simpleMap = bf.getBean("simpleMap");
boolean condition1 = simpleMap instanceof Map;
assertThat(condition1).isTrue();
boolean condition = simpleMap instanceof HashMap;
assertThat(condition).isTrue();
assertThat(simpleMap instanceof Map).isTrue();
assertThat(simpleMap instanceof HashMap).isTrue();
}
@Test
public void testScopedOverride() throws Exception {
void testScopedOverride() {
GenericApplicationContext ctx = new GenericApplicationContext();
new XmlBeanDefinitionReader(ctx).loadBeanDefinitions(OVERRIDE_CONTEXT);
SimpleMapScope scope = new SimpleMapScope();
@@ -87,7 +84,7 @@ public class ScopedProxyTests {
}
@Test
public void testJdkScopedProxy() throws Exception {
void testJdkScopedProxy() throws Exception {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(TESTBEAN_CONTEXT);
bf.setSerializationId("X");
@@ -97,8 +94,7 @@ public class ScopedProxyTests {
ITestBean bean = (ITestBean) bf.getBean("testBean");
assertThat(bean).isNotNull();
assertThat(AopUtils.isJdkDynamicProxy(bean)).isTrue();
boolean condition1 = bean instanceof ScopedObject;
assertThat(condition1).isTrue();
assertThat(bean instanceof ScopedObject).isTrue();
ScopedObject scoped = (ScopedObject) bean;
assertThat(scoped.getTargetObject().getClass()).isEqualTo(TestBean.class);
bean.setAge(101);
@@ -110,8 +106,7 @@ public class ScopedProxyTests {
assertThat(deserialized).isNotNull();
assertThat(AopUtils.isJdkDynamicProxy(deserialized)).isTrue();
assertThat(bean.getAge()).isEqualTo(101);
boolean condition = deserialized instanceof ScopedObject;
assertThat(condition).isTrue();
assertThat(deserialized instanceof ScopedObject).isTrue();
ScopedObject scopedDeserialized = (ScopedObject) deserialized;
assertThat(scopedDeserialized.getTargetObject().getClass()).isEqualTo(TestBean.class);
@@ -119,7 +114,7 @@ public class ScopedProxyTests {
}
@Test
public void testCglibScopedProxy() throws Exception {
void testCglibScopedProxy() throws Exception {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(LIST_CONTEXT);
bf.setSerializationId("Y");
@@ -128,8 +123,7 @@ public class ScopedProxyTests {
TestBean tb = (TestBean) bf.getBean("testBean");
assertThat(AopUtils.isCglibProxy(tb.getFriends())).isTrue();
boolean condition1 = tb.getFriends() instanceof ScopedObject;
assertThat(condition1).isTrue();
assertThat(tb.getFriends() instanceof ScopedObject).isTrue();
ScopedObject scoped = (ScopedObject) tb.getFriends();
assertThat(scoped.getTargetObject().getClass()).isEqualTo(ArrayList.class);
tb.getFriends().add("myFriend");
@@ -137,12 +131,11 @@ public class ScopedProxyTests {
assertThat(scope.getMap().containsKey("scopedTarget.scopedList")).isTrue();
assertThat(scope.getMap().get("scopedTarget.scopedList").getClass()).isEqualTo(ArrayList.class);
ArrayList<?> deserialized = (ArrayList<?>) SerializationTestUtils.serializeAndDeserialize(tb.getFriends());
ArrayList<Object> deserialized = (ArrayList<Object>) SerializationTestUtils.serializeAndDeserialize(tb.getFriends());
assertThat(deserialized).isNotNull();
assertThat(AopUtils.isCglibProxy(deserialized)).isTrue();
assertThat(deserialized.contains("myFriend")).isTrue();
boolean condition = deserialized instanceof ScopedObject;
assertThat(condition).isTrue();
assertThat(deserialized).contains("myFriend");
assertThat(deserialized instanceof ScopedObject).isTrue();
ScopedObject scopedDeserialized = (ScopedObject) deserialized;
assertThat(scopedDeserialized.getTargetObject().getClass()).isEqualTo(ArrayList.class);
@@ -63,12 +63,12 @@ public class QualifierAnnotationAutowireContextTests {
new RootBeanDefinition(QualifiedFieldTestBean.class));
AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(
context::refresh)
.satisfies(ex -> {
assertThat(ex.getRootCause()).isInstanceOf(NoSuchBeanDefinitionException.class);
assertThat(ex.getBeanName()).isEqualTo("autowired");
});
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(context::refresh)
.satisfies(ex -> {
assertThat(ex.getRootCause()).isInstanceOf(NoSuchBeanDefinitionException.class);
assertThat(ex.getBeanName()).isEqualTo("autowired");
});
}
@Test
@@ -81,12 +81,13 @@ public class QualifierAnnotationAutowireContextTests {
context.registerBeanDefinition("autowired",
new RootBeanDefinition(QualifiedMethodParameterTestBean.class));
AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(
context::refresh)
.satisfies(ex -> {
assertThat(ex.getRootCause()).isInstanceOf(NoSuchBeanDefinitionException.class);
assertThat(ex.getBeanName()).isEqualTo("autowired");
});
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(context::refresh)
.satisfies(ex -> {
assertThat(ex.getRootCause()).isInstanceOf(NoSuchBeanDefinitionException.class);
assertThat(ex.getBeanName()).isEqualTo("autowired");
});
}
@@ -100,9 +101,10 @@ public class QualifierAnnotationAutowireContextTests {
context.registerBeanDefinition("autowired",
new RootBeanDefinition(QualifiedConstructorArgumentTestBean.class));
AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
assertThatExceptionOfType(UnsatisfiedDependencyException.class).isThrownBy(
context::refresh)
.satisfies(ex -> assertThat(ex.getBeanName()).isEqualTo("autowired"));
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
.isThrownBy(context::refresh)
.satisfies(ex -> assertThat(ex.getBeanName()).isEqualTo("autowired"));
}
@Test
@@ -205,12 +207,13 @@ public class QualifierAnnotationAutowireContextTests {
context.registerBeanDefinition("autowired",
new RootBeanDefinition(QualifiedFieldTestBean.class));
AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(
context::refresh)
.satisfies(ex -> {
assertThat(ex.getRootCause()).isInstanceOf(NoSuchBeanDefinitionException.class);
assertThat(ex.getBeanName()).isEqualTo("autowired");
});
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(context::refresh)
.satisfies(ex -> {
assertThat(ex.getRootCause()).isInstanceOf(NoSuchBeanDefinitionException.class);
assertThat(ex.getBeanName()).isEqualTo("autowired");
});
}
@Test
@@ -227,12 +230,13 @@ public class QualifierAnnotationAutowireContextTests {
context.registerBeanDefinition("autowired",
new RootBeanDefinition(QualifiedMethodParameterTestBean.class));
AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(
context::refresh)
.satisfies(ex -> {
assertThat(ex.getRootCause()).isInstanceOf(NoSuchBeanDefinitionException.class);
assertThat(ex.getBeanName()).isEqualTo("autowired");
});
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(context::refresh)
.satisfies(ex -> {
assertThat(ex.getRootCause()).isInstanceOf(NoSuchBeanDefinitionException.class);
assertThat(ex.getBeanName()).isEqualTo("autowired");
});
}
@Test
@@ -249,9 +253,10 @@ public class QualifierAnnotationAutowireContextTests {
context.registerBeanDefinition("autowired",
new RootBeanDefinition(QualifiedConstructorArgumentTestBean.class));
AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
assertThatExceptionOfType(UnsatisfiedDependencyException.class).isThrownBy(
context::refresh)
.satisfies(ex -> assertThat(ex.getBeanName()).isEqualTo("autowired"));
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
.isThrownBy(context::refresh)
.satisfies(ex -> assertThat(ex.getBeanName()).isEqualTo("autowired"));
}
@Test
@@ -374,12 +379,13 @@ public class QualifierAnnotationAutowireContextTests {
context.registerBeanDefinition("autowired",
new RootBeanDefinition(QualifiedFieldWithDefaultValueTestBean.class));
AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(
context::refresh)
.satisfies(ex -> {
assertThat(ex.getRootCause()).isInstanceOf(NoSuchBeanDefinitionException.class);
assertThat(ex.getBeanName()).isEqualTo("autowired");
});
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(context::refresh)
.satisfies(ex -> {
assertThat(ex.getRootCause()).isInstanceOf(NoSuchBeanDefinitionException.class);
assertThat(ex.getBeanName()).isEqualTo("autowired");
});
}
@Test
@@ -451,12 +457,13 @@ public class QualifierAnnotationAutowireContextTests {
context.registerBeanDefinition("autowired",
new RootBeanDefinition(QualifiedFieldWithMultipleAttributesTestBean.class));
AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(
context::refresh)
.satisfies(ex -> {
assertThat(ex.getRootCause()).isInstanceOf(NoSuchBeanDefinitionException.class);
assertThat(ex.getBeanName()).isEqualTo("autowired");
});
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(context::refresh)
.satisfies(ex -> {
assertThat(ex.getRootCause()).isInstanceOf(NoSuchBeanDefinitionException.class);
assertThat(ex.getBeanName()).isEqualTo("autowired");
});
}
@Test
@@ -507,12 +514,13 @@ public class QualifierAnnotationAutowireContextTests {
context.registerBeanDefinition("autowired",
new RootBeanDefinition(QualifiedFieldWithMultipleAttributesTestBean.class));
AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(
context::refresh)
.satisfies(ex -> {
assertThat(ex.getRootCause()).isInstanceOf(NoSuchBeanDefinitionException.class);
assertThat(ex.getBeanName()).isEqualTo("autowired");
});
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(context::refresh)
.satisfies(ex -> {
assertThat(ex.getRootCause()).isInstanceOf(NoSuchBeanDefinitionException.class);
assertThat(ex.getBeanName()).isEqualTo("autowired");
});
}
@Test
@@ -574,9 +582,10 @@ public class QualifierAnnotationAutowireContextTests {
context.registerBeanDefinition("autowired",
new RootBeanDefinition(QualifiedConstructorArgumentWithBaseQualifierNonDefaultValueTestBean.class));
AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
assertThatExceptionOfType(UnsatisfiedDependencyException.class).isThrownBy(
context::refresh)
.satisfies(ex -> assertThat(ex.getBeanName()).isEqualTo("autowired"));
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
.isThrownBy(context::refresh)
.satisfies(ex -> assertThat(ex.getBeanName()).isEqualTo("autowired"));
}
@@ -752,7 +761,7 @@ public class QualifierAnnotationAutowireContextTests {
@Qualifier
@interface TestQualifierWithMultipleAttributes {
String value() default "default";
String[] value() default "default";
int number();
}
@@ -58,9 +58,10 @@ public class QualifierAnnotationTests {
BeanDefinitionReader reader = new XmlBeanDefinitionReader(context);
reader.loadBeanDefinitions(CONFIG_LOCATION);
context.registerSingleton("testBean", NonQualifiedTestBean.class);
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(
context::refresh)
.withMessageContaining("found 6");
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(context::refresh)
.withMessageContaining("found 6");
}
@Test
@@ -191,9 +192,10 @@ public class QualifierAnnotationTests {
BeanDefinitionReader reader = new XmlBeanDefinitionReader(context);
reader.loadBeanDefinitions(CONFIG_LOCATION);
context.registerSingleton("testBean", QualifiedByAttributesTestBean.class);
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(
context::refresh)
.withMessageContaining("found 6");
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(context::refresh)
.withMessageContaining("found 6");
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -268,6 +268,7 @@ public class ClassPathBeanDefinitionScannerTests {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
scanner.setIncludeAnnotationConfig(false);
scanner.scan("org.springframework.context.annotation2");
assertThatIllegalStateException().isThrownBy(() -> scanner.scan(BASE_PACKAGE))
.withMessageContaining("myNamedDao")
.withMessageContaining(NamedStubDao.class.getName())
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -215,7 +215,7 @@ public class CommonAnnotationBeanPostProcessorTests {
bf.registerBeanDefinition("testBean4", tbd);
bf.registerResolvableDependency(BeanFactory.class, bf);
bf.registerResolvableDependency(INestedTestBean.class, (ObjectFactory<Object>) () -> new NestedTestBean());
bf.registerResolvableDependency(INestedTestBean.class, (ObjectFactory<Object>) NestedTestBean::new);
@SuppressWarnings("deprecation")
org.springframework.beans.factory.config.PropertyPlaceholderConfigurer ppc = new org.springframework.beans.factory.config.PropertyPlaceholderConfigurer();
@@ -233,7 +233,7 @@ public class CommonAnnotationBeanPostProcessorTests {
assertThat(tb).isNotSameAs(anotherBean.getTestBean6());
String[] depBeans = bf.getDependenciesForBean("annotatedBean");
assertThat(depBeans.length).isEqualTo(1);
assertThat(depBeans).hasSize(1);
assertThat(depBeans[0]).isEqualTo("testBean4");
}
@@ -508,6 +508,25 @@ public class CommonAnnotationBeanPostProcessorTests {
assertThat(tb.getName()).isEqualTo("notLazyAnymore");
}
@Test
public void testLazyResolutionWithFallbackTypeMatch() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
CommonAnnotationBeanPostProcessor bpp = new CommonAnnotationBeanPostProcessor();
bpp.setBeanFactory(bf);
bf.addBeanPostProcessor(bpp);
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(LazyResourceCglibInjectionBean.class));
bf.registerBeanDefinition("tb", new RootBeanDefinition(TestBean.class));
LazyResourceCglibInjectionBean bean = (LazyResourceCglibInjectionBean) bf.getBean("annotatedBean");
assertThat(bf.containsSingleton("tb")).isFalse();
bean.testBean.setName("notLazyAnymore");
assertThat(bf.containsSingleton("tb")).isTrue();
TestBean tb = (TestBean) bf.getBean("tb");
assertThat(tb.getName()).isEqualTo("notLazyAnymore");
}
public static class AnnotatedInitDestroyBean {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,7 +29,9 @@ import org.springframework.beans.factory.support.AbstractBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.ResolvableType;
import org.springframework.core.type.AnnotationMetadata;
import static org.assertj.core.api.Assertions.assertThat;
@@ -39,51 +41,62 @@ import static org.assertj.core.api.Assertions.assertThat;
* {@link FactoryBean FactoryBeans} defined in the configuration.
*
* @author Phillip Webb
* @author Juergen Hoeller
*/
public class ConfigurationWithFactoryBeanBeanEarlyDeductionTests {
class ConfigurationWithFactoryBeanEarlyDeductionTests {
@Test
public void preFreezeDirect() {
void preFreezeDirect() {
assertPreFreeze(DirectConfiguration.class);
}
@Test
public void postFreezeDirect() {
void postFreezeDirect() {
assertPostFreeze(DirectConfiguration.class);
}
@Test
public void preFreezeGenericMethod() {
void preFreezeGenericMethod() {
assertPreFreeze(GenericMethodConfiguration.class);
}
@Test
public void postFreezeGenericMethod() {
void postFreezeGenericMethod() {
assertPostFreeze(GenericMethodConfiguration.class);
}
@Test
public void preFreezeGenericClass() {
void preFreezeGenericClass() {
assertPreFreeze(GenericClassConfiguration.class);
}
@Test
public void postFreezeGenericClass() {
void postFreezeGenericClass() {
assertPostFreeze(GenericClassConfiguration.class);
}
@Test
public void preFreezeAttribute() {
void preFreezeAttribute() {
assertPreFreeze(AttributeClassConfiguration.class);
}
@Test
public void postFreezeAttribute() {
void postFreezeAttribute() {
assertPostFreeze(AttributeClassConfiguration.class);
}
@Test
public void preFreezeUnresolvedGenericFactoryBean() {
void preFreezeTargetType() {
assertPreFreeze(TargetTypeConfiguration.class);
}
@Test
void postFreezeTargetType() {
assertPostFreeze(TargetTypeConfiguration.class);
}
@Test
void preFreezeUnresolvedGenericFactoryBean() {
// Covers the case where a @Configuration is picked up via component scanning
// and its bean definition only has a String bean class. In such cases
// beanDefinition.hasBeanClass() returns false so we need to actually
@@ -108,14 +121,13 @@ public class ConfigurationWithFactoryBeanBeanEarlyDeductionTests {
}
}
private void assertPostFreeze(Class<?> configurationClass) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
configurationClass);
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(configurationClass);
assertContainsMyBeanName(context);
}
private void assertPreFreeze(Class<?> configurationClass,
BeanFactoryPostProcessor... postProcessors) {
private void assertPreFreeze(Class<?> configurationClass, BeanFactoryPostProcessor... postProcessors) {
NameCollectingBeanFactoryPostProcessor postProcessor = new NameCollectingBeanFactoryPostProcessor();
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
try {
@@ -138,41 +150,38 @@ public class ConfigurationWithFactoryBeanBeanEarlyDeductionTests {
assertThat(names).containsExactly("myBean");
}
private static class NameCollectingBeanFactoryPostProcessor
implements BeanFactoryPostProcessor {
private static class NameCollectingBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
private String[] names;
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
this.names = beanFactory.getBeanNamesForType(MyBean.class, true, false);
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
ResolvableType typeToMatch = ResolvableType.forClassWithGenerics(MyBean.class, String.class);
this.names = beanFactory.getBeanNamesForType(typeToMatch, true, false);
}
public String[] getNames() {
return this.names;
}
}
@Configuration
static class DirectConfiguration {
@Bean
MyBean myBean() {
return new MyBean();
MyBean<String> myBean() {
return new MyBean<>();
}
}
@Configuration
static class GenericMethodConfiguration {
@Bean
FactoryBean<MyBean> myBean() {
return new TestFactoryBean<>(new MyBean());
FactoryBean<MyBean<String>> myBean() {
return new TestFactoryBean<>(new MyBean<>());
}
}
@Configuration
@@ -182,13 +191,11 @@ public class ConfigurationWithFactoryBeanBeanEarlyDeductionTests {
MyFactoryBean myBean() {
return new MyFactoryBean();
}
}
@Configuration
@Import(AttributeClassRegistrar.class)
static class AttributeClassConfiguration {
}
static class AttributeClassRegistrar implements ImportBeanDefinitionRegistrar {
@@ -197,16 +204,34 @@ public class ConfigurationWithFactoryBeanBeanEarlyDeductionTests {
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
BeanDefinition definition = BeanDefinitionBuilder.genericBeanDefinition(
RawWithAbstractObjectTypeFactoryBean.class).getBeanDefinition();
definition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, MyBean.class);
definition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE,
ResolvableType.forClassWithGenerics(MyBean.class, String.class));
registry.registerBeanDefinition("myBean", definition);
}
}
@Configuration
@Import(TargetTypeRegistrar.class)
static class TargetTypeConfiguration {
}
static class TargetTypeRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
RootBeanDefinition definition = new RootBeanDefinition(RawWithAbstractObjectTypeFactoryBean.class);
definition.setTargetType(ResolvableType.forClassWithGenerics(FactoryBean.class,
ResolvableType.forClassWithGenerics(MyBean.class, String.class)));
definition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE,
ResolvableType.forClassWithGenerics(MyBean.class, String.class));
registry.registerBeanDefinition("myBean", definition);
}
}
abstract static class AbstractMyBean {
}
static class MyBean extends AbstractMyBean {
static class MyBean<T> extends AbstractMyBean {
}
static class TestFactoryBean<T> implements FactoryBean<T> {
@@ -218,7 +243,7 @@ public class ConfigurationWithFactoryBeanBeanEarlyDeductionTests {
}
@Override
public T getObject() throws Exception {
public T getObject() {
return this.instance;
}
@@ -226,31 +251,26 @@ public class ConfigurationWithFactoryBeanBeanEarlyDeductionTests {
public Class<?> getObjectType() {
return this.instance.getClass();
}
}
static class MyFactoryBean extends TestFactoryBean<MyBean> {
static class MyFactoryBean extends TestFactoryBean<MyBean<String>> {
public MyFactoryBean() {
super(new MyBean());
super(new MyBean<>());
}
}
static class RawWithAbstractObjectTypeFactoryBean implements FactoryBean<Object> {
private final Object object = new MyBean();
@Override
public Object getObject() throws Exception {
return object;
throw new IllegalStateException();
}
@Override
public Class<?> getObjectType() {
return MyBean.class;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -138,8 +138,8 @@ class PropertySourceAnnotationTests {
@Test
void withUnresolvablePlaceholder() {
assertThatExceptionOfType(BeanDefinitionStoreException.class)
.isThrownBy(() -> new AnnotationConfigApplicationContext(ConfigWithUnresolvablePlaceholder.class))
.withCauseInstanceOf(IllegalArgumentException.class);
.isThrownBy(() -> new AnnotationConfigApplicationContext(ConfigWithUnresolvablePlaceholder.class))
.withCauseInstanceOf(IllegalArgumentException.class);
}
@Test
@@ -170,8 +170,8 @@ class PropertySourceAnnotationTests {
@Test
void withEmptyResourceLocations() {
assertThatExceptionOfType(BeanDefinitionStoreException.class)
.isThrownBy(() -> new AnnotationConfigApplicationContext(ConfigWithEmptyResourceLocations.class))
.withCauseInstanceOf(IllegalArgumentException.class);
.isThrownBy(() -> new AnnotationConfigApplicationContext(ConfigWithEmptyResourceLocations.class))
.withCauseInstanceOf(IllegalArgumentException.class);
}
@Test
@@ -253,8 +253,8 @@ class PropertySourceAnnotationTests {
@Test
void withMissingPropertySource() {
assertThatExceptionOfType(BeanDefinitionStoreException.class)
.isThrownBy(() -> new AnnotationConfigApplicationContext(ConfigWithMissingPropertySource.class))
.withCauseInstanceOf(FileNotFoundException.class);
.isThrownBy(() -> new AnnotationConfigApplicationContext(ConfigWithMissingPropertySource.class))
.withCauseInstanceOf(FileNotFoundException.class);
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,6 +36,10 @@ public abstract class AbstractApplicationEventListenerTests {
}
}
protected <T> GenericTestEvent<T> createGenericTestEvent(T payload) {
return new GenericTestEvent<>(this, payload);
}
protected static class GenericTestEvent<T> extends ApplicationEvent {
@@ -51,6 +55,7 @@ public abstract class AbstractApplicationEventListenerTests {
}
}
protected static class SmartGenericTestEvent<T> extends GenericTestEvent<T> implements ResolvableTypeProvider {
private final ResolvableType resolvableType;
@@ -67,6 +72,7 @@ public abstract class AbstractApplicationEventListenerTests {
}
}
protected static class StringEvent extends GenericTestEvent<String> {
public StringEvent(Object source, String payload) {
@@ -74,6 +80,7 @@ public abstract class AbstractApplicationEventListenerTests {
}
}
protected static class LongEvent extends GenericTestEvent<Long> {
public LongEvent(Object source, Long payload) {
@@ -81,31 +88,31 @@ public abstract class AbstractApplicationEventListenerTests {
}
}
protected <T> GenericTestEvent<T> createGenericTestEvent(T payload) {
return new GenericTestEvent<>(this, payload);
}
static class GenericEventListener implements ApplicationListener<GenericTestEvent<?>> {
@Override
public void onApplicationEvent(GenericTestEvent<?> event) {
}
}
static class ObjectEventListener implements ApplicationListener<GenericTestEvent<Object>> {
@Override
public void onApplicationEvent(GenericTestEvent<Object> event) {
}
}
static class UpperBoundEventListener
implements ApplicationListener<GenericTestEvent<? extends RuntimeException>> {
static class UpperBoundEventListener implements ApplicationListener<GenericTestEvent<? extends RuntimeException>> {
@Override
public void onApplicationEvent(GenericTestEvent<? extends RuntimeException> event) {
}
}
static class StringEventListener implements ApplicationListener<GenericTestEvent<String>> {
@Override
@@ -113,6 +120,7 @@ public abstract class AbstractApplicationEventListenerTests {
}
}
@SuppressWarnings("rawtypes")
static class RawApplicationListener implements ApplicationListener {
@@ -121,10 +129,10 @@ public abstract class AbstractApplicationEventListenerTests {
}
}
static class TestEvents {
public GenericTestEvent<?> wildcardEvent;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -104,7 +104,8 @@ public class GenericApplicationListenerAdapterTests extends AbstractApplicationE
@Test
public void genericListenerStrictTypeSubClass() {
supportsEventType(false, ObjectEventListener.class, ResolvableType.forClassWithGenerics(GenericTestEvent.class, Long.class));
supportsEventType(false, ObjectEventListener.class,
ResolvableType.forClassWithGenerics(GenericTestEvent.class, Long.class));
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -102,16 +102,16 @@ class GenericApplicationContextTests {
assertThat(context.getBean(String.class)).isSameAs(context.getBean("testBean"));
assertThat(context.getAutowireCapableBeanFactory().getBean(String.class))
.isSameAs(context.getAutowireCapableBeanFactory().getBean("testBean"));
.isSameAs(context.getAutowireCapableBeanFactory().getBean("testBean"));
context.close();
assertThatIllegalStateException()
.isThrownBy(() -> context.getBean(String.class));
.isThrownBy(() -> context.getBean(String.class));
assertThatIllegalStateException()
.isThrownBy(() -> context.getAutowireCapableBeanFactory().getBean(String.class));
.isThrownBy(() -> context.getAutowireCapableBeanFactory().getBean(String.class));
assertThatIllegalStateException()
.isThrownBy(() -> context.getAutowireCapableBeanFactory().getBean("testBean"));
.isThrownBy(() -> context.getAutowireCapableBeanFactory().getBean("testBean"));
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,8 +17,8 @@
package org.springframework.scheduling.concurrent;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RunnableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@@ -52,8 +52,8 @@ class ConcurrentTaskExecutorTests extends AbstractSchedulingTaskExecutorTests {
@AfterEach
void shutdownExecutor() {
for (Runnable task : concurrentExecutor.shutdownNow()) {
if (task instanceof RunnableFuture) {
((RunnableFuture<?>) task).cancel(true);
if (task instanceof Future) {
((Future<?>) task).cancel(true);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,7 +39,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
* @author Sam Brannen
* @since 3.0
*/
public class ThreadPoolTaskSchedulerTests extends AbstractSchedulingTaskExecutorTests {
class ThreadPoolTaskSchedulerTests extends AbstractSchedulingTaskExecutorTests {
private final ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
@@ -97,7 +97,7 @@ public class ThreadPoolTaskSchedulerTests extends AbstractSchedulingTaskExecutor
}
@Test
void scheduleOneTimeFailingTaskWithoutErrorHandler() throws Exception {
void scheduleOneTimeFailingTaskWithoutErrorHandler() {
TestTask task = new TestTask(this.testName, 0);
Future<?> future = scheduler.schedule(task, new Date());
assertThatExceptionOfType(ExecutionException.class).isThrownBy(() -> future.get(1000, TimeUnit.MILLISECONDS));
@@ -149,7 +149,7 @@ public class ThreadPoolTaskSchedulerTests extends AbstractSchedulingTaskExecutor
catch (InterruptedException ex) {
throw new IllegalStateException(ex);
}
assertThat(latch.getCount()).as("latch did not count down,").isEqualTo(0);
assertThat(latch.getCount()).as("latch did not count down").isEqualTo(0);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,27 +35,32 @@ class BitsCronFieldTests {
@Test
void parse() {
assertThat(BitsCronField.parseSeconds("42")).has(clearRange(0, 41)).has(set(42)).has(clearRange(43, 59));
assertThat(BitsCronField.parseSeconds("0-4,8-12")).has(setRange(0, 4)).has(clearRange(5,7)).has(setRange(8, 12)).has(clearRange(13,59));
assertThat(BitsCronField.parseSeconds("57/2")).has(clearRange(0, 56)).has(set(57)).has(clear(58)).has(set(59));
assertThat(BitsCronField.parseSeconds("0-4,8-12")).has(setRange(0, 4)).has(clearRange(5,7))
.has(setRange(8, 12)).has(clearRange(13,59));
assertThat(BitsCronField.parseSeconds("57/2")).has(clearRange(0, 56)).has(set(57))
.has(clear(58)).has(set(59));
assertThat(BitsCronField.parseMinutes("30")).has(set(30)).has(clearRange(1, 29)).has(clearRange(31, 59));
assertThat(BitsCronField.parseHours("23")).has(set(23)).has(clearRange(0, 23));
assertThat(BitsCronField.parseHours("0-23/2")).has(set(0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22)).has(clear(1,3,5,7,9,11,13,15,17,19,21,23));
assertThat(BitsCronField.parseHours("0-23/2")).has(set(0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22))
.has(clear(1,3,5,7,9,11,13,15,17,19,21,23));
assertThat(BitsCronField.parseDaysOfMonth("1")).has(set(1)).has(clearRange(2, 31));
assertThat(BitsCronField.parseMonth("1")).has(set(1)).has(clearRange(2, 12));
assertThat(BitsCronField.parseDaysOfWeek("0")).has(set(7, 7)).has(clearRange(0, 6));
assertThat(BitsCronField.parseDaysOfWeek("7-5")).has(clear(0)).has(setRange(1, 5)).has(clear(6)).has(set(7));
assertThat(BitsCronField.parseDaysOfWeek("7-5")).has(clear(0)).has(setRange(1, 5))
.has(clear(6)).has(set(7));
}
@Test
void parseLists() {
assertThat(BitsCronField.parseSeconds("15,30")).has(set(15, 30)).has(clearRange(1, 15)).has(clearRange(31, 59));
assertThat(BitsCronField.parseMinutes("1,2,5,9")).has(set(1, 2, 5, 9)).has(clear(0)).has(clearRange(3, 4)).has(clearRange(6, 8)).has(clearRange(10, 59));
assertThat(BitsCronField.parseSeconds("15,30")).has(set(15, 30)).has(clearRange(1, 15))
.has(clearRange(31, 59));
assertThat(BitsCronField.parseMinutes("1,2,5,9")).has(set(1, 2, 5, 9)).has(clear(0))
.has(clearRange(3, 4)).has(clearRange(6, 8)).has(clearRange(10, 59));
assertThat(BitsCronField.parseHours("1,2,3")).has(set(1, 2, 3)).has(clearRange(4, 23));
assertThat(BitsCronField.parseDaysOfMonth("1,2,3")).has(set(1, 2, 3)).has(clearRange(4, 31));
assertThat(BitsCronField.parseMonth("1,2,3")).has(set(1, 2, 3)).has(clearRange(4, 12));
@@ -107,6 +112,7 @@ class BitsCronFieldTests {
.has(clear(0)).has(setRange(1, 7));
}
private static Condition<BitsCronField> set(int... indices) {
return new Condition<BitsCronField>(String.format("set bits %s", Arrays.toString(indices))) {
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -848,6 +848,7 @@ class CronTriggerTests {
assertThat(nextExecutionTime).isEqualTo(this.calendar.getTime());
}
private static void roundup(Calendar calendar) {
calendar.add(Calendar.SECOND, 1);
calendar.set(Calendar.MILLISECOND, 0);
@@ -861,9 +862,7 @@ class CronTriggerTests {
}
private static TriggerContext getTriggerContext(Date lastCompletionTime) {
SimpleTriggerContext context = new SimpleTriggerContext();
context.update(null, null, lastCompletionTime);
return context;
return new SimpleTriggerContext(null, null, lastCompletionTime);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
* Unit tests for {@link QuartzCronField}.
*
* @author Arjen Poutsma
* @author Juergen Hoeller
*/
class QuartzCronFieldTests {
@@ -71,6 +72,46 @@ class QuartzCronFieldTests {
assertThat(field.nextOrSame(last)).isEqualTo(expected);
}
@Test
void dayOfWeek_0(){
// third Sunday (0) of the month
QuartzCronField field = QuartzCronField.parseDaysOfWeek("0#3");
LocalDate last = LocalDate.of(2024, 1, 1);
LocalDate expected = LocalDate.of(2024, 1, 21);
assertThat(field.nextOrSame(last)).isEqualTo(expected);
}
@Test
void dayOfWeek_1(){
// third Monday (1) of the month
QuartzCronField field = QuartzCronField.parseDaysOfWeek("1#3");
LocalDate last = LocalDate.of(2024, 1, 1);
LocalDate expected = LocalDate.of(2024, 1, 15);
assertThat(field.nextOrSame(last)).isEqualTo(expected);
}
@Test
void dayOfWeek_2(){
// third Tuesday (2) of the month
QuartzCronField field = QuartzCronField.parseDaysOfWeek("2#3");
LocalDate last = LocalDate.of(2024, 1, 1);
LocalDate expected = LocalDate.of(2024, 1, 16);
assertThat(field.nextOrSame(last)).isEqualTo(expected);
}
@Test
void dayOfWeek_7() {
// third Sunday (7 as alternative to 0) of the month
QuartzCronField field = QuartzCronField.parseDaysOfWeek("7#3");
LocalDate last = LocalDate.of(2024, 1, 1);
LocalDate expected = LocalDate.of(2024, 1, 21);
assertThat(field.nextOrSame(last)).isEqualTo(expected);
}
@Test
void invalidValues() {
assertThatIllegalArgumentException().isThrownBy(() -> QuartzCronField.parseDaysOfMonth(""));
@@ -3,9 +3,6 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<!--
Not yet in use: illustration of possible approach
-->
<bean id="overrideOneMethod" class="org.springframework.beans.factory.xml.OverrideOneMethod">
<lookup-method name="getPrototypeDependency" bean="jenny"/>
@@ -27,39 +24,34 @@
<lookup-method name="protectedOverrideSingleton" bean="david"/>
<!--
This method is not overloaded, so we don't need to specify any arg types
-->
<!-- This method is not overloaded, so we don't need to specify any arg types -->
<replaced-method name="doSomething" replacer="doSomethingReplacer"/>
</bean>
<bean id="replaceVoidMethod" parent="someParent"
class="org.springframework.beans.factory.xml.OverrideOneMethodSubclass">
<bean id="replaceVoidMethod" parent="someParent" class="org.springframework.beans.factory.xml.OverrideOneMethodSubclass"/>
<bean id="replaceEchoMethod" class="org.springframework.beans.factory.xml.EchoService">
<!-- This method is not overloaded, so we don't need to specify any arg types -->
<replaced-method name="echo" replacer="reverseArrayReplacer" />
</bean>
<bean id="reverseReplacer"
class="org.springframework.beans.factory.xml.ReverseMethodReplacer"/>
<bean id="reverseReplacer" class="org.springframework.beans.factory.xml.ReverseMethodReplacer"/>
<bean id="fixedReplacer"
class="org.springframework.beans.factory.xml.FixedMethodReplacer"/>
<bean id="reverseArrayReplacer" class="org.springframework.beans.factory.xml.ReverseArrayMethodReplacer"/>
<bean id="doSomethingReplacer"
class="org.springframework.beans.factory.xml.XmlBeanFactoryTests$DoSomethingReplacer"/>
<bean id="fixedReplacer" class="org.springframework.beans.factory.xml.FixedMethodReplacer"/>
<bean id="serializableReplacer"
class="org.springframework.beans.factory.xml.SerializableMethodReplacerCandidate">
<bean id="doSomethingReplacer" class="org.springframework.beans.factory.xml.XmlBeanFactoryTests$DoSomethingReplacer"/>
<bean id="serializableReplacer" class="org.springframework.beans.factory.xml.SerializableMethodReplacerCandidate">
<!-- Arbitrary method replacer -->
<replaced-method name="replaceMe" replacer="reverseReplacer">
<arg-type>String</arg-type>
</replaced-method>
</bean>
<bean id="jenny" class="org.springframework.beans.testfixture.beans.TestBean"
scope="prototype">
<bean id="jenny" class="org.springframework.beans.testfixture.beans.TestBean" scope="prototype">
<property name="name"><value>Jenny</value></property>
<property name="age"><value>30</value></property>
<property name="spouse">
@@ -68,8 +60,7 @@
</property>
</bean>
<bean id="david" class="org.springframework.beans.testfixture.beans.TestBean"
scope="singleton">
<bean id="david" class="org.springframework.beans.testfixture.beans.TestBean" scope="singleton">
<description>
Simple bean, without any collections.
</description>
@@ -16,12 +16,24 @@
package org.springframework.cglib.beans;
import java.beans.PropertyDescriptor;
import java.lang.reflect.*;
import java.security.ProtectionDomain;
import org.springframework.cglib.core.*;
import java.util.HashMap;
import java.util.Map;
import org.springframework.asm.ClassVisitor;
import org.springframework.asm.Type;
import java.util.*;
import org.springframework.cglib.core.AbstractClassGenerator;
import org.springframework.cglib.core.ClassEmitter;
import org.springframework.cglib.core.CodeEmitter;
import org.springframework.cglib.core.Constants;
import org.springframework.cglib.core.Converter;
import org.springframework.cglib.core.EmitUtils;
import org.springframework.cglib.core.KeyFactory;
import org.springframework.cglib.core.Local;
import org.springframework.cglib.core.MethodInfo;
import org.springframework.cglib.core.ReflectUtils;
import org.springframework.cglib.core.Signature;
import org.springframework.cglib.core.TypeUtils;
/**
* @author Chris Nokleberg
@@ -29,151 +41,154 @@ import java.util.*;
@SuppressWarnings({"rawtypes", "unchecked"})
abstract public class BeanCopier
{
private static final BeanCopierKey KEY_FACTORY =
(BeanCopierKey)KeyFactory.create(BeanCopierKey.class);
private static final Type CONVERTER =
TypeUtils.parseType("org.springframework.cglib.core.Converter");
private static final Type BEAN_COPIER =
TypeUtils.parseType("org.springframework.cglib.beans.BeanCopier");
private static final Signature COPY =
new Signature("copy", Type.VOID_TYPE, new Type[]{ Constants.TYPE_OBJECT, Constants.TYPE_OBJECT, CONVERTER });
private static final Signature CONVERT =
TypeUtils.parseSignature("Object convert(Object, Class, Object)");
interface BeanCopierKey {
public Object newInstance(String source, String target, boolean useConverter);
}
private static final BeanCopierKey KEY_FACTORY =
(BeanCopierKey)KeyFactory.create(BeanCopierKey.class);
private static final Type CONVERTER =
TypeUtils.parseType("org.springframework.cglib.core.Converter");
private static final Type BEAN_COPIER =
TypeUtils.parseType("org.springframework.cglib.beans.BeanCopier");
private static final Signature COPY =
new Signature("copy", Type.VOID_TYPE, new Type[]{ Constants.TYPE_OBJECT, Constants.TYPE_OBJECT, CONVERTER });
private static final Signature CONVERT =
TypeUtils.parseSignature("Object convert(Object, Class, Object)");
public static BeanCopier create(Class source, Class target, boolean useConverter) {
Generator gen = new Generator();
gen.setSource(source);
gen.setTarget(target);
gen.setUseConverter(useConverter);
return gen.create();
}
interface BeanCopierKey {
public Object newInstance(String source, String target, boolean useConverter);
}
abstract public void copy(Object from, Object to, Converter converter);
public static BeanCopier create(Class source, Class target, boolean useConverter) {
Generator gen = new Generator();
gen.setSource(source);
gen.setTarget(target);
gen.setUseConverter(useConverter);
return gen.create();
}
public static class Generator extends AbstractClassGenerator {
private static final Source SOURCE = new Source(BeanCopier.class.getName());
private Class source;
private Class target;
private boolean useConverter;
abstract public void copy(Object from, Object to, Converter converter);
public Generator() {
super(SOURCE);
}
public static class Generator extends AbstractClassGenerator {
private static final Source SOURCE = new Source(BeanCopier.class.getName());
private Class source;
private Class target;
private boolean useConverter;
public void setSource(Class source) {
if(!Modifier.isPublic(source.getModifiers())){
setNamePrefix(source.getName());
}
this.source = source;
}
public void setTarget(Class target) {
if(!Modifier.isPublic(target.getModifiers())){
setNamePrefix(target.getName());
}
this.target = target;
public Generator() {
super(SOURCE);
}
public void setSource(Class source) {
this.source = source;
// SPRING PATCH BEGIN
setContextClass(source);
setNamePrefix(source.getName());
// SPRING PATCH END
}
public void setTarget(Class target) {
this.target = target;
// SPRING PATCH BEGIN
setContextClass(target);
setNamePrefix(target.getName());
// SPRING PATCH END
}
}
public void setUseConverter(boolean useConverter) {
this.useConverter = useConverter;
}
public void setUseConverter(boolean useConverter) {
this.useConverter = useConverter;
}
protected ClassLoader getDefaultClassLoader() {
return source.getClassLoader();
}
@Override
protected ClassLoader getDefaultClassLoader() {
return source.getClassLoader();
}
protected ProtectionDomain getProtectionDomain() {
return ReflectUtils.getProtectionDomain(source);
}
@Override
protected ProtectionDomain getProtectionDomain() {
return ReflectUtils.getProtectionDomain(source);
}
public BeanCopier create() {
Object key = KEY_FACTORY.newInstance(source.getName(), target.getName(), useConverter);
return (BeanCopier)super.create(key);
}
public BeanCopier create() {
Object key = KEY_FACTORY.newInstance(source.getName(), target.getName(), useConverter);
return (BeanCopier)super.create(key);
}
public void generateClass(ClassVisitor v) {
Type sourceType = Type.getType(source);
Type targetType = Type.getType(target);
ClassEmitter ce = new ClassEmitter(v);
ce.begin_class(Constants.V1_8,
Constants.ACC_PUBLIC,
getClassName(),
BEAN_COPIER,
null,
Constants.SOURCE_FILE);
@Override
public void generateClass(ClassVisitor v) {
Type sourceType = Type.getType(source);
Type targetType = Type.getType(target);
ClassEmitter ce = new ClassEmitter(v);
ce.begin_class(Constants.V1_8,
Constants.ACC_PUBLIC,
getClassName(),
BEAN_COPIER,
null,
Constants.SOURCE_FILE);
EmitUtils.null_constructor(ce);
CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, COPY, null);
PropertyDescriptor[] getters = ReflectUtils.getBeanGetters(source);
PropertyDescriptor[] setters = ReflectUtils.getBeanSetters(target);
EmitUtils.null_constructor(ce);
CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, COPY, null);
PropertyDescriptor[] getters = ReflectUtils.getBeanGetters(source);
PropertyDescriptor[] setters = ReflectUtils.getBeanSetters(target);
Map names = new HashMap();
for (int i = 0; i < getters.length; i++) {
names.put(getters[i].getName(), getters[i]);
}
Local targetLocal = e.make_local();
Local sourceLocal = e.make_local();
if (useConverter) {
e.load_arg(1);
e.checkcast(targetType);
e.store_local(targetLocal);
e.load_arg(0);
e.checkcast(sourceType);
e.store_local(sourceLocal);
} else {
e.load_arg(1);
e.checkcast(targetType);
e.load_arg(0);
e.checkcast(sourceType);
}
for (int i = 0; i < setters.length; i++) {
PropertyDescriptor setter = setters[i];
PropertyDescriptor getter = (PropertyDescriptor)names.get(setter.getName());
if (getter != null) {
MethodInfo read = ReflectUtils.getMethodInfo(getter.getReadMethod());
MethodInfo write = ReflectUtils.getMethodInfo(setter.getWriteMethod());
if (useConverter) {
Type setterType = write.getSignature().getArgumentTypes()[0];
e.load_local(targetLocal);
e.load_arg(2);
e.load_local(sourceLocal);
e.invoke(read);
e.box(read.getSignature().getReturnType());
EmitUtils.load_class(e, setterType);
e.push(write.getSignature().getName());
e.invoke_interface(CONVERTER, CONVERT);
e.unbox_or_zero(setterType);
e.invoke(write);
} else if (compatible(getter, setter)) {
e.dup2();
e.invoke(read);
e.invoke(write);
}
}
}
e.return_value();
e.end_method();
ce.end_class();
}
Map names = new HashMap();
for (PropertyDescriptor getter : getters) {
names.put(getter.getName(), getter);
}
Local targetLocal = e.make_local();
Local sourceLocal = e.make_local();
if (useConverter) {
e.load_arg(1);
e.checkcast(targetType);
e.store_local(targetLocal);
e.load_arg(0);
e.checkcast(sourceType);
e.store_local(sourceLocal);
} else {
e.load_arg(1);
e.checkcast(targetType);
e.load_arg(0);
e.checkcast(sourceType);
}
for (PropertyDescriptor setter : setters) {
PropertyDescriptor getter = (PropertyDescriptor)names.get(setter.getName());
if (getter != null) {
MethodInfo read = ReflectUtils.getMethodInfo(getter.getReadMethod());
MethodInfo write = ReflectUtils.getMethodInfo(setter.getWriteMethod());
if (useConverter) {
Type setterType = write.getSignature().getArgumentTypes()[0];
e.load_local(targetLocal);
e.load_arg(2);
e.load_local(sourceLocal);
e.invoke(read);
e.box(read.getSignature().getReturnType());
EmitUtils.load_class(e, setterType);
e.push(write.getSignature().getName());
e.invoke_interface(CONVERTER, CONVERT);
e.unbox_or_zero(setterType);
e.invoke(write);
} else if (compatible(getter, setter)) {
e.dup2();
e.invoke(read);
e.invoke(write);
}
}
}
e.return_value();
e.end_method();
ce.end_class();
}
private static boolean compatible(PropertyDescriptor getter, PropertyDescriptor setter) {
// TODO: allow automatic widening conversions?
return setter.getPropertyType().isAssignableFrom(getter.getPropertyType());
}
private static boolean compatible(PropertyDescriptor getter, PropertyDescriptor setter) {
// TODO: allow automatic widening conversions?
return setter.getPropertyType().isAssignableFrom(getter.getPropertyType());
}
protected Object firstInstance(Class type) {
return ReflectUtils.newInstance(type);
}
@Override
protected Object firstInstance(Class type) {
return ReflectUtils.newInstance(type);
}
protected Object nextInstance(Object instance) {
return instance;
}
}
@Override
protected Object nextInstance(Object instance) {
return instance;
}
}
}
@@ -17,10 +17,18 @@ package org.springframework.cglib.beans;
import java.beans.PropertyDescriptor;
import java.security.ProtectionDomain;
import java.util.*;
import org.springframework.cglib.core.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.springframework.asm.ClassVisitor;
import org.springframework.asm.Type;
import org.springframework.cglib.core.AbstractClassGenerator;
import org.springframework.cglib.core.ClassEmitter;
import org.springframework.cglib.core.Constants;
import org.springframework.cglib.core.EmitUtils;
import org.springframework.cglib.core.KeyFactory;
import org.springframework.cglib.core.ReflectUtils;
/**
* @author Juozas Baliuka, Chris Nokleberg
@@ -28,126 +36,131 @@ import org.springframework.asm.Type;
@SuppressWarnings({"rawtypes", "unchecked"})
public class BeanGenerator extends AbstractClassGenerator
{
private static final Source SOURCE = new Source(BeanGenerator.class.getName());
private static final BeanGeneratorKey KEY_FACTORY =
(BeanGeneratorKey)KeyFactory.create(BeanGeneratorKey.class);
interface BeanGeneratorKey {
public Object newInstance(String superclass, Map props);
}
private static final Source SOURCE = new Source(BeanGenerator.class.getName());
private static final BeanGeneratorKey KEY_FACTORY =
(BeanGeneratorKey)KeyFactory.create(BeanGeneratorKey.class);
private Class superclass;
private Map props = new HashMap();
private boolean classOnly;
interface BeanGeneratorKey {
public Object newInstance(String superclass, Map props);
}
public BeanGenerator() {
super(SOURCE);
}
private Class superclass;
private Map props = new HashMap();
private boolean classOnly;
/**
* Set the class which the generated class will extend. The class
* must not be declared as final, and must have a non-private
* no-argument constructor.
* @param superclass class to extend, or null to extend Object
*/
public void setSuperclass(Class superclass) {
if (superclass != null && superclass.equals(Object.class)) {
superclass = null;
}
this.superclass = superclass;
public BeanGenerator() {
super(SOURCE);
}
/**
* Set the class which the generated class will extend. The class
* must not be declared as final, and must have a non-private
* no-argument constructor.
* @param superclass class to extend, or null to extend Object
*/
public void setSuperclass(Class superclass) {
if (superclass != null && superclass.equals(Object.class)) {
superclass = null;
}
this.superclass = superclass;
// SPRING PATCH BEGIN
setContextClass(superclass);
// SPRING PATCH END
}
}
public void addProperty(String name, Class type) {
if (props.containsKey(name)) {
throw new IllegalArgumentException("Duplicate property name \"" + name + "\"");
}
props.put(name, Type.getType(type));
}
public void addProperty(String name, Class type) {
if (props.containsKey(name)) {
throw new IllegalArgumentException("Duplicate property name \"" + name + "\"");
}
props.put(name, Type.getType(type));
}
protected ClassLoader getDefaultClassLoader() {
if (superclass != null) {
return superclass.getClassLoader();
} else {
return null;
}
}
@Override
protected ClassLoader getDefaultClassLoader() {
if (superclass != null) {
return superclass.getClassLoader();
} else {
return null;
}
}
protected ProtectionDomain getProtectionDomain() {
return ReflectUtils.getProtectionDomain(superclass);
}
@Override
protected ProtectionDomain getProtectionDomain() {
return ReflectUtils.getProtectionDomain(superclass);
}
public Object create() {
classOnly = false;
return createHelper();
}
public Object create() {
classOnly = false;
return createHelper();
}
public Object createClass() {
classOnly = true;
return createHelper();
}
public Object createClass() {
classOnly = true;
return createHelper();
}
private Object createHelper() {
if (superclass != null) {
setNamePrefix(superclass.getName());
}
String superName = (superclass != null) ? superclass.getName() : "java.lang.Object";
Object key = KEY_FACTORY.newInstance(superName, props);
return super.create(key);
}
private Object createHelper() {
if (superclass != null) {
setNamePrefix(superclass.getName());
}
String superName = (superclass != null) ? superclass.getName() : "java.lang.Object";
Object key = KEY_FACTORY.newInstance(superName, props);
return super.create(key);
}
public void generateClass(ClassVisitor v) throws Exception {
int size = props.size();
String[] names = (String[])props.keySet().toArray(new String[size]);
Type[] types = new Type[size];
for (int i = 0; i < size; i++) {
types[i] = (Type)props.get(names[i]);
}
ClassEmitter ce = new ClassEmitter(v);
ce.begin_class(Constants.V1_8,
Constants.ACC_PUBLIC,
getClassName(),
superclass != null ? Type.getType(superclass) : Constants.TYPE_OBJECT,
null,
null);
EmitUtils.null_constructor(ce);
EmitUtils.add_properties(ce, names, types);
ce.end_class();
}
@Override
public void generateClass(ClassVisitor v) throws Exception {
int size = props.size();
String[] names = (String[])props.keySet().toArray(new String[size]);
Type[] types = new Type[size];
for (int i = 0; i < size; i++) {
types[i] = (Type)props.get(names[i]);
}
ClassEmitter ce = new ClassEmitter(v);
ce.begin_class(Constants.V1_8,
Constants.ACC_PUBLIC,
getClassName(),
superclass != null ? Type.getType(superclass) : Constants.TYPE_OBJECT,
null,
null);
EmitUtils.null_constructor(ce);
EmitUtils.add_properties(ce, names, types);
ce.end_class();
}
protected Object firstInstance(Class type) {
if (classOnly) {
return type;
} else {
return ReflectUtils.newInstance(type);
}
}
@Override
protected Object firstInstance(Class type) {
if (classOnly) {
return type;
} else {
return ReflectUtils.newInstance(type);
}
}
protected Object nextInstance(Object instance) {
Class protoclass = (instance instanceof Class) ? (Class)instance : instance.getClass();
if (classOnly) {
return protoclass;
} else {
return ReflectUtils.newInstance(protoclass);
}
}
@Override
protected Object nextInstance(Object instance) {
Class protoclass = (instance instanceof Class) ? (Class)instance : instance.getClass();
if (classOnly) {
return protoclass;
} else {
return ReflectUtils.newInstance(protoclass);
}
}
public static void addProperties(BeanGenerator gen, Map props) {
for (Iterator it = props.keySet().iterator(); it.hasNext();) {
String name = (String)it.next();
gen.addProperty(name, (Class)props.get(name));
}
}
public static void addProperties(BeanGenerator gen, Map props) {
for (Iterator it = props.keySet().iterator(); it.hasNext();) {
String name = (String)it.next();
gen.addProperty(name, (Class)props.get(name));
}
}
public static void addProperties(BeanGenerator gen, Class type) {
addProperties(gen, ReflectUtils.getBeanProperties(type));
}
public static void addProperties(BeanGenerator gen, Class type) {
addProperties(gen, ReflectUtils.getBeanProperties(type));
}
public static void addProperties(BeanGenerator gen, PropertyDescriptor[] descriptors) {
for (int i = 0; i < descriptors.length; i++) {
gen.addProperty(descriptors[i].getName(), descriptors[i].getPropertyType());
}
}
public static void addProperties(BeanGenerator gen, PropertyDescriptor[] descriptors) {
for (PropertyDescriptor descriptor : descriptors) {
gen.addProperty(descriptor.getName(), descriptor.getPropertyType());
}
}
}
@@ -41,293 +41,310 @@ import org.springframework.cglib.core.ReflectUtils;
*/
@SuppressWarnings({"rawtypes", "unchecked"})
abstract public class BeanMap implements Map {
/**
* Limit the properties reflected in the key set of the map
* to readable properties.
* @see BeanMap.Generator#setRequire
*/
public static final int REQUIRE_GETTER = 1;
/**
* Limit the properties reflected in the key set of the map
* to readable properties.
* @see BeanMap.Generator#setRequire
*/
public static final int REQUIRE_GETTER = 1;
/**
* Limit the properties reflected in the key set of the map
* to writable properties.
* @see BeanMap.Generator#setRequire
*/
public static final int REQUIRE_SETTER = 2;
/**
* Helper method to create a new <code>BeanMap</code>. For finer
* control over the generated instance, use a new instance of
* <code>BeanMap.Generator</code> instead of this static method.
* @param bean the JavaBean underlying the map
* @return a new <code>BeanMap</code> instance
*/
public static BeanMap create(Object bean) {
Generator gen = new Generator();
gen.setBean(bean);
return gen.create();
}
/**
* Limit the properties reflected in the key set of the map
* to writable properties.
* @see BeanMap.Generator#setRequire
*/
public static final int REQUIRE_SETTER = 2;
public static class Generator extends AbstractClassGenerator {
private static final Source SOURCE = new Source(BeanMap.class.getName());
/**
* Helper method to create a new <code>BeanMap</code>. For finer
* control over the generated instance, use a new instance of
* <code>BeanMap.Generator</code> instead of this static method.
* @param bean the JavaBean underlying the map
* @return a new <code>BeanMap</code> instance
*/
public static BeanMap create(Object bean) {
Generator gen = new Generator();
gen.setBean(bean);
return gen.create();
}
private static final BeanMapKey KEY_FACTORY =
(BeanMapKey)KeyFactory.create(BeanMapKey.class, KeyFactory.CLASS_BY_NAME);
public static class Generator extends AbstractClassGenerator {
private static final Source SOURCE = new Source(BeanMap.class.getName());
interface BeanMapKey {
public Object newInstance(Class type, int require);
}
private Object bean;
private Class beanClass;
private int require;
public Generator() {
super(SOURCE);
}
private static final BeanMapKey KEY_FACTORY =
(BeanMapKey)KeyFactory.create(BeanMapKey.class, KeyFactory.CLASS_BY_NAME);
/**
* Set the bean that the generated map should reflect. The bean may be swapped
* out for another bean of the same type using {@link #setBean}.
* Calling this method overrides any value previously set using {@link #setBeanClass}.
* You must call either this method or {@link #setBeanClass} before {@link #create}.
* @param bean the initial bean
*/
public void setBean(Object bean) {
this.bean = bean;
if (bean != null) {
interface BeanMapKey {
public Object newInstance(Class type, int require);
}
private Object bean;
private Class beanClass;
private int require;
public Generator() {
super(SOURCE);
}
/**
* Set the bean that the generated map should reflect. The bean may be swapped
* out for another bean of the same type using {@link #setBean}.
* Calling this method overrides any value previously set using {@link #setBeanClass}.
* You must call either this method or {@link #setBeanClass} before {@link #create}.
* @param bean the initial bean
*/
public void setBean(Object bean) {
this.bean = bean;
if (bean != null) {
beanClass = bean.getClass();
// SPRING PATCH BEGIN
setContextClass(beanClass);
// SPRING PATCH END
}
}
}
/**
* Set the class of the bean that the generated map should support.
* You must call either this method or {@link #setBeanClass} before {@link #create}.
* @param beanClass the class of the bean
*/
public void setBeanClass(Class beanClass) {
this.beanClass = beanClass;
}
/**
* Set the class of the bean that the generated map should support.
* You must call either this method or {@link #setBeanClass} before {@link #create}.
* @param beanClass the class of the bean
*/
public void setBeanClass(Class beanClass) {
this.beanClass = beanClass;
}
/**
* Limit the properties reflected by the generated map.
* @param require any combination of {@link #REQUIRE_GETTER} and
* {@link #REQUIRE_SETTER}; default is zero (any property allowed)
*/
public void setRequire(int require) {
this.require = require;
}
/**
* Limit the properties reflected by the generated map.
* @param require any combination of {@link #REQUIRE_GETTER} and
* {@link #REQUIRE_SETTER}; default is zero (any property allowed)
*/
public void setRequire(int require) {
this.require = require;
}
protected ClassLoader getDefaultClassLoader() {
return beanClass.getClassLoader();
}
@Override
protected ClassLoader getDefaultClassLoader() {
return beanClass.getClassLoader();
}
protected ProtectionDomain getProtectionDomain() {
return ReflectUtils.getProtectionDomain(beanClass);
}
@Override
protected ProtectionDomain getProtectionDomain() {
return ReflectUtils.getProtectionDomain(beanClass);
}
/**
* Create a new instance of the <code>BeanMap</code>. An existing
* generated class will be reused if possible.
*/
public BeanMap create() {
if (beanClass == null)
throw new IllegalArgumentException("Class of bean unknown");
setNamePrefix(beanClass.getName());
return (BeanMap)super.create(KEY_FACTORY.newInstance(beanClass, require));
}
/**
* Create a new instance of the <code>BeanMap</code>. An existing
* generated class will be reused if possible.
*/
public BeanMap create() {
if (beanClass == null) {
throw new IllegalArgumentException("Class of bean unknown");
}
setNamePrefix(beanClass.getName());
return (BeanMap)super.create(KEY_FACTORY.newInstance(beanClass, require));
}
public void generateClass(ClassVisitor v) throws Exception {
new BeanMapEmitter(v, getClassName(), beanClass, require);
}
@Override
public void generateClass(ClassVisitor v) throws Exception {
new BeanMapEmitter(v, getClassName(), beanClass, require);
}
protected Object firstInstance(Class type) {
return ((BeanMap)ReflectUtils.newInstance(type)).newInstance(bean);
}
@Override
protected Object firstInstance(Class type) {
return ((BeanMap)ReflectUtils.newInstance(type)).newInstance(bean);
}
protected Object nextInstance(Object instance) {
return ((BeanMap)instance).newInstance(bean);
}
}
@Override
protected Object nextInstance(Object instance) {
return ((BeanMap)instance).newInstance(bean);
}
}
/**
* Create a new <code>BeanMap</code> instance using the specified bean.
* This is faster than using the {@link #create} static method.
* @param bean the JavaBean underlying the map
* @return a new <code>BeanMap</code> instance
*/
abstract public BeanMap newInstance(Object bean);
/**
* Create a new <code>BeanMap</code> instance using the specified bean.
* This is faster than using the {@link #create} static method.
* @param bean the JavaBean underlying the map
* @return a new <code>BeanMap</code> instance
*/
abstract public BeanMap newInstance(Object bean);
/**
* Get the type of a property.
* @param name the name of the JavaBean property
* @return the type of the property, or null if the property does not exist
*/
abstract public Class getPropertyType(String name);
/**
* Get the type of a property.
* @param name the name of the JavaBean property
* @return the type of the property, or null if the property does not exist
*/
abstract public Class getPropertyType(String name);
protected Object bean;
protected Object bean;
protected BeanMap() {
}
protected BeanMap() {
}
protected BeanMap(Object bean) {
setBean(bean);
}
protected BeanMap(Object bean) {
setBean(bean);
}
public Object get(Object key) {
return get(bean, key);
}
@Override
public Object get(Object key) {
return get(bean, key);
}
public Object put(Object key, Object value) {
return put(bean, key, value);
}
@Override
public Object put(Object key, Object value) {
return put(bean, key, value);
}
/**
* Get the property of a bean. This allows a <code>BeanMap</code>
* to be used statically for multiple beans--the bean instance tied to the
* map is ignored and the bean passed to this method is used instead.
* @param bean the bean to query; must be compatible with the type of
* this <code>BeanMap</code>
* @param key must be a String
* @return the current value, or null if there is no matching property
*/
abstract public Object get(Object bean, Object key);
/**
* Get the property of a bean. This allows a <code>BeanMap</code>
* to be used statically for multiple beans--the bean instance tied to the
* map is ignored and the bean passed to this method is used instead.
* @param bean the bean to query; must be compatible with the type of
* this <code>BeanMap</code>
* @param key must be a String
* @return the current value, or null if there is no matching property
*/
abstract public Object get(Object bean, Object key);
/**
* Set the property of a bean. This allows a <code>BeanMap</code>
* to be used statically for multiple beans--the bean instance tied to the
* map is ignored and the bean passed to this method is used instead.
* @param key must be a String
* @return the old value, if there was one, or null
*/
abstract public Object put(Object bean, Object key, Object value);
/**
* Set the property of a bean. This allows a <code>BeanMap</code>
* to be used statically for multiple beans--the bean instance tied to the
* map is ignored and the bean passed to this method is used instead.
* @param key must be a String
* @return the old value, if there was one, or null
*/
abstract public Object put(Object bean, Object key, Object value);
/**
* Change the underlying bean this map should use.
* @param bean the new JavaBean
* @see #getBean
*/
public void setBean(Object bean) {
this.bean = bean;
}
/**
* Change the underlying bean this map should use.
* @param bean the new JavaBean
* @see #getBean
*/
public void setBean(Object bean) {
this.bean = bean;
}
/**
* Return the bean currently in use by this map.
* @return the current JavaBean
* @see #setBean
*/
public Object getBean() {
return bean;
}
/**
* Return the bean currently in use by this map.
* @return the current JavaBean
* @see #setBean
*/
public Object getBean() {
return bean;
}
public void clear() {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
public boolean containsKey(Object key) {
return keySet().contains(key);
}
@Override
public boolean containsKey(Object key) {
return keySet().contains(key);
}
public boolean containsValue(Object value) {
for (Iterator it = keySet().iterator(); it.hasNext();) {
Object v = get(it.next());
if (((value == null) && (v == null)) || (value != null && value.equals(v)))
return true;
}
return false;
}
@Override
public boolean containsValue(Object value) {
for (Iterator it = keySet().iterator(); it.hasNext();) {
Object v = get(it.next());
if (((value == null) && (v == null)) || (value != null && value.equals(v))) {
return true;
}
}
return false;
}
public int size() {
return keySet().size();
}
@Override
public int size() {
return keySet().size();
}
public boolean isEmpty() {
return size() == 0;
}
@Override
public boolean isEmpty() {
return size() == 0;
}
public Object remove(Object key) {
throw new UnsupportedOperationException();
}
@Override
public Object remove(Object key) {
throw new UnsupportedOperationException();
}
public void putAll(Map t) {
for (Iterator it = t.keySet().iterator(); it.hasNext();) {
Object key = it.next();
put(key, t.get(key));
}
}
@Override
public void putAll(Map t) {
for (Object key : t.keySet()) {
put(key, t.get(key));
}
}
public boolean equals(Object o) {
if (o == null || !(o instanceof Map)) {
return false;
}
Map other = (Map)o;
if (size() != other.size()) {
return false;
}
for (Iterator it = keySet().iterator(); it.hasNext();) {
Object key = it.next();
if (!other.containsKey(key)) {
return false;
}
Object v1 = get(key);
Object v2 = other.get(key);
if (!((v1 == null) ? v2 == null : v1.equals(v2))) {
return false;
}
}
return true;
}
@Override
public boolean equals(Object o) {
if (o == null || !(o instanceof Map)) {
return false;
}
Map other = (Map)o;
if (size() != other.size()) {
return false;
}
for (Object key : keySet()) {
if (!other.containsKey(key)) {
return false;
}
Object v1 = get(key);
Object v2 = other.get(key);
if (!((v1 == null) ? v2 == null : v1.equals(v2))) {
return false;
}
}
return true;
}
public int hashCode() {
int code = 0;
for (Iterator it = keySet().iterator(); it.hasNext();) {
Object key = it.next();
Object value = get(key);
code += ((key == null) ? 0 : key.hashCode()) ^
((value == null) ? 0 : value.hashCode());
}
return code;
}
@Override
public int hashCode() {
int code = 0;
for (Object key : keySet()) {
Object value = get(key);
code += ((key == null) ? 0 : key.hashCode()) ^
((value == null) ? 0 : value.hashCode());
}
return code;
}
// TODO: optimize
public Set entrySet() {
HashMap copy = new HashMap();
for (Iterator it = keySet().iterator(); it.hasNext();) {
Object key = it.next();
copy.put(key, get(key));
}
return Collections.unmodifiableMap(copy).entrySet();
}
// TODO: optimize
@Override
public Set entrySet() {
HashMap copy = new HashMap();
for (Object key : keySet()) {
copy.put(key, get(key));
}
return Collections.unmodifiableMap(copy).entrySet();
}
public Collection values() {
Set keys = keySet();
List values = new ArrayList(keys.size());
for (Iterator it = keys.iterator(); it.hasNext();) {
values.add(get(it.next()));
}
return Collections.unmodifiableCollection(values);
}
@Override
public Collection values() {
Set keys = keySet();
List values = new ArrayList(keys.size());
for (Iterator it = keys.iterator(); it.hasNext();) {
values.add(get(it.next()));
}
return Collections.unmodifiableCollection(values);
}
/*
* @see java.util.AbstractMap#toString
*/
public String toString()
{
StringBuffer sb = new StringBuffer();
sb.append('{');
for (Iterator it = keySet().iterator(); it.hasNext();) {
Object key = it.next();
sb.append(key);
sb.append('=');
sb.append(get(key));
if (it.hasNext()) {
sb.append(", ");
}
}
sb.append('}');
return sb.toString();
}
/*
* @see java.util.AbstractMap#toString
*/
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append('{');
for (Iterator it = keySet().iterator(); it.hasNext();) {
Object key = it.next();
sb.append(key);
sb.append('=');
sb.append(get(key));
if (it.hasNext()) {
sb.append(", ");
}
}
sb.append('}');
return sb.toString();
}
}
@@ -15,12 +15,23 @@
*/
package org.springframework.cglib.beans;
import java.beans.*;
import java.util.*;
import org.springframework.cglib.core.*;
import java.beans.PropertyDescriptor;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.springframework.asm.ClassVisitor;
import org.springframework.asm.Label;
import org.springframework.asm.Type;
import org.springframework.cglib.core.ClassEmitter;
import org.springframework.cglib.core.CodeEmitter;
import org.springframework.cglib.core.Constants;
import org.springframework.cglib.core.EmitUtils;
import org.springframework.cglib.core.MethodInfo;
import org.springframework.cglib.core.ObjectSwitchCallback;
import org.springframework.cglib.core.ReflectUtils;
import org.springframework.cglib.core.Signature;
import org.springframework.cglib.core.TypeUtils;
@SuppressWarnings({"rawtypes", "unchecked"})
class BeanMapEmitter extends ClassEmitter {
@@ -50,7 +61,7 @@ class BeanMapEmitter extends ClassEmitter {
EmitUtils.null_constructor(this);
EmitUtils.factory_method(this, NEW_INSTANCE);
generateConstructor();
Map getters = makePropertyMap(ReflectUtils.getBeanGetters(type));
Map setters = makePropertyMap(ReflectUtils.getBeanSetters(type));
Map allProps = new HashMap();
@@ -79,8 +90,8 @@ class BeanMapEmitter extends ClassEmitter {
private Map makePropertyMap(PropertyDescriptor[] props) {
Map names = new HashMap();
for (int i = 0; i < props.length; i++) {
names.put(props[i].getName(), props[i]);
for (PropertyDescriptor prop : props) {
names.put(prop.getName(), prop);
}
return names;
}
@@ -97,7 +108,7 @@ class BeanMapEmitter extends ClassEmitter {
e.return_value();
e.end_method();
}
private void generateGet(Class type, final Map getters) {
final CodeEmitter e = begin_method(Constants.ACC_PUBLIC, BEAN_MAP_GET, null);
e.load_arg(0);
@@ -105,14 +116,16 @@ class BeanMapEmitter extends ClassEmitter {
e.load_arg(1);
e.checkcast(Constants.TYPE_STRING);
EmitUtils.string_switch(e, getNames(getters), Constants.SWITCH_STYLE_HASH, new ObjectSwitchCallback() {
public void processCase(Object key, Label end) {
@Override
public void processCase(Object key, Label end) {
PropertyDescriptor pd = (PropertyDescriptor)getters.get(key);
MethodInfo method = ReflectUtils.getMethodInfo(pd.getReadMethod());
e.invoke(method);
e.box(method.getSignature().getReturnType());
e.return_value();
}
public void processDefault() {
@Override
public void processDefault() {
e.aconst_null();
e.return_value();
}
@@ -127,7 +140,8 @@ class BeanMapEmitter extends ClassEmitter {
e.load_arg(1);
e.checkcast(Constants.TYPE_STRING);
EmitUtils.string_switch(e, getNames(setters), Constants.SWITCH_STYLE_HASH, new ObjectSwitchCallback() {
public void processCase(Object key, Label end) {
@Override
public void processCase(Object key, Label end) {
PropertyDescriptor pd = (PropertyDescriptor)setters.get(key);
if (pd.getReadMethod() == null) {
e.aconst_null();
@@ -144,7 +158,8 @@ class BeanMapEmitter extends ClassEmitter {
e.invoke(write);
e.return_value();
}
public void processDefault() {
@Override
public void processDefault() {
// fall-through
}
});
@@ -152,7 +167,7 @@ class BeanMapEmitter extends ClassEmitter {
e.return_value();
e.end_method();
}
private void generateKeySet(String[] allNames) {
// static initializer
declare_field(Constants.ACC_STATIC | Constants.ACC_PRIVATE, "keys", FIXED_KEY_SET, null);
@@ -178,12 +193,14 @@ class BeanMapEmitter extends ClassEmitter {
final CodeEmitter e = begin_method(Constants.ACC_PUBLIC, GET_PROPERTY_TYPE, null);
e.load_arg(0);
EmitUtils.string_switch(e, allNames, Constants.SWITCH_STYLE_HASH, new ObjectSwitchCallback() {
public void processCase(Object key, Label end) {
@Override
public void processCase(Object key, Label end) {
PropertyDescriptor pd = (PropertyDescriptor)allProps.get(key);
EmitUtils.load_class(e, Type.getType(pd.getPropertyType()));
e.return_value();
}
public void processDefault() {
@Override
public void processDefault() {
e.aconst_null();
e.return_value();
}
@@ -16,8 +16,11 @@
package org.springframework.cglib.beans;
import java.security.ProtectionDomain;
import org.springframework.cglib.core.*;
import org.springframework.asm.ClassVisitor;
import org.springframework.cglib.core.AbstractClassGenerator;
import org.springframework.cglib.core.KeyFactory;
import org.springframework.cglib.core.ReflectUtils;
/**
* @author Juozas Baliuka
@@ -25,118 +28,123 @@ import org.springframework.asm.ClassVisitor;
@SuppressWarnings({"rawtypes", "unchecked"})
abstract public class BulkBean
{
private static final BulkBeanKey KEY_FACTORY =
(BulkBeanKey)KeyFactory.create(BulkBeanKey.class);
interface BulkBeanKey {
public Object newInstance(String target, String[] getters, String[] setters, String[] types);
}
protected Class target;
protected String[] getters, setters;
protected Class[] types;
protected BulkBean() { }
abstract public void getPropertyValues(Object bean, Object[] values);
abstract public void setPropertyValues(Object bean, Object[] values);
private static final BulkBeanKey KEY_FACTORY =
(BulkBeanKey)KeyFactory.create(BulkBeanKey.class);
public Object[] getPropertyValues(Object bean) {
Object[] values = new Object[getters.length];
getPropertyValues(bean, values);
return values;
}
public Class[] getPropertyTypes() {
return types.clone();
}
public String[] getGetters() {
return getters.clone();
}
public String[] getSetters() {
return setters.clone();
}
interface BulkBeanKey {
public Object newInstance(String target, String[] getters, String[] setters, String[] types);
}
public static BulkBean create(Class target, String[] getters, String[] setters, Class[] types) {
Generator gen = new Generator();
gen.setTarget(target);
gen.setGetters(getters);
gen.setSetters(setters);
gen.setTypes(types);
return gen.create();
}
protected Class target;
protected String[] getters, setters;
protected Class[] types;
public static class Generator extends AbstractClassGenerator {
private static final Source SOURCE = new Source(BulkBean.class.getName());
private Class target;
private String[] getters;
private String[] setters;
private Class[] types;
protected BulkBean() { }
public Generator() {
super(SOURCE);
}
abstract public void getPropertyValues(Object bean, Object[] values);
abstract public void setPropertyValues(Object bean, Object[] values);
public void setTarget(Class target) {
this.target = target;
public Object[] getPropertyValues(Object bean) {
Object[] values = new Object[getters.length];
getPropertyValues(bean, values);
return values;
}
public Class[] getPropertyTypes() {
return types.clone();
}
public String[] getGetters() {
return getters.clone();
}
public String[] getSetters() {
return setters.clone();
}
public static BulkBean create(Class target, String[] getters, String[] setters, Class[] types) {
Generator gen = new Generator();
gen.setTarget(target);
gen.setGetters(getters);
gen.setSetters(setters);
gen.setTypes(types);
return gen.create();
}
public static class Generator extends AbstractClassGenerator {
private static final Source SOURCE = new Source(BulkBean.class.getName());
private Class target;
private String[] getters;
private String[] setters;
private Class[] types;
public Generator() {
super(SOURCE);
}
public void setTarget(Class target) {
this.target = target;
// SPRING PATCH BEGIN
setContextClass(target);
// SPRING PATCH END
}
}
public void setGetters(String[] getters) {
this.getters = getters;
}
public void setGetters(String[] getters) {
this.getters = getters;
}
public void setSetters(String[] setters) {
this.setters = setters;
}
public void setSetters(String[] setters) {
this.setters = setters;
}
public void setTypes(Class[] types) {
this.types = types;
}
public void setTypes(Class[] types) {
this.types = types;
}
protected ClassLoader getDefaultClassLoader() {
return target.getClassLoader();
}
@Override
protected ClassLoader getDefaultClassLoader() {
return target.getClassLoader();
}
protected ProtectionDomain getProtectionDomain() {
return ReflectUtils.getProtectionDomain(target);
}
@Override
protected ProtectionDomain getProtectionDomain() {
return ReflectUtils.getProtectionDomain(target);
}
public BulkBean create() {
setNamePrefix(target.getName());
String targetClassName = target.getName();
String[] typeClassNames = ReflectUtils.getNames(types);
Object key = KEY_FACTORY.newInstance(targetClassName, getters, setters, typeClassNames);
return (BulkBean)super.create(key);
}
public BulkBean create() {
setNamePrefix(target.getName());
String targetClassName = target.getName();
String[] typeClassNames = ReflectUtils.getNames(types);
Object key = KEY_FACTORY.newInstance(targetClassName, getters, setters, typeClassNames);
return (BulkBean)super.create(key);
}
public void generateClass(ClassVisitor v) throws Exception {
new BulkBeanEmitter(v, getClassName(), target, getters, setters, types);
}
@Override
public void generateClass(ClassVisitor v) throws Exception {
new BulkBeanEmitter(v, getClassName(), target, getters, setters, types);
}
protected Object firstInstance(Class type) {
BulkBean instance = (BulkBean)ReflectUtils.newInstance(type);
instance.target = target;
int length = getters.length;
instance.getters = new String[length];
System.arraycopy(getters, 0, instance.getters, 0, length);
instance.setters = new String[length];
System.arraycopy(setters, 0, instance.setters, 0, length);
instance.types = new Class[types.length];
System.arraycopy(types, 0, instance.types, 0, types.length);
@Override
protected Object firstInstance(Class type) {
BulkBean instance = (BulkBean)ReflectUtils.newInstance(type);
instance.target = target;
return instance;
}
int length = getters.length;
instance.getters = new String[length];
System.arraycopy(getters, 0, instance.getters, 0, length);
protected Object nextInstance(Object instance) {
return instance;
}
}
instance.setters = new String[length];
System.arraycopy(setters, 0, instance.setters, 0, length);
instance.types = new Class[types.length];
System.arraycopy(types, 0, instance.types, 0, types.length);
return instance;
}
@Override
protected Object nextInstance(Object instance) {
return instance;
}
}
}
@@ -17,9 +17,19 @@ package org.springframework.cglib.beans;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import org.springframework.cglib.core.*;
import org.springframework.asm.ClassVisitor;
import org.springframework.asm.Type;
import org.springframework.cglib.core.Block;
import org.springframework.cglib.core.ClassEmitter;
import org.springframework.cglib.core.CodeEmitter;
import org.springframework.cglib.core.Constants;
import org.springframework.cglib.core.EmitUtils;
import org.springframework.cglib.core.Local;
import org.springframework.cglib.core.MethodInfo;
import org.springframework.cglib.core.ReflectUtils;
import org.springframework.cglib.core.Signature;
import org.springframework.cglib.core.TypeUtils;
@SuppressWarnings({"rawtypes", "unchecked"})
class BulkBeanEmitter extends ClassEmitter {
@@ -33,7 +43,7 @@ class BulkBeanEmitter extends ClassEmitter {
TypeUtils.parseType("org.springframework.cglib.beans.BulkBean");
private static final Type BULK_BEAN_EXCEPTION =
TypeUtils.parseType("org.springframework.cglib.beans.BulkBeanException");
public BulkBeanEmitter(ClassVisitor v,
String className,
Class target,
@@ -116,7 +126,7 @@ class BulkBeanEmitter extends ClassEmitter {
}
e.end_method();
}
private static void validate(Class target,
String[] getters,
String[] setters,
@@ -20,7 +20,7 @@ public class BulkBeanException extends RuntimeException
{
private int index;
private Throwable cause;
public BulkBeanException(String message, int index) {
super(message);
this.index = index;
@@ -35,8 +35,9 @@ public class BulkBeanException extends RuntimeException
public int getIndex() {
return index;
}
public Throwable getCause() {
@Override
public Throwable getCause() {
return cause;
}
}
@@ -15,7 +15,12 @@
*/
package org.springframework.cglib.beans;
import java.util.*;
import java.util.AbstractSet;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
@SuppressWarnings({"rawtypes", "unchecked"})
public /* need it for class loading */ class FixedKeySet extends AbstractSet {
@@ -27,11 +32,13 @@ public /* need it for class loading */ class FixedKeySet extends AbstractSet {
set = Collections.unmodifiableSet(new HashSet(Arrays.asList(keys)));
}
public Iterator iterator() {
@Override
public Iterator iterator() {
return set.iterator();
}
public int size() {
@Override
public int size() {
return size;
}
}
@@ -18,115 +18,129 @@ package org.springframework.cglib.beans;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.security.ProtectionDomain;
import org.springframework.cglib.core.*;
import org.springframework.asm.ClassVisitor;
import org.springframework.asm.Type;
import org.springframework.cglib.core.AbstractClassGenerator;
import org.springframework.cglib.core.ClassEmitter;
import org.springframework.cglib.core.CodeEmitter;
import org.springframework.cglib.core.Constants;
import org.springframework.cglib.core.EmitUtils;
import org.springframework.cglib.core.MethodInfo;
import org.springframework.cglib.core.ReflectUtils;
import org.springframework.cglib.core.Signature;
import org.springframework.cglib.core.TypeUtils;
/**
* @author Chris Nokleberg
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public class ImmutableBean
{
private static final Type ILLEGAL_STATE_EXCEPTION =
TypeUtils.parseType("IllegalStateException");
private static final Signature CSTRUCT_OBJECT =
TypeUtils.parseConstructor("Object");
private static final Class[] OBJECT_CLASSES = { Object.class };
private static final String FIELD_NAME = "CGLIB$RWBean";
private static final Type ILLEGAL_STATE_EXCEPTION =
TypeUtils.parseType("IllegalStateException");
private static final Signature CSTRUCT_OBJECT =
TypeUtils.parseConstructor("Object");
private static final Class[] OBJECT_CLASSES = { Object.class };
private static final String FIELD_NAME = "CGLIB$RWBean";
private ImmutableBean() {
}
private ImmutableBean() {
}
public static Object create(Object bean) {
Generator gen = new Generator();
gen.setBean(bean);
return gen.create();
}
public static Object create(Object bean) {
Generator gen = new Generator();
gen.setBean(bean);
return gen.create();
}
public static class Generator extends AbstractClassGenerator {
private static final Source SOURCE = new Source(ImmutableBean.class.getName());
private Object bean;
private Class target;
public static class Generator extends AbstractClassGenerator {
private static final Source SOURCE = new Source(ImmutableBean.class.getName());
private Object bean;
private Class target;
public Generator() {
super(SOURCE);
}
public Generator() {
super(SOURCE);
}
public void setBean(Object bean) {
this.bean = bean;
target = bean.getClass();
public void setBean(Object bean) {
this.bean = bean;
target = bean.getClass();
// SPRING PATCH BEGIN
setContextClass(target);
// SPRING PATCH END
}
}
protected ClassLoader getDefaultClassLoader() {
return target.getClassLoader();
}
@Override
protected ClassLoader getDefaultClassLoader() {
return target.getClassLoader();
}
protected ProtectionDomain getProtectionDomain() {
return ReflectUtils.getProtectionDomain(target);
}
@Override
protected ProtectionDomain getProtectionDomain() {
return ReflectUtils.getProtectionDomain(target);
}
public Object create() {
String name = target.getName();
setNamePrefix(name);
return super.create(name);
}
public Object create() {
String name = target.getName();
setNamePrefix(name);
return super.create(name);
}
public void generateClass(ClassVisitor v) {
Type targetType = Type.getType(target);
ClassEmitter ce = new ClassEmitter(v);
ce.begin_class(Constants.V1_8,
Constants.ACC_PUBLIC,
getClassName(),
targetType,
null,
Constants.SOURCE_FILE);
@Override
public void generateClass(ClassVisitor v) {
Type targetType = Type.getType(target);
ClassEmitter ce = new ClassEmitter(v);
ce.begin_class(Constants.V1_8,
Constants.ACC_PUBLIC,
getClassName(),
targetType,
null,
Constants.SOURCE_FILE);
ce.declare_field(Constants.ACC_FINAL | Constants.ACC_PRIVATE, FIELD_NAME, targetType, null);
ce.declare_field(Constants.ACC_FINAL | Constants.ACC_PRIVATE, FIELD_NAME, targetType, null);
CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, CSTRUCT_OBJECT, null);
e.load_this();
e.super_invoke_constructor();
e.load_this();
e.load_arg(0);
e.checkcast(targetType);
e.putfield(FIELD_NAME);
e.return_value();
e.end_method();
CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, CSTRUCT_OBJECT, null);
e.load_this();
e.super_invoke_constructor();
e.load_this();
e.load_arg(0);
e.checkcast(targetType);
e.putfield(FIELD_NAME);
e.return_value();
e.end_method();
PropertyDescriptor[] descriptors = ReflectUtils.getBeanProperties(target);
Method[] getters = ReflectUtils.getPropertyMethods(descriptors, true, false);
Method[] setters = ReflectUtils.getPropertyMethods(descriptors, false, true);
PropertyDescriptor[] descriptors = ReflectUtils.getBeanProperties(target);
Method[] getters = ReflectUtils.getPropertyMethods(descriptors, true, false);
Method[] setters = ReflectUtils.getPropertyMethods(descriptors, false, true);
for (int i = 0; i < getters.length; i++) {
MethodInfo getter = ReflectUtils.getMethodInfo(getters[i]);
e = EmitUtils.begin_method(ce, getter, Constants.ACC_PUBLIC);
e.load_this();
e.getfield(FIELD_NAME);
e.invoke(getter);
e.return_value();
e.end_method();
}
for (Method getter2 : getters) {
MethodInfo getter = ReflectUtils.getMethodInfo(getter2);
e = EmitUtils.begin_method(ce, getter, Constants.ACC_PUBLIC);
e.load_this();
e.getfield(FIELD_NAME);
e.invoke(getter);
e.return_value();
e.end_method();
}
for (int i = 0; i < setters.length; i++) {
MethodInfo setter = ReflectUtils.getMethodInfo(setters[i]);
e = EmitUtils.begin_method(ce, setter, Constants.ACC_PUBLIC);
e.throw_exception(ILLEGAL_STATE_EXCEPTION, "Bean is immutable");
e.end_method();
}
for (Method setter2 : setters) {
MethodInfo setter = ReflectUtils.getMethodInfo(setter2);
e = EmitUtils.begin_method(ce, setter, Constants.ACC_PUBLIC);
e.throw_exception(ILLEGAL_STATE_EXCEPTION, "Bean is immutable");
e.end_method();
}
ce.end_class();
}
ce.end_class();
}
protected Object firstInstance(Class type) {
return ReflectUtils.newInstance(type, OBJECT_CLASSES, new Object[]{ bean });
}
@Override
protected Object firstInstance(Class type) {
return ReflectUtils.newInstance(type, OBJECT_CLASSES, new Object[]{ bean });
}
// TODO: optimize
protected Object nextInstance(Object instance) {
return firstInstance(instance.getClass());
}
}
// TODO: optimize
@Override
protected Object nextInstance(Object instance) {
return firstInstance(instance.getClass());
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,6 +36,7 @@ import org.springframework.util.ReflectionUtils;
*
* @author Juergen Hoeller
* @author Rossen Stoyanchev
* @author Sam Brannen
* @since 4.2.3
*/
public final class MethodIntrospector {
@@ -75,6 +76,7 @@ public final class MethodIntrospector {
if (result != null) {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
if (bridgedMethod == specificMethod || bridgedMethod == method ||
bridgedMethod.equals(specificMethod) || bridgedMethod.equals(method) ||
metadataLookup.inspect(bridgedMethod) == null) {
methodMap.put(specificMethod, result);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -98,7 +98,9 @@ public final class ReactiveTypeDescriptor {
*/
public Object getEmptyValue() {
Assert.state(this.emptySupplier != null, "Empty values not supported");
return this.emptySupplier.get();
Object emptyValue = this.emptySupplier.get();
Assert.notNull(emptyValue, "Invalid null return value from emptySupplier");
return emptyValue;
}
/**
@@ -130,7 +132,7 @@ public final class ReactiveTypeDescriptor {
/**
* Descriptor for a reactive type that can produce 0..N values.
* Descriptor for a reactive type that can produce {@code 0..N} values.
* @param type the reactive type
* @param emptySupplier a supplier of an empty-value instance of the reactive type
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -138,7 +138,7 @@ public class ResolvableType implements Serializable {
/**
* Private constructor used to create a new {@link ResolvableType} for cache key purposes,
* Private constructor used to create a new {@code ResolvableType} for cache key purposes,
* with no upfront resolution.
*/
private ResolvableType(
@@ -153,7 +153,7 @@ public class ResolvableType implements Serializable {
}
/**
* Private constructor used to create a new {@link ResolvableType} for cache value purposes,
* Private constructor used to create a new {@code ResolvableType} for cache value purposes,
* with upfront resolution and a pre-calculated hash.
* @since 4.2
*/
@@ -169,7 +169,7 @@ public class ResolvableType implements Serializable {
}
/**
* Private constructor used to create a new {@link ResolvableType} for uncached purposes,
* Private constructor used to create a new {@code ResolvableType} for uncached purposes,
* with upfront resolution but lazily calculated hash.
*/
private ResolvableType(Type type, @Nullable TypeProvider typeProvider,
@@ -184,7 +184,7 @@ public class ResolvableType implements Serializable {
}
/**
* Private constructor used to create a new {@link ResolvableType} on a {@link Class} basis.
* Private constructor used to create a new {@code ResolvableType} on a {@link Class} basis.
* <p>Avoids all {@code instanceof} checks in order to create a straight {@link Class} wrapper.
* @since 4.2
*/
@@ -223,7 +223,7 @@ public class ResolvableType implements Serializable {
/**
* Return the underlying source of the resolvable type. Will return a {@link Field},
* {@link MethodParameter} or {@link Type} depending on how the {@link ResolvableType}
* {@link MethodParameter} or {@link Type} depending on how the {@code ResolvableType}
* was constructed. This method is primarily to provide access to additional type
* information or meta-data that alternative JVM languages may provide.
*/
@@ -341,13 +341,14 @@ public class ResolvableType implements Serializable {
}
}
if (ourResolved == null) {
ourResolved = resolve(Object.class);
ourResolved = toClass();
}
Class<?> otherResolved = other.toClass();
// We need an exact type match for generics
// List<CharSequence> is not assignable from List<String>
if (exactMatch ? !ourResolved.equals(otherResolved) : !ClassUtils.isAssignable(ourResolved, otherResolved)) {
if (exactMatch ? !ourResolved.equals(otherResolved) :
!ClassUtils.isAssignable(ourResolved, otherResolved)) {
return false;
}
@@ -358,13 +359,15 @@ public class ResolvableType implements Serializable {
if (ourGenerics.length != typeGenerics.length) {
return false;
}
if (matchedBefore == null) {
matchedBefore = new IdentityHashMap<>(1);
}
matchedBefore.put(this.type, other.type);
for (int i = 0; i < ourGenerics.length; i++) {
if (!ourGenerics[i].isAssignableFrom(typeGenerics[i], matchedBefore)) {
return false;
if (ourGenerics.length > 0) {
if (matchedBefore == null) {
matchedBefore = new IdentityHashMap<>(1);
}
matchedBefore.put(this.type, other.type);
for (int i = 0; i < ourGenerics.length; i++) {
if (!ourGenerics[i].isAssignableFrom(typeGenerics[i], matchedBefore)) {
return false;
}
}
}
}
@@ -429,12 +432,12 @@ public class ResolvableType implements Serializable {
}
/**
* Return this type as a {@link ResolvableType} of the specified class. Searches
* Return this type as a {@code ResolvableType} of the specified class. Searches
* {@link #getSuperType() supertype} and {@link #getInterfaces() interface}
* hierarchies to find a match, returning {@link #NONE} if this type does not
* implement or extend the specified class.
* @param type the required type (typically narrowed)
* @return a {@link ResolvableType} representing this object as the specified
* @return a {@code ResolvableType} representing this object as the specified
* type, or {@link #NONE} if not resolvable as that type
* @see #asCollection()
* @see #asMap()
@@ -459,9 +462,9 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} representing the direct supertype of this type.
* Return a {@code ResolvableType} representing the direct supertype of this type.
* <p>If no supertype is available this method returns {@link #NONE}.
* <p>Note: The resulting {@link ResolvableType} instance may not be {@link Serializable}.
* <p>Note: The resulting {@code ResolvableType} instance may not be {@link Serializable}.
* @see #getInterfaces()
*/
public ResolvableType getSuperType() {
@@ -488,10 +491,10 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} array representing the direct interfaces
* Return a {@code ResolvableType} array representing the direct interfaces
* implemented by this type. If this type does not implement any interfaces an
* empty array is returned.
* <p>Note: The resulting {@link ResolvableType} instances may not be {@link Serializable}.
* <p>Note: The resulting {@code ResolvableType} instances may not be {@link Serializable}.
* @see #getSuperType()
*/
public ResolvableType[] getInterfaces() {
@@ -621,17 +624,17 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified nesting level.
* Return a {@code ResolvableType} for the specified nesting level.
* <p>See {@link #getNested(int, Map)} for details.
* @param nestingLevel the nesting level
* @return the {@link ResolvableType} type, or {@code #NONE}
* @return the {@code ResolvableType} type, or {@code #NONE}
*/
public ResolvableType getNested(int nestingLevel) {
return getNested(nestingLevel, null);
}
/**
* Return a {@link ResolvableType} for the specified nesting level.
* Return a {@code ResolvableType} for the specified nesting level.
* <p>The nesting level refers to the specific generic parameter that should be returned.
* A nesting level of 1 indicates this type; 2 indicates the first nested generic;
* 3 the second; and so on. For example, given {@code List<Set<Integer>>} level 1 refers
@@ -648,7 +651,7 @@ public class ResolvableType implements Serializable {
* current type, 2 for the first nested generic, 3 for the second and so on
* @param typeIndexesPerLevel a map containing the generic index for a given
* nesting level (may be {@code null})
* @return a {@link ResolvableType} for the nested level, or {@link #NONE}
* @return a {@code ResolvableType} for the nested level, or {@link #NONE}
*/
public ResolvableType getNested(int nestingLevel, @Nullable Map<Integer, Integer> typeIndexesPerLevel) {
ResolvableType result = this;
@@ -670,7 +673,7 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} representing the generic parameter for the
* Return a {@code ResolvableType} representing the generic parameter for the
* given indexes. Indexes are zero based; for example given the type
* {@code Map<Integer, List<String>>}, {@code getGeneric(0)} will access the
* {@code Integer}. Nested generics can be accessed by specifying multiple indexes;
@@ -680,7 +683,7 @@ public class ResolvableType implements Serializable {
* <p>If no generic is available at the specified indexes {@link #NONE} is returned.
* @param indexes the indexes that refer to the generic parameter
* (may be omitted to return the first generic)
* @return a {@link ResolvableType} for the specified generic, or {@link #NONE}
* @return a {@code ResolvableType} for the specified generic, or {@link #NONE}
* @see #hasGenerics()
* @see #getGenerics()
* @see #resolveGeneric(int...)
@@ -703,12 +706,12 @@ public class ResolvableType implements Serializable {
}
/**
* Return an array of {@link ResolvableType ResolvableTypes} representing the generic parameters of
* Return an array of {@code ResolvableType ResolvableTypes} representing the generic parameters of
* this type. If no generics are available an empty array is returned. If you need to
* access a specific generic consider using the {@link #getGeneric(int...)} method as
* it allows access to nested generics and protects against
* {@code IndexOutOfBoundsExceptions}.
* @return an array of {@link ResolvableType ResolvableTypes} representing the generic parameters
* @return an array of {@code ResolvableType ResolvableTypes} representing the generic parameters
* (never {@code null})
* @see #hasGenerics()
* @see #getGeneric(int...)
@@ -747,7 +750,7 @@ public class ResolvableType implements Serializable {
* Convenience method that will {@link #getGenerics() get} and
* {@link #resolve() resolve} generic parameters.
* @return an array of resolved generic parameters (the resulting array
* will never be {@code null}, but it may contain {@code null} elements})
* will never be {@code null}, but it may contain {@code null} elements)
* @see #getGenerics()
* @see #resolve()
*/
@@ -780,7 +783,7 @@ public class ResolvableType implements Serializable {
/**
* Convenience method that will {@link #getGeneric(int...) get} and
* {@link #resolve() resolve} a specific generic parameters.
* {@link #resolve() resolve} a specific generic parameter.
* @param indexes the indexes that refer to the generic parameter
* (may be omitted to return the first generic)
* @return a resolved {@link Class} or {@code null}
@@ -842,7 +845,7 @@ public class ResolvableType implements Serializable {
/**
* Resolve this type by a single level, returning the resolved value or {@link #NONE}.
* <p>Note: The returned {@link ResolvableType} should only be used as an intermediary
* <p>Note: The returned {@code ResolvableType} should only be used as an intermediary
* as it cannot be serialized.
*/
ResolvableType resolveType() {
@@ -964,7 +967,7 @@ public class ResolvableType implements Serializable {
}
/**
* Adapts this {@link ResolvableType} to a {@link VariableResolver}.
* Adapts this {@code ResolvableType} to a {@link VariableResolver}.
*/
@Nullable
VariableResolver asVariableResolver() {
@@ -1011,12 +1014,12 @@ public class ResolvableType implements Serializable {
// Factory methods
/**
* Return a {@link ResolvableType} for the specified {@link Class},
* Return a {@code ResolvableType} for the specified {@link Class},
* using the full generic type information for assignability checks.
* <p>For example: {@code ResolvableType.forClass(MyArrayList.class)}.
* @param clazz the class to introspect ({@code null} is semantically
* equivalent to {@code Object.class} for typical use cases here)
* @return a {@link ResolvableType} for the specified class
* @return a {@code ResolvableType} for the specified class
* @see #forClass(Class, Class)
* @see #forClassWithGenerics(Class, Class...)
*/
@@ -1025,13 +1028,13 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link Class},
* Return a {@code ResolvableType} for the specified {@link Class},
* doing assignability checks against the raw class only (analogous to
* {@link Class#isAssignableFrom}, which this serves as a wrapper for).
* <p>For example: {@code ResolvableType.forRawClass(List.class)}.
* @param clazz the class to introspect ({@code null} is semantically
* equivalent to {@code Object.class} for typical use cases here)
* @return a {@link ResolvableType} for the specified class
* @return a {@code ResolvableType} for the specified class
* @since 4.2
* @see #forClass(Class)
* @see #getRawClass()
@@ -1055,12 +1058,12 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified base type
* Return a {@code ResolvableType} for the specified base type
* (interface or base class) with a given implementation class.
* <p>For example: {@code ResolvableType.forClass(List.class, MyArrayList.class)}.
* @param baseType the base type (must not be {@code null})
* @param implementationClass the implementation class
* @return a {@link ResolvableType} for the specified base type backed by the
* @return a {@code ResolvableType} for the specified base type backed by the
* given implementation class
* @see #forClass(Class)
* @see #forClassWithGenerics(Class, Class...)
@@ -1072,10 +1075,10 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link Class} with pre-declared generics.
* Return a {@code ResolvableType} for the specified {@link Class} with pre-declared generics.
* @param clazz the class (or interface) to introspect
* @param generics the generics of the class
* @return a {@link ResolvableType} for the specific class and generics
* @return a {@code ResolvableType} for the specific class and generics
* @see #forClassWithGenerics(Class, ResolvableType...)
*/
public static ResolvableType forClassWithGenerics(Class<?> clazz, Class<?>... generics) {
@@ -1089,10 +1092,10 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link Class} with pre-declared generics.
* Return a {@code ResolvableType} for the specified {@link Class} with pre-declared generics.
* @param clazz the class (or interface) to introspect
* @param generics the generics of the class
* @return a {@link ResolvableType} for the specific class and generics
* @return a {@code ResolvableType} for the specific class and generics
* @see #forClassWithGenerics(Class, Class...)
*/
public static ResolvableType forClassWithGenerics(Class<?> clazz, ResolvableType... generics) {
@@ -1113,12 +1116,12 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified instance. The instance does not
* Return a {@code ResolvableType} for the specified instance. The instance does not
* convey generic information but if it implements {@link ResolvableTypeProvider} a
* more precise {@link ResolvableType} can be used than the simple one based on
* more precise {@code ResolvableType} can be used than the simple one based on
* the {@link #forClass(Class) Class instance}.
* @param instance the instance (possibly {@code null})
* @return a {@link ResolvableType} for the specified instance,
* @return a {@code ResolvableType} for the specified instance,
* or {@code NONE} for {@code null}
* @since 4.2
* @see ResolvableTypeProvider
@@ -1134,9 +1137,9 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link Field}.
* Return a {@code ResolvableType} for the specified {@link Field}.
* @param field the source field
* @return a {@link ResolvableType} for the specified field
* @return a {@code ResolvableType} for the specified field
* @see #forField(Field, Class)
*/
public static ResolvableType forField(Field field) {
@@ -1145,13 +1148,13 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link Field} with a given
* Return a {@code ResolvableType} for the specified {@link Field} with a given
* implementation.
* <p>Use this variant when the class that declares the field includes generic
* parameter variables that are satisfied by the implementation class.
* @param field the source field
* @param implementationClass the implementation class
* @return a {@link ResolvableType} for the specified field
* @return a {@code ResolvableType} for the specified field
* @see #forField(Field)
*/
public static ResolvableType forField(Field field, Class<?> implementationClass) {
@@ -1161,13 +1164,13 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link Field} with a given
* Return a {@code ResolvableType} for the specified {@link Field} with a given
* implementation.
* <p>Use this variant when the class that declares the field includes generic
* parameter variables that are satisfied by the implementation type.
* @param field the source field
* @param implementationType the implementation type
* @return a {@link ResolvableType} for the specified field
* @return a {@code ResolvableType} for the specified field
* @see #forField(Field)
*/
public static ResolvableType forField(Field field, @Nullable ResolvableType implementationType) {
@@ -1178,7 +1181,7 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link Field} with the
* Return a {@code ResolvableType} for the specified {@link Field} with the
* given nesting level.
* @param field the source field
* @param nestingLevel the nesting level (1 for the outer level; 2 for a nested
@@ -1191,7 +1194,7 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link Field} with a given
* Return a {@code ResolvableType} for the specified {@link Field} with a given
* implementation and the given nesting level.
* <p>Use this variant when the class that declares the field includes generic
* parameter variables that are satisfied by the implementation class.
@@ -1199,7 +1202,7 @@ public class ResolvableType implements Serializable {
* @param nestingLevel the nesting level (1 for the outer level; 2 for a nested
* generic type; etc)
* @param implementationClass the implementation class
* @return a {@link ResolvableType} for the specified field
* @return a {@code ResolvableType} for the specified field
* @see #forField(Field)
*/
public static ResolvableType forField(Field field, int nestingLevel, @Nullable Class<?> implementationClass) {
@@ -1209,10 +1212,10 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link Constructor} parameter.
* Return a {@code ResolvableType} for the specified {@link Constructor} parameter.
* @param constructor the source constructor (must not be {@code null})
* @param parameterIndex the parameter index
* @return a {@link ResolvableType} for the specified constructor parameter
* @return a {@code ResolvableType} for the specified constructor parameter
* @see #forConstructorParameter(Constructor, int, Class)
*/
public static ResolvableType forConstructorParameter(Constructor<?> constructor, int parameterIndex) {
@@ -1221,14 +1224,14 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link Constructor} parameter
* Return a {@code ResolvableType} for the specified {@link Constructor} parameter
* with a given implementation. Use this variant when the class that declares the
* constructor includes generic parameter variables that are satisfied by the
* implementation class.
* @param constructor the source constructor (must not be {@code null})
* @param parameterIndex the parameter index
* @param implementationClass the implementation class
* @return a {@link ResolvableType} for the specified constructor parameter
* @return a {@code ResolvableType} for the specified constructor parameter
* @see #forConstructorParameter(Constructor, int)
*/
public static ResolvableType forConstructorParameter(Constructor<?> constructor, int parameterIndex,
@@ -1240,9 +1243,9 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link Method} return type.
* Return a {@code ResolvableType} for the specified {@link Method} return type.
* @param method the source for the method return type
* @return a {@link ResolvableType} for the specified method return
* @return a {@code ResolvableType} for the specified method return
* @see #forMethodReturnType(Method, Class)
*/
public static ResolvableType forMethodReturnType(Method method) {
@@ -1251,12 +1254,12 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link Method} return type.
* Return a {@code ResolvableType} for the specified {@link Method} return type.
* <p>Use this variant when the class that declares the method includes generic
* parameter variables that are satisfied by the implementation class.
* @param method the source for the method return type
* @param implementationClass the implementation class
* @return a {@link ResolvableType} for the specified method return
* @return a {@code ResolvableType} for the specified method return
* @see #forMethodReturnType(Method)
*/
public static ResolvableType forMethodReturnType(Method method, Class<?> implementationClass) {
@@ -1266,10 +1269,10 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link Method} parameter.
* Return a {@code ResolvableType} for the specified {@link Method} parameter.
* @param method the source method (must not be {@code null})
* @param parameterIndex the parameter index
* @return a {@link ResolvableType} for the specified method parameter
* @return a {@code ResolvableType} for the specified method parameter
* @see #forMethodParameter(Method, int, Class)
* @see #forMethodParameter(MethodParameter)
*/
@@ -1279,13 +1282,13 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link Method} parameter with a
* Return a {@code ResolvableType} for the specified {@link Method} parameter with a
* given implementation. Use this variant when the class that declares the method
* includes generic parameter variables that are satisfied by the implementation class.
* @param method the source method (must not be {@code null})
* @param parameterIndex the parameter index
* @param implementationClass the implementation class
* @return a {@link ResolvableType} for the specified method parameter
* @return a {@code ResolvableType} for the specified method parameter
* @see #forMethodParameter(Method, int, Class)
* @see #forMethodParameter(MethodParameter)
*/
@@ -1296,9 +1299,9 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link MethodParameter}.
* Return a {@code ResolvableType} for the specified {@link MethodParameter}.
* @param methodParameter the source method parameter (must not be {@code null})
* @return a {@link ResolvableType} for the specified method parameter
* @return a {@code ResolvableType} for the specified method parameter
* @see #forMethodParameter(Method, int)
*/
public static ResolvableType forMethodParameter(MethodParameter methodParameter) {
@@ -1306,12 +1309,12 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link MethodParameter} with a
* Return a {@code ResolvableType} for the specified {@link MethodParameter} with a
* given implementation type. Use this variant when the class that declares the method
* includes generic parameter variables that are satisfied by the implementation type.
* @param methodParameter the source method parameter (must not be {@code null})
* @param implementationType the implementation type
* @return a {@link ResolvableType} for the specified method parameter
* @return a {@code ResolvableType} for the specified method parameter
* @see #forMethodParameter(MethodParameter)
*/
public static ResolvableType forMethodParameter(MethodParameter methodParameter,
@@ -1326,11 +1329,11 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link MethodParameter},
* Return a {@code ResolvableType} for the specified {@link MethodParameter},
* overriding the target type to resolve with a specific given type.
* @param methodParameter the source method parameter (must not be {@code null})
* @param targetType the type to resolve (a part of the method parameter's type)
* @return a {@link ResolvableType} for the specified method parameter
* @return a {@code ResolvableType} for the specified method parameter
* @see #forMethodParameter(Method, int)
*/
public static ResolvableType forMethodParameter(MethodParameter methodParameter, @Nullable Type targetType) {
@@ -1339,13 +1342,13 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link MethodParameter} at
* Return a {@code ResolvableType} for the specified {@link MethodParameter} at
* a specific nesting level, overriding the target type to resolve with a specific
* given type.
* @param methodParameter the source method parameter (must not be {@code null})
* @param targetType the type to resolve (a part of the method parameter's type)
* @param nestingLevel the nesting level to use
* @return a {@link ResolvableType} for the specified method parameter
* @return a {@code ResolvableType} for the specified method parameter
* @since 5.2
* @see #forMethodParameter(Method, int)
*/
@@ -1358,9 +1361,9 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} as an array of the specified {@code componentType}.
* Return a {@code ResolvableType} as an array of the specified {@code componentType}.
* @param componentType the component type
* @return a {@link ResolvableType} as an array of the specified component type
* @return a {@code ResolvableType} as an array of the specified component type
*/
public static ResolvableType forArrayComponent(ResolvableType componentType) {
Assert.notNull(componentType, "Component type must not be null");
@@ -1369,10 +1372,10 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link Type}.
* <p>Note: The resulting {@link ResolvableType} instance may not be {@link Serializable}.
* Return a {@code ResolvableType} for the specified {@link Type}.
* <p>Note: The resulting {@code ResolvableType} instance may not be {@link Serializable}.
* @param type the source type (potentially {@code null})
* @return a {@link ResolvableType} for the specified {@link Type}
* @return a {@code ResolvableType} for the specified {@link Type}
* @see #forType(Type, ResolvableType)
*/
public static ResolvableType forType(@Nullable Type type) {
@@ -1380,12 +1383,12 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link Type} backed by the given
* Return a {@code ResolvableType} for the specified {@link Type} backed by the given
* owner type.
* <p>Note: The resulting {@link ResolvableType} instance may not be {@link Serializable}.
* <p>Note: The resulting {@code ResolvableType} instance may not be {@link Serializable}.
* @param type the source type or {@code null}
* @param owner the owner type used to resolve variables
* @return a {@link ResolvableType} for the specified {@link Type} and owner
* @return a {@code ResolvableType} for the specified {@link Type} and owner
* @see #forType(Type)
*/
public static ResolvableType forType(@Nullable Type type, @Nullable ResolvableType owner) {
@@ -1398,10 +1401,10 @@ public class ResolvableType implements Serializable {
/**
* Return a {@link ResolvableType} for the specified {@link ParameterizedTypeReference}.
* <p>Note: The resulting {@link ResolvableType} instance may not be {@link Serializable}.
* Return a {@code ResolvableType} for the specified {@link ParameterizedTypeReference}.
* <p>Note: The resulting {@code ResolvableType} instance may not be {@link Serializable}.
* @param typeReference the reference to obtain the source type from
* @return a {@link ResolvableType} for the specified {@link ParameterizedTypeReference}
* @return a {@code ResolvableType} for the specified {@link ParameterizedTypeReference}
* @since 4.3.12
* @see #forType(Type)
*/
@@ -1410,23 +1413,23 @@ public class ResolvableType implements Serializable {
}
/**
* Return a {@link ResolvableType} for the specified {@link Type} backed by a given
* Return a {@code ResolvableType} for the specified {@link Type} backed by a given
* {@link VariableResolver}.
* @param type the source type or {@code null}
* @param variableResolver the variable resolver or {@code null}
* @return a {@link ResolvableType} for the specified {@link Type} and {@link VariableResolver}
* @return a {@code ResolvableType} for the specified {@link Type} and {@link VariableResolver}
*/
static ResolvableType forType(@Nullable Type type, @Nullable VariableResolver variableResolver) {
return forType(type, null, variableResolver);
}
/**
* Return a {@link ResolvableType} for the specified {@link Type} backed by a given
* Return a {@code ResolvableType} for the specified {@link Type} backed by a given
* {@link VariableResolver}.
* @param type the source type or {@code null}
* @param typeProvider the type provider or {@code null}
* @param variableResolver the variable resolver or {@code null}
* @return a {@link ResolvableType} for the specified {@link Type} and {@link VariableResolver}
* @return a {@code ResolvableType} for the specified {@link Type} and {@link VariableResolver}
*/
static ResolvableType forType(
@Nullable Type type, @Nullable TypeProvider typeProvider, @Nullable VariableResolver variableResolver) {

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