Compare commits

...

100 Commits

Author SHA1 Message Date
Spring Builds f8bad939c3 Release v6.0.17 2024-02-15 12:23:22 +00:00
Juergen Hoeller ee100cfba7 Upgrade to Mockito 5.10, Mutiny 1.10, Undertow 2.3.11 2024-02-15 10:49:54 +01:00
Juergen Hoeller aaf90f5a96 Upgrade to Reactor 2022.0.16
Includes SLF4J 2.0.12, Groovy 4.0.18, Jetty 11.0.20, Netty 4.1.107, OpenPDF 1.3.40
2024-02-15 10:28:18 +01:00
Simon Baslé 70be04ba81 Infer reflection hints for Jackson annotations builder attributes
This notably enables Jackson to reflectively call a user-provided
builder class and invoke its declared methods (setters and build) in
a native app.

See gh-32238
Closes gh-32257
2024-02-13 15:16:04 +01:00
Juergen Hoeller b976ee3f67 Consistent Lock field declaration (instead of ReentrantLock field type)
(cherry picked from commit b4153618a4)
2024-02-13 11:14:10 +01:00
rstoyanchev b11ff96652 Update user info pattern
Closes gh-32211
2024-02-13 07:14:12 +00:00
Arjen Poutsma 667e4e7531 Implement MatchableHandlerMapping in RouterFunctionMapping
See gh-32221
Closes gh-32222
2024-02-08 12:25:00 +01:00
Juergen Hoeller 5fd9fab0ce Polishing (backported from main) 2024-02-07 23:40:11 +01:00
Juergen Hoeller 95a8646309 Polishing 2024-02-06 17:06:05 +01:00
Juergen Hoeller 5434edd726 Avoid sendError call when response committed already (Tomcat 10.1.16)
Closes gh-32206

(cherry picked from commit 4ed337247c)
2024-02-06 16:48:15 +01:00
Brian Clozel 5f13ea95fb Polish
See gh-32197
2024-02-05 11:15:49 +01:00
Patrick Strawderman 74bb42b78f Optimize Map methods in ServletAttributesMap
ServletAttributesMap inherited default implementations of the size
and isEmpty methods from AbstractMap which delegates to the Set returned
by entrySet. ServletAttributesMap's entrySet method made this fairly
expensive, since it would copy the attributes to a List, then use a
Stream to build the Set. To avoid the cost, add implementations of
isEmpty / size that don't need to call entrySet at all.

Additionally, change entrySet to return a Set view that simply lazily
delegates to the underlying servlet request for iteration.

Closes gh-32197
2024-02-05 10:42:46 +01:00
Stéphane Nicoll 55717adf88 Upgrade to Gradle 8.6
Closes gh-32193
2024-02-03 11:36:40 +01:00
Juergen Hoeller 72835f10b9 Polishing 2024-02-01 14:58:13 +01:00
Stéphane Nicoll 125ebd029c Prevent AOT from failing with spring-orm without JPA
This commit improves PersistenceManagedTypesBeanRegistrationAotProcessor
so that it does not attempt to load JPA classes when checking for the
presence of a PersistenceManagedTypes bean. To make it more clear a
check on the presence for JPA has been added to prevent the nested
classes to be loaded regardless of the presence of the bean.

Closes gh-32160
2024-01-30 15:42:19 +01:00
Juergen Hoeller d8d4fa0e24 Polishing 2024-01-29 16:42:32 +01:00
Juergen Hoeller bfd3b3ad88 Eagerly initialize ZERO_NANOS constant 2024-01-29 16:21:35 +01:00
Juergen Hoeller 0909161ba1 Polishing 2024-01-29 14:19:48 +01:00
Juergen Hoeller 4910c217dc Explicit documentation note on cron-vs-quartz parsing convention
Closes gh-32128

(cherry picked from commit a738e4d5fd)
2024-01-29 13:58:39 +01:00
Sébastien Deleuze f262046ef9 Update basics.adoc
Closes gh-32151
2024-01-29 09:15:17 +01:00
Juergen Hoeller d50d4a9092 Polishing 2024-01-24 22:44:29 +01:00
Juergen Hoeller b06305e64f Compare qualifier value arrays with equality semantics
Closes gh-32106

(cherry picked from commit c5a75219ce)
2024-01-24 22:39:24 +01:00
Arjen Poutsma c749a14326 Guard against multiple body subscriptions
Before this commit, the JDK and Jetty connectors do not have any
safeguards against multiple body subscriptions. Such as check has now
been added.

See gh-32100
Closes gh-32102
2024-01-24 13:42:25 +01:00
Juergen Hoeller 3817936ca5 Declare current observation context as available since 6.0.15
See gh-31609
See gh-31646
2024-01-24 12:35:30 +01:00
Juergen Hoeller 9bd2be80b9 Declare allowPrivateNetwork as available since 5.3.32
See gh-28546
See gh-31974
2024-01-24 12:34:53 +01:00
Juergen Hoeller c6e9cd0c62 Polishing 2024-01-24 11:59:15 +01:00
Juergen Hoeller e2a5cfb459 Consistent nullability for concurrent result
(cherry picked from commit b92877990d)
2024-01-24 11:59:10 +01:00
Brian Clozel b484ab116f Record errors thrown by custom handler in RestTemplate observations
Prior to this commit, the `RestTemplate` observation instrumentation
would only record `RestClientException` and `IOException` as errors in
the observation. Other types of errors can be thrown by custom
components, such as `ResponseErrorHandler` and in this case they aren't
recorded with the observation.
Also, the current instrumentation does not create any observation scope
around the execution. While this would have a limited benefit as no
application code is executed there, developers could set up custom
components (such as, again, `ResponseErrorHandler`) that could use
contextual logging with trace ids.

This commit ensures that all `Throwable` are recorded as errors with the
observations and that an observation `Scope` is created around the
execution of the client exchange.

Fixes gh-32063
2024-01-22 11:28:07 +01:00
Juergen Hoeller c668473186 Polishing 2024-01-19 17:18:28 +01:00
Arjen Poutsma 38595c6a99 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-32050
2024-01-18 15:46:14 +01:00
Brian Clozel d756c2b128 Remove JDK 20 variant from CI build. 2024-01-17 21:26:37 +01:00
Brian Clozel 3be4322f71 Upgrade CI image to JDK 17.0.10+13 2024-01-17 21:21:55 +01:00
Brian Clozel 328e444db3 Upgrade CI image to Ubuntu Jammy 20240111 2024-01-17 21:19:27 +01:00
Stéphane Nicoll 94bdd4380f Fix incorrect assertions using json path
Closes gh-32040
2024-01-16 11:02:36 +01:00
rstoyanchev 3f9d479583 Double-checked lock in ChannelSendOperator#request
Closes gh-31865
2024-01-12 17:16:23 +00:00
Sébastien Deleuze 338922f03d Find destroy methods in superclass interfaces
Related tests will be added in
https://github.com/spring-projects/spring-aot-smoke-tests.

Closes gh-32017
2024-01-12 12:28:07 +01:00
Spring Builds fad857e06a Next development version (v6.0.17-SNAPSHOT) 2024-01-11 10:33:22 +00:00
Juergen Hoeller b8395a2321 Upgrade to spring-javaformat-checkstyle 0.0.41 2024-01-10 17:10:23 +01:00
rstoyanchev 6df8be8be3 Exclude query from URI in WebClient checkpoints
Closes gh-31992
2024-01-10 12:33:28 +00:00
Juergen Hoeller 4a599d0b22 Upgrade to Reactor 2022.0.15
Includes SLF4J 2.0.11, Groovy 4.0.17, Jetty 11.0.19, Netty 4.1.104, Apache HttpClient 5.2.3, POI 5.2.5, OpenPDF 1.3.36, Checkstyle 10.12.7

Closes gh-31997
2024-01-10 11:09:11 +01:00
rstoyanchev a1463c2bf2 Do not set exception attribute if response body is set
ResponseEntityExceptionHandler should not set the exception attribute
when there is a response body, and the response is fully handled.

Closes gh-31541
2024-01-09 14:54:48 +00:00
Sébastien Deleuze 23eff5c650 Update ContentRequestMatchers#multipartData Javadoc
This commit updates ContentRequestMatchers#multipartData
Javadoc to mention Tomcat fork of Commons FileUpload library
instead of the original variant.

It also adds a similar note to
ContentRequestMatchers#multipartDataContains.

Closes gh-31989

(Backport of 598c972a78)
2024-01-09 14:14:45 +01:00
Juergen Hoeller c44bb29aa5 Polishing 2024-01-09 12:56:52 +01:00
Arjen Poutsma 0c22866b72 Ensure correct capacity in DefaultDataBuffer
See gh-31873
Closes gh-31979
2024-01-09 10:40:41 +01:00
Sébastien Deleuze 0c6957e395 Polishing
See gh-31975
2024-01-08 12:26:03 +01:00
Sébastien Deleuze 8d51fc0444 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-31975

(cherry picked from commit 318d460256)
2024-01-08 11:23:03 +01:00
Juergen Hoeller bad01011da 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:35:12 +01:00
Juergen Hoeller 867a199507 Propagate arguments for dynamic prototype-scoped advice
Closes gh-28407

(cherry picked from commit 43107e7eb1)
2024-01-07 00:15:43 +01:00
Juergen Hoeller 1b4a4ac51f Polishing 2024-01-06 23:21:57 +01:00
Juergen Hoeller dd0d26b4ba 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:10:30 +01:00
Juergen Hoeller d074f660a1 Default time zone resolution from scheduler-wide Clock
Closes gh-31948
2024-01-05 10:19:03 +01:00
Stéphane Nicoll 1b8baffbd7 Upgrade CI to Ubuntu Jammy 20231211.1 2024-01-05 09:43:01 +01:00
Brian Clozel 56c63c779f Fix HandlerMappingIntrospector uri matching
Prior to this commit, the `HandlerMappingIntrospector` would comparea
request with a cached request by using `String#matches` on their String
URI. This could lead to `PatternSyntaxException` exceptions at runtime
if the request URI contained pattern characters.

This commit fixes this typo to use `String#equals` instead.

Fixes gh-31946
2024-01-04 17:06:17 +01:00
Juergen Hoeller 198cf063fd Polishing 2023-12-30 11:45:34 +01:00
Yanming Zhou b1b6b544a2 Add missing @Test
See gh-31914
2023-12-28 11:41:18 +01:00
Brian Clozel 9d13ea290f Reject invalid forwarded requests in ForwardedHeaderFilter
Prior to this commit, the `ForwardedHeaderFilter` and the forwarded
header utils would throw `IllegalArgumentException` and
`IllegalStateException` when request headers are invalid and cannot be
parsed for Forwarded handling.

This commit aligns the behavior with the WebFlux counterpart by
rejecting such requests with HTTP 400 responses directly.

Fixes gh-31894
2023-12-22 17:51:27 +01:00
Sébastien Deleuze c9163b77df Add support for @Async Kotlin function returning Unit?
Closes gh-31891
2023-12-22 15:35:56 +01:00
Juergen Hoeller 033c8df53f Polishing 2023-12-22 12:54:16 +01:00
Arjen Poutsma b272f4e615 Correctly set capacity of remainder in DefaultDataBuffer::split
This commit ensures that the capacity of the remainder buffer after a
split operation is set directly on the field. Calling capacity(int)
caused a new buffer to be allocated.

See gh-31848
Closes gh-31859
2023-12-18 11:50:30 +01:00
Spring Builds bafff5079f Next development version (v6.0.16-SNAPSHOT) 2023-12-14 08:52:04 +00:00
Juergen Hoeller ccaecab500 Polishing 2023-12-14 08:32:08 +01:00
Sam Brannen 67e03105b5 Introduce test for XML replaced-method element without explicit arg-type
This commit introduces an integration test for the regression fixed in
the previous commit (76bc9cf325).

See gh-31826
Closes gh-31828

(cherry picked from commit 8d4deca2a6)
2023-12-13 15:12:29 +01:00
Juergen Hoeller 76bc9cf325 Prepare method overrides when bean class gets resolved
See gh-31826
See gh-31828

(cherry picked from commit cd64e6676c)
2023-12-13 15:11:29 +01:00
rstoyanchev db52c77cca Minor updates in HandlerMappingIntrospector
Required by Spring Security to complete work on
https://github.com/spring-projects/spring-security/issues/14128

The setCache and resetCache methods used from createCacheFilter are now
public. Generally they don't need to be used outside of the Filter if
only making checks against the current request. Spring Security, however,
makes additional checks against requests with alternative paths.
2023-12-12 20:22:29 +00:00
Sam Brannen 1e742aae34 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)
2023-12-12 18:39:38 +01:00
Sam Brannen 20dd585c93 Polish MergedAnnotation tests
(cherry picked from commit 952223dcf9)
2023-12-12 18:38:06 +01:00
Juergen Hoeller 707eb701dc Polishing 2023-12-12 14:02:02 +01:00
Juergen Hoeller 2c97996796 Upgrade to Reactor 2022.0.14
Includes Groovy 4.0.16 and Mockito 5.8

Closes gh-31815
2023-12-12 14:01:55 +01:00
rstoyanchev 3a068b807b Update link to stompjs library
Closes gh-28409
2023-12-12 12:32:21 +00:00
Stéphane Nicoll f54b19ff90 Start building against Reactor 2022.0.14 snapshots
See gh-31815
2023-12-11 15:07:32 +01:00
Stéphane Nicoll 494d2ab727 Remove .mailmap file
See gh-31740
2023-12-11 11:08:31 +01:00
Juergen Hoeller 627d9cf8be Polishing 2023-12-10 00:26:22 +01:00
Arjen Poutsma dd3a67c7ab Process tokens after each feed in Jackson2Tokenizer
This commit ensures that we process after each fed buffer in
Jackson2Tokenizer, instead of after all fed buffers.

See gh-31747
Closes gh-31772
2023-12-06 14:49:08 +01:00
Sam Brannen 85cc229063 Fix and polish Javadoc for MimeTypeUtils
(cherry picked from commit 1afea0b144)
2023-12-06 14:31:35 +01:00
Johnny Lim fa95f12be0 Fix condition for "Too many elements" in MimeTypeUtils.sortBySpecificity()
See gh-31254
See gh-31769
Closes gh-31773

(cherry picked from commit 7b95bd72f7)
2023-12-06 14:31:16 +01:00
Sam Brannen 035cc72fc8 Polishing 2023-12-06 12:40:49 +01:00
Arjen Poutsma 29a39b617e Support empty part in DefaultPartHttpMessageReader
This commit fixes a bug in DefaultPartHttpMessageReader's
MultipartParser, due to which the last token in a part window was not
properly indicated.

See gh-30953
Closes gh-31766
2023-12-06 12:21:28 +01:00
Sébastien Deleuze a1471a9266 Document @ModelAttribute usage with native images
Closes gh-31767
2023-12-06 12:18:42 +01:00
Sam Brannen 21793b4f93 Suppress warnings in Gradle build
(cherry picked from commit c05b4ce776)
2023-12-01 15:55:36 +01:00
Sam Brannen 2d255c4d5e Upgrade to Gradle 8.5
Closes gh-31734

(cherry picked from commit 3a53446a2b)
2023-12-01 15:54:40 +01:00
Juergen Hoeller 10391586d1 PathEditor considers single-letter URI scheme as NIO path candidate
Closes gh-29881

(cherry picked from commit c56c304536)
2023-11-30 14:16:37 +01:00
Brian Clozel edadc79835 Fix reactive HTTP server Observation instrumentation
Prior to this commit, regressions were introduced with gh-31417:

1. the observation keyvalues would be inconsistent with the HTTP response
2. the observation scope would not cover all controller handlers, causing
  traceIds to be missing

The first issue is caused by the fact that in case of error signals, the
observation was stopped before the response was fully committed, which
means further processing could happen and update the response status.
This commit delays the stop event until the response is committed in
case of errors.

The second problem is caused by the change from a `contextWrite`
operator to using the `tap` operator with a `SignalListener`. The
observation was started in the `doOnSubscription` callback, which is too
late in some cases. If the WebFlux controller handler is synchronous
non-blocking, the execution of the handler is performed before the
subscription happens. This means that for those handlers, the
observation was not started, even if the current observation was
present in the reactor context. This commit changes the
`doOnSubscription` to `doFirst` to ensure that the observation is
started at the right time.

Fixes gh-31715
Fixes gh-31716
2023-11-29 14:55:57 +01:00
Stéphane Nicoll 3783d31c09 Quote name attribute if necessary
This commit updates MetadataNamingStrategy to quote an ObjectName
attribute value if necessary. For now, only the name attribute is
handled as it is usually a bean name, and we have no control over
its structure.

Closes gh-31708
2023-11-28 17:05:36 +01:00
Sam Brannen 87730f76b1 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-31683

(cherry picked from commit a15f472898)
2023-11-26 12:14:33 +01:00
Juergen Hoeller 6fae3e150e Consider generics in equals method (for ConversionService caching)
Closes gh-31672

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

(cherry picked from commit 0599320bd8)
2023-11-24 23:28:59 +01:00
Juergen Hoeller aadf96ba92 Properly return loaded type even if identified as reloadable
Closes gh-31668

(cherry picked from commit 8921be18de)
2023-11-24 23:28:57 +01:00
Brian Clozel 2baf064d04 Add current observation context in ClientRequest
Prior to this commit, `ExchangeFilterFunction` could only get the
current observation from the reactor context. This is particularly
useful when such filters want to add KeyValues to the observation
context.

This commit makes this use case easier by adding the context of the
current observation as a request attribute. This also aligns the
behavior with other instrumentations.

Fixes gh-31646
2023-11-24 17:37:04 +01:00
Brian Clozel 6121e2f526 Remove API diff Gradle plugin configuration
We have not published API diffs for a while now so we should remove the
configuration entirely from our build.
2023-11-23 15:53:35 +01:00
Brian Clozel 8e33805d29 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-31656
2023-11-22 20:56:37 +01:00
rstoyanchev 1f19bb2311 Update STOMP WebSocket transport reference docs
Closes gh-31616
2023-11-22 15:32:49 +00:00
Juergen Hoeller 2784410cc6 Polishing 2023-11-22 13:01:03 +01:00
Juergen Hoeller f4ac323409 Test for mixed order across bean factory hierarchy
See gh-28374

(cherry picked from commit 48f3c08395)
2023-11-22 12:46:36 +01:00
rstoyanchev 6db00e63c7 WebSocketMessageBrokerStats implements SmartInitializingSingleton
Closes gh-26536
2023-11-21 17:58:32 +00:00
“7fantasy7” ed613e767a Skip buffer in StreamUtils#copy(String)
(cherry picked from commit 54f87f1ff7)
2023-11-20 21:30:01 +01:00
Juergen Hoeller 65781046cf Polishing
(cherry picked from commit fff50657d2)
2023-11-20 21:23:46 +01:00
Juergen Hoeller 0ee36095e7 Restore outdated local/remote-slsb attributes for declaration compatibility
Legacy EJB attributes are ignored since 6.0 due to being bound to a plain JndiObjectFactoryBean - but can still be declared now, e.g. when validating against the common versions of spring-jee.xsd out there.

Closes gh-31627

(cherry picked from commit 695559879e)
2023-11-20 21:23:42 +01:00
Stéphane Nicoll 0fc38117df Handle default package with AOT processing
Adding generated code in the default package is not supported as we
intend to import it, most probably from another package, and that is
not supported. While this situation is hard to replicate with Java,
Kotlin is unfortunately more lenient and users can end up in that
situation if they forget to add a package statement.

This commit checks for the presence of a valid package, and throws
a dedicated exception if necessary.

Closes gh-31629
2023-11-20 11:54:55 +01:00
Sam Brannen 86b8a70ce6 Ensure PathResourceResolvers log warnings for non-existent resources
Prior to this commit, the getResource() methods in PathResourceResolver
implementations allowed an exception thrown from Resource#getURL() to
propagate instead of logging a warning about the missing resource as
intended.

This commit modifies the getResource() methods in PathResourceResolver
implementations so that the log messages include the output of the
toString() implementations of the underlying resources instead of their
getURL() implementations, which may throw an exception.

Furthermore, logging the toString() output of resources aligns with the
existing output for "allowed locations" in the same log message.

Note that the toString() implementations could potentially also throw
exceptions, but that is considered less likely.

See gh-31623
Closes gh-31624

(cherry picked from commit 7d2ea7e7e1)
2023-11-20 11:50:51 +01:00
Spring Builds 4bf759d872 Next development version (v6.0.15-SNAPSHOT) 2023-11-16 14:07:29 +00:00
223 changed files with 3391 additions and 2066 deletions
-36
View File
@@ -1,36 +0,0 @@
Juergen Hoeller <jhoeller@vmware.com>
Juergen Hoeller <jhoeller@vmware.com> <jhoeller@pivotal.io>
Juergen Hoeller <jhoeller@vmware.com> <jhoeller@gopivotal.com>
Rossen Stoyanchev <rstoyanchev@vmware.com>
Rossen Stoyanchev <rstoyanchev@vmware.com> <rstoyanchev@pivotal.io>
Rossen Stoyanchev <rstoyanchev@vmware.com> <rstoyanchev@gopivotal.com>
Phillip Webb <pwebb@vmware.com>
Phillip Webb <pwebb@vmware.com> <pwebb@pivotal.io>
Phillip Webb <pwebb@vmware.com> <pwebb@gopivotal.com>
Chris Beams <cbeams@vmware.com>
Chris Beams <cbeams@vmware.com> <cbeams@pivotal.io>
Chris Beams <cbeams@vmware.com> <cbeams@gopivotal.com>
Arjen Poutsma <poutsmaa@vmware.com>
Arjen Poutsma <poutsmaa@vmware.com> <apoutsma@pivotal.io>
Arjen Poutsma <poutsmaa@vmware.com> <apoutsma@gopivotal.com>
Arjen Poutsma <poutsmaa@vmware.com> <poutsma@mac.com>
Arjen Poutsma <poutsmaa@vmware.com> <apoutsma@vmware.com>
Oliver Drotbohm <odrotbohm@vmware.com>
Oliver Drotbohm <odrotbohm@vmware.com> <ogierke@vmware.com>
Oliver Drotbohm <odrotbohm@vmware.com> <ogierke@pivotal.io>
Oliver Drotbohm <odrotbohm@vmware.com> <ogierke@gopivotal.com>
Dave Syer <dsyer@vmware.com>
Dave Syer <dsyer@vmware.com> <dsyer@pivotal.io>
Dave Syer <dsyer@vmware.com> <dsyer@gopivotal.com>
Dave Syer <dsyer@vmware.com> <david_syer@hotmail.com>
Andy Clement <aclement@vmware.com>
Andy Clement <aclement@vmware.com> <aclement@pivotal.io>
Andy Clement <aclement@vmware.com> <aclement@gopivotal.com>
Andy Clement <aclement@vmware.com> <andrew.clement@gmail.com>
Sam Brannen <sbrannen@vmware.com>
Sam Brannen <sbrannen@vmware.com> <sbrannen@pivotal.io>
Sam Brannen <sbrannen@vmware.com> <sam@sambrannen.com>
Simon Basle <sbasle@vmware.com>
Simon Baslé <sbasle@vmware.com>
<dmitry.katsubo@gmail.com> <dmitry.katsubo@gmai.com>
Nick Williams <nicholas@nicholaswilliams.net>
+2 -3
View File
@@ -75,7 +75,7 @@ configure([rootProject] + javaProjects) { project ->
}
checkstyle {
toolVersion = "10.12.5"
toolVersion = "10.12.7"
configDirectory.set(rootProject.file("src/checkstyle"))
}
@@ -106,7 +106,7 @@ configure([rootProject] + javaProjects) { project ->
// JSR-305 only used for non-required meta-annotations
compileOnly("com.google.code.findbugs:jsr305")
testCompileOnly("com.google.code.findbugs:jsr305")
checkstyle("io.spring.javaformat:spring-javaformat-checkstyle:0.0.39")
checkstyle("io.spring.javaformat:spring-javaformat-checkstyle:0.0.41")
}
ext.javadocLinks = [
@@ -146,7 +146,6 @@ configure(rootProject) {
description = "Spring Framework"
apply plugin: "io.spring.nohttp"
apply plugin: 'org.springframework.build.api-diff'
nohttp {
source.exclude "**/test-output/**"
-15
View File
@@ -22,21 +22,6 @@ but doesn't affect the classpath of dependent projects.
This plugin does not provide a `provided` configuration, as the native `compileOnly` and `testCompileOnly`
configurations are preferred.
### API Diff
This plugin uses the [Gradle JApiCmp](https://github.com/melix/japicmp-gradle-plugin) plugin
to generate API Diff reports for each Spring Framework module. This plugin is applied once on the root
project and creates tasks in each framework module. Unlike previous versions of this part of the build,
there is no need for checking out a specific tag. The plugin will fetch the JARs we want to compare the
current working version with. You can generate the reports for all modules or a single module:
```
./gradlew apiDiff -PbaselineVersion=5.1.0.RELEASE
./gradlew :spring-core:apiDiff -PbaselineVersion=5.1.0.RELEASE
```
The reports are located under `build/reports/api-diff/$OLDVERSION_to_$NEWVERSION/`.
### RuntimeHints Java Agent
+1 -6
View File
@@ -19,16 +19,11 @@ ext {
dependencies {
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
implementation("org.jetbrains.kotlin:kotlin-compiler-embeddable:${kotlinVersion}")
implementation "me.champeau.gradle:japicmp-gradle-plugin:0.3.0"
implementation "org.gradle:test-retry-gradle-plugin:1.4.1"
implementation "org.gradle:test-retry-gradle-plugin:1.5.6"
}
gradlePlugin {
plugins {
apiDiffPlugin {
id = "org.springframework.build.api-diff"
implementationClass = "org.springframework.build.api.ApiDiffPlugin"
}
conventionsPlugin {
id = "org.springframework.build.conventions"
implementationClass = "org.springframework.build.ConventionsPlugin"
@@ -1,141 +0,0 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.build.api;
import java.io.File;
import java.net.URI;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
import me.champeau.gradle.japicmp.JapicmpPlugin;
import me.champeau.gradle.japicmp.JapicmpTask;
import org.gradle.api.GradleException;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.jvm.tasks.Jar;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* {@link Plugin} that applies the {@code "japicmp-gradle-plugin"}
* and create tasks for all subprojects named {@code "spring-*"}, diffing the public API one by one
* and creating the reports in {@code "build/reports/api-diff/$OLDVERSION_to_$NEWVERSION/"}.
* <p>{@code "./gradlew apiDiff -PbaselineVersion=5.1.0.RELEASE"} will output the
* reports for the API diff between the baseline version and the current one for all modules.
* You can limit the report to a single module with
* {@code "./gradlew :spring-core:apiDiff -PbaselineVersion=5.1.0.RELEASE"}.
*
* @author Brian Clozel
*/
public class ApiDiffPlugin implements Plugin<Project> {
private static final Logger logger = LoggerFactory.getLogger(ApiDiffPlugin.class);
public static final String TASK_NAME = "apiDiff";
private static final String BASELINE_VERSION_PROPERTY = "baselineVersion";
private static final List<String> PACKAGE_INCLUDES = Collections.singletonList("org.springframework.*");
private static final URI SPRING_MILESTONE_REPOSITORY = URI.create("https://repo.spring.io/milestone");
@Override
public void apply(Project project) {
if (project.hasProperty(BASELINE_VERSION_PROPERTY) && project.equals(project.getRootProject())) {
project.getPluginManager().apply(JapicmpPlugin.class);
project.getPlugins().withType(JapicmpPlugin.class,
plugin -> applyApiDiffConventions(project));
}
}
private void applyApiDiffConventions(Project project) {
String baselineVersion = project.property(BASELINE_VERSION_PROPERTY).toString();
project.subprojects(subProject -> {
if (subProject.getName().startsWith("spring-")) {
createApiDiffTask(baselineVersion, subProject);
}
});
}
private void createApiDiffTask(String baselineVersion, Project project) {
if (isProjectEligible(project)) {
// Add Spring Milestone repository for generating diffs against previous milestones
project.getRootProject()
.getRepositories()
.maven(mavenArtifactRepository -> mavenArtifactRepository.setUrl(SPRING_MILESTONE_REPOSITORY));
JapicmpTask apiDiff = project.getTasks().create(TASK_NAME, JapicmpTask.class);
apiDiff.setDescription("Generates an API diff report with japicmp");
apiDiff.setGroup(JavaBasePlugin.DOCUMENTATION_GROUP);
apiDiff.setOldClasspath(createBaselineConfiguration(baselineVersion, project));
TaskProvider<Jar> jar = project.getTasks().withType(Jar.class).named("jar");
apiDiff.setNewArchives(project.getLayout().files(jar.get().getArchiveFile().get().getAsFile()));
apiDiff.setNewClasspath(getRuntimeClassPath(project));
apiDiff.setPackageIncludes(PACKAGE_INCLUDES);
apiDiff.setOnlyModified(true);
apiDiff.setIgnoreMissingClasses(true);
// Ignore Kotlin metadata annotations since they contain
// illegal HTML characters and fail the report generation
apiDiff.setAnnotationExcludes(Collections.singletonList("@kotlin.Metadata"));
apiDiff.setHtmlOutputFile(getOutputFile(baselineVersion, project));
apiDiff.dependsOn(project.getTasks().getByName("jar"));
}
}
private boolean isProjectEligible(Project project) {
return project.getPlugins().hasPlugin(JavaPlugin.class)
&& project.getPlugins().hasPlugin(MavenPublishPlugin.class);
}
private Configuration createBaselineConfiguration(String baselineVersion, Project project) {
String baseline = String.join(":",
project.getGroup().toString(), project.getName(), baselineVersion);
Dependency baselineDependency = project.getDependencies().create(baseline + "@jar");
Configuration baselineConfiguration = project.getRootProject().getConfigurations().detachedConfiguration(baselineDependency);
try {
// eagerly resolve the baseline configuration to check whether this is a new Spring module
baselineConfiguration.resolve();
return baselineConfiguration;
}
catch (GradleException exception) {
logger.warn("Could not resolve {} - assuming this is a new Spring module.", baseline);
}
return project.getRootProject().getConfigurations().detachedConfiguration();
}
private Configuration getRuntimeClassPath(Project project) {
return project.getConfigurations().getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);
}
private File getOutputFile(String baseLineVersion, Project project) {
String buildDirectoryPath = project.getRootProject()
.getLayout().getBuildDirectory().getAsFile().get().getAbsolutePath();
Path outDir = Paths.get(buildDirectoryPath, "reports", "api-diff",
baseLineVersion + "_to_" + project.getRootProject().getVersion());
return project.file(outDir.resolve(project.getName() + ".html").toString());
}
}
+1 -2
View File
@@ -1,4 +1,4 @@
FROM ubuntu:jammy-20231004
FROM ubuntu:jammy-20240111
ADD setup.sh /setup.sh
ADD get-jdk-url.sh /get-jdk-url.sh
@@ -6,6 +6,5 @@ RUN ./setup.sh
ENV JAVA_HOME /opt/openjdk/java17
ENV JDK17 /opt/openjdk/java17
ENV JDK20 /opt/openjdk/java20
ENV PATH $JAVA_HOME/bin:$PATH
+1 -4
View File
@@ -3,10 +3,7 @@ set -e
case "$1" in
java17)
echo "https://github.com/bell-sw/Liberica/releases/download/17.0.9+11/bellsoft-jdk17.0.9+11-linux-amd64.tar.gz"
;;
java20)
echo "https://github.com/bell-sw/Liberica/releases/download/20.0.1+10/bellsoft-jdk20.0.1+10-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 -1
View File
@@ -20,7 +20,7 @@ curl https://raw.githubusercontent.com/spring-io/concourse-java-scripts/v0.0.4/c
mkdir -p /opt/openjdk
pushd /opt/openjdk > /dev/null
for jdk in java17 java20
for jdk in java17
do
JDK_URL=$( /get-jdk-url.sh $jdk )
mkdir $jdk
+1 -44
View File
@@ -90,13 +90,6 @@ resources:
<<: *docker-resource-source
repository: ((docker-hub-organization))/spring-framework-ci
tag: ((milestone))
- name: every-morning
type: time
icon: alarm
source:
start: 8:00 AM
stop: 9:00 AM
location: Europe/Vienna
- name: artifactory-repo
type: artifactory-resource
icon: package-variant
@@ -113,14 +106,6 @@ resources:
access_token: ((github-ci-status-token))
branch: ((branch))
context: build
- name: repo-status-jdk20-build
type: github-status-resource
icon: eye-check-outline
source:
repository: ((github-repo-name))
access_token: ((github-ci-status-token))
branch: ((branch))
context: jdk20-build
- name: slack-alert
type: slack-notification
icon: slack
@@ -217,34 +202,6 @@ jobs:
"zip.type": "schema"
get_params:
threads: 8
- name: jdk20-build
serial: true
public: true
plan:
- get: ci-image
- get: git-repo
- get: every-morning
trigger: true
- put: repo-status-jdk20-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: 20
<<: *build-project-task-params
on_failure:
do:
- put: repo-status-jdk20-build
params: { state: "failure", commit: "git-repo" }
- put: slack-alert
params:
<<: *slack-fail-params
- put: repo-status-jdk20-build
params: { state: "success", commit: "git-repo" }
- name: stage-milestone
serial: true
plan:
@@ -396,7 +353,7 @@ jobs:
groups:
- name: "builds"
jobs: ["build", "jdk20-build"]
jobs: ["build"]
- name: "releases"
jobs: ["stage-milestone", "stage-rc", "stage-release", "promote-milestone", "promote-rc", "promote-release", "create-github-release"]
- name: "ci-images"
+1 -1
View File
@@ -4,6 +4,6 @@ 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,JDK20 \
./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 antora
popd > /dev/null
@@ -168,7 +168,7 @@ Kotlin::
======
NOTE: Pooling stateless service objects is not usually necessary. We do not believe it should
be the default choice, as most stateless objects are naturally thread safe, and instance
be the default choice, as most stateless objects are naturally thread-safe, and instance
pooling is problematic if resources are cached.
Simpler pooling is available by using auto-proxying. You can set the `TargetSource` implementations
@@ -141,7 +141,7 @@ Kotlin::
====
After you learn about Spring's IoC container, you may want to know more about Spring's
`Resource` abstraction (as described in
xref:web/webflux-webclient/client-builder.adoc#webflux-client-builder-reactor-resources[Resources])
xref:core/resources.adoc[Resources])
which provides a convenient mechanism for reading an InputStream from locations defined
in a URI syntax. In particular, `Resource` paths are used to construct applications contexts,
as described in xref:core/resources.adoc#resources-app-ctx[Application Contexts and Resource Paths].
@@ -324,7 +324,6 @@ Kotlin::
[[beans-factory-scopes-application]]
=== Application Scope
@@ -374,7 +373,6 @@ Kotlin::
[[beans-factory-scopes-websocket]]
=== WebSocket Scope
@@ -384,7 +382,6 @@ xref:web/websocket/stomp/scope.adoc[WebSocket scope] for more details.
[[beans-factory-scopes-other-injection]]
=== Scoped Beans as Dependencies
@@ -4,7 +4,7 @@
Projection lets a collection drive the evaluation of a sub-expression, and the result is
a new collection. The syntax for projection is `.![projectionExpression]`. For example,
suppose we have a list of inventors but want the list of cities where they were born.
Effectively, we want to evaluate 'placeOfBirth.city' for every entry in the inventor
Effectively, we want to evaluate `placeOfBirth.city` for every entry in the inventor
list. The following example uses projection to do so:
[tabs]
@@ -13,16 +13,18 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
// returns ['Smiljan', 'Idvor' ]
List placesOfBirth = (List)parser.parseExpression("members.![placeOfBirth.city]");
// evaluates to ["SmilJan", "Idvor"]
List placesOfBirth = parser.parseExpression("members.![placeOfBirth.city]")
.getValue(societyContext, List.class);
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
// returns ['Smiljan', 'Idvor' ]
val placesOfBirth = parser.parseExpression("members.![placeOfBirth.city]") as List<*>
// evaluates to ["SmilJan", "Idvor"]
val placesOfBirth = parser.parseExpression("members.![placeOfBirth.city]")
.getValue(societyContext) as List<*>
----
======
@@ -28,13 +28,14 @@ Kotlin::
======
Selection is supported for arrays and anything that implements `java.lang.Iterable` or
`java.util.Map`. For a list or array, the selection criteria is evaluated against each
individual element. Against a map, the selection criteria is evaluated against each map
entry (objects of the Java type `Map.Entry`). Each map entry has its `key` and `value`
accessible as properties for use in the selection.
`java.util.Map`. For an array or `Iterable`, the selection expression is evaluated
against each individual element. Against a map, the selection expression is evaluated
against each map entry (objects of the Java type `Map.Entry`). Each map entry has its
`key` and `value` accessible as properties for use in the selection.
The following expression returns a new map that consists of those elements of the
original map where the entry's value is less than 27:
Given a `Map` stored in a variable named `#map`, the following expression returns a new
map that consists of those elements of the original map where the entry's value is less
than 27:
[tabs]
======
@@ -42,21 +43,21 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
Map newMap = parser.parseExpression("map.?[value<27]").getValue();
Map newMap = parser.parseExpression("#map.?[value < 27]").getValue(Map.class);
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val newMap = parser.parseExpression("map.?[value<27]").getValue()
val newMap = parser.parseExpression("#map.?[value < 27]").getValue() as Map
----
======
In addition to returning all the selected elements, you can retrieve only the first or
the last element. To obtain the first element matching the selection, the syntax is
`.^[selectionExpression]`. To obtain the last matching selection, the syntax is
`.$[selectionExpression]`.
the last element. To obtain the first element matching the selection expression, the
syntax is `.^[selectionExpression]`. To obtain the last element matching the selection
expression, the syntax is `.$[selectionExpression]`.
@@ -85,7 +85,7 @@ email when someone places an order:
// Call the collaborators to persist the order...
// Create a thread safe "copy" of the template message and customize it
// Create a thread-safe "copy" of the template message and customize it
SimpleMailMessage msg = new SimpleMailMessage(this.templateMessage);
msg.setTo(order.getCustomer().getEmailAddress());
msg.setText(
@@ -152,7 +152,7 @@ Kotlin::
@Autowired
lateinit var accountService: AccountService
lateinit mockMvc: MockMvc
lateinit var mockMvc: MockMvc
@BeforeEach
fun setup(wac: WebApplicationContext) {
@@ -166,4 +166,7 @@ By default, any argument that is not a simple value type (as determined by
and is not resolved by any other argument resolver is treated as if it were annotated
with `@ModelAttribute`.
WARNING: When compiling to a native image with GraalVM, the implicit `@ModelAttribute`
support described above does not allow proper ahead-of-time inference of related data
binding reflection hints. As a consequence, it is recommended to explicitly annotate
method parameters with `@ModelAttribute` for use in a GraalVM native image.
@@ -70,8 +70,9 @@ it does the same, but it also compares the computed value against the `If-None-M
request header and, if the two are equal, returns a 304 (NOT_MODIFIED).
This strategy saves network bandwidth but not CPU, as the full response must be computed for each request.
State-changing HTTP methods and other HTTP conditional request headers such as `If-Match` and `If-Unmodified-Since` are outside the scope of this filter.
Other strategies at the controller level can avoid the computation and have a broader support for HTTP conditional requests.
State-changing HTTP methods and other HTTP conditional request headers such as `If-Match` and
`If-Unmodified-Since` are outside the scope of this filter. Other strategies at the controller level
can avoid the computation and have a broader support for HTTP conditional requests.
See xref:web/webmvc/mvc-caching.adoc[HTTP Caching].
This filter has a `writeWeakETag` parameter that configures the filter to write weak ETags
@@ -216,4 +216,7 @@ By default, any argument that is not a simple value type (as determined by
and is not resolved by any other argument resolver is treated as if it were annotated
with `@ModelAttribute`.
WARNING: When compiling to a native image with GraalVM, the implicit `@ModelAttribute`
support described above does not allow proper ahead-of-time inference of related data
binding reflection hints. As a consequence, it is recommended to explicitly annotate
method parameters with `@ModelAttribute` for use in a GraalVM native image.
@@ -229,34 +229,27 @@ Java initialization API. The following example shows how to do so:
[[websocket-server-runtime-configuration]]
== Server Configuration
== Configuring the Server
[.small]#xref:web/webflux-websocket.adoc#webflux-websocket-server-config[See equivalent in the Reactive stack]#
Each underlying WebSocket engine exposes configuration properties that control
runtime characteristics, such as the size of message buffer sizes, idle timeout,
and others.
You can configure of the underlying WebSocket server such as input message buffer size,
idle timeout, and more.
For Tomcat, WildFly, and GlassFish, you can add a `ServletServerContainerFactoryBean` to your
WebSocket Java config, as the following example shows:
For Jakarta WebSocket servers, you can add a `ServletServerContainerFactoryBean` to your
Java configuration. For example:
[source,java,indent=0,subs="verbatim,quotes"]
----
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
container.setMaxTextMessageBufferSize(8192);
container.setMaxBinaryMessageBufferSize(8192);
return container;
}
}
@Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
container.setMaxTextMessageBufferSize(8192);
container.setMaxBinaryMessageBufferSize(8192);
return container;
}
----
The following example shows the XML configuration equivalent of the preceding example:
Or to your XML configuration:
[source,xml,indent=0,subs="verbatim,quotes,attributes"]
----
@@ -277,12 +270,11 @@ The following example shows the XML configuration equivalent of the preceding ex
</beans>
----
NOTE: For client-side WebSocket configuration, you should use `WebSocketContainerFactoryBean`
(XML) or `ContainerProvider.getWebSocketContainer()` (Java configuration).
NOTE: For client Jakarta WebSocket configuration, use
ContainerProvider.getWebSocketContainer() in Java configuration, or
`WebSocketContainerFactoryBean` in XML.
For Jetty, you need to supply a pre-configured Jetty `WebSocketServerFactory` and plug
that into Spring's `DefaultHandshakeHandler` through your WebSocket Java config.
The following example shows how to do so:
For Jetty, you can supply a `Consumer` callback to configure the WebSocket server:
[source,java,indent=0,subs="verbatim,quotes"]
----
@@ -298,11 +290,9 @@ The following example shows how to do so:
@Bean
public DefaultHandshakeHandler handshakeHandler() {
WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
policy.setInputBufferSize(8192);
policy.setIdleTimeout(600000);
return new DefaultHandshakeHandler(
new JettyRequestUpgradeStrategy(new WebSocketServerFactory(policy)));
}
@@ -349,6 +339,10 @@ The following example shows the XML configuration equivalent of the preceding ex
</beans>
----
TIP: When using STOMP over WebSocket, you will also need to configure
xref:web/websocket/stomp/server-config.adoc[STOMP WebSocket transport]
properties.
[[websocket-server-allowed-origins]]
@@ -103,9 +103,9 @@ You can also use the WebSocket transport configuration shown earlier to configur
maximum allowed size for incoming STOMP messages. In theory, a WebSocket
message can be almost unlimited in size. In practice, WebSocket servers impose
limits -- for example, 8K on Tomcat and 64K on Jetty. For this reason, STOMP clients
(such as the JavaScript https://github.com/JSteunou/webstomp-client[webstomp-client]
and others) split larger STOMP messages at 16K boundaries and send them as multiple
WebSocket messages, which requires the server to buffer and re-assemble.
such as https://github.com/stomp-js/stompjs[`stomp-js/stompjs`] and others split larger
STOMP messages at 16K boundaries and send them as multiple WebSocket messages,
which requires the server to buffer and re-assemble.
Spring's STOMP-over-WebSocket support does this ,so applications can configure the
maximum size for STOMP messages irrespective of WebSocket server-specific message
@@ -1,9 +1,14 @@
[[websocket-stomp-server-config]]
= WebSocket Server
= WebSocket Transport
To configure the underlying WebSocket server, the information in
xref:web/websocket/server.adoc#websocket-server-runtime-configuration[Server Configuration] applies. For Jetty, however you need to set
the `HandshakeHandler` and `WebSocketPolicy` through the `StompEndpointRegistry`:
This section explains how to configure the underlying WebSocket server transport.
For Jakarta WebSocket servers, add a `ServletServerContainerFactoryBean` to your
configuration. For examples, see
xref:web/websocket/server.adoc#websocket-server-runtime-configuration[Configuring the Server]
under the WebSocket section.
For Jetty WebSocket servers, customize the `JettyRequestUpgradeStrategy` as follows:
[source,java,indent=0,subs="verbatim,quotes"]
----
@@ -29,5 +34,20 @@ the `HandshakeHandler` and `WebSocketPolicy` through the `StompEndpointRegistry`
}
----
In addition to WebSocket server properties, there are also STOMP WebSocket transport properties
to customize as follows:
[source,java,indent=0,subs="verbatim,quotes"]
----
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureWebSocketTransport(WebSocketTransportRegistration registry) {
registry.setMessageSizeLimit(4 * 8192);
registry.setTimeToFirstMessage(30000);
}
}
----
+15 -15
View File
@@ -9,23 +9,23 @@ javaPlatform {
dependencies {
api(platform("com.fasterxml.jackson:jackson-bom:2.14.3"))
api(platform("io.micrometer:micrometer-bom:1.10.13"))
api(platform("io.netty:netty-bom:4.1.101.Final"))
api(platform("io.netty:netty-bom:4.1.107.Final"))
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
api(platform("io.projectreactor:reactor-bom:2022.0.13"))
api(platform("io.projectreactor:reactor-bom:2022.0.16"))
api(platform("io.rsocket:rsocket-bom:1.1.3"))
api(platform("org.apache.groovy:groovy-bom:4.0.15"))
api(platform("org.apache.groovy:groovy-bom:4.0.18"))
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
api(platform("org.eclipse.jetty:jetty-bom:11.0.18"))
api(platform("org.eclipse.jetty:jetty-bom:11.0.20"))
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.4"))
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.4.0"))
api(platform("org.junit:junit-bom:5.9.3"))
api(platform("org.mockito:mockito-bom:5.7.0"))
api(platform("org.mockito:mockito-bom:5.10.0"))
constraints {
api("com.fasterxml:aalto-xml:1.3.2")
api("com.fasterxml.woodstox:woodstox-core:6.5.1")
api("com.github.ben-manes.caffeine:caffeine:3.1.8")
api("com.github.librepdf:openpdf:1.3.33")
api("com.github.librepdf:openpdf:1.3.40")
api("com.google.code.findbugs:findbugs:3.0.1")
api("com.google.code.findbugs:jsr305:3.0.2")
api("com.google.code.gson:gson:2.10.1")
@@ -53,10 +53,10 @@ dependencies {
api("io.r2dbc:r2dbc-spi-test:1.0.0.RELEASE")
api("io.r2dbc:r2dbc-spi:1.0.0.RELEASE")
api("io.reactivex.rxjava3:rxjava:3.1.8")
api("io.smallrye.reactive:mutiny:1.9.0")
api("io.undertow:undertow-core:2.3.10.Final")
api("io.undertow:undertow-servlet:2.3.10.Final")
api("io.undertow:undertow-websockets-jsr:2.3.10.Final")
api("io.smallrye.reactive:mutiny:1.10.0")
api("io.undertow:undertow-core:2.3.11.Final")
api("io.undertow:undertow-servlet:2.3.11.Final")
api("io.undertow:undertow-websockets-jsr:2.3.11.Final")
api("io.vavr:vavr:0.10.4")
api("jakarta.activation:jakarta.activation-api:2.0.1")
api("jakarta.annotation:jakarta.annotation-api:2.0.0")
@@ -96,9 +96,9 @@ dependencies {
api("org.apache.derby:derby:10.16.1.1")
api("org.apache.derby:derbyclient:10.16.1.1")
api("org.apache.derby:derbytools:10.16.1.1")
api("org.apache.httpcomponents.client5:httpclient5:5.2.1")
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.2.3")
api("org.apache.poi:poi-ooxml:5.2.4")
api("org.apache.httpcomponents.client5:httpclient5:5.2.3")
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.2.4")
api("org.apache.poi:poi-ooxml:5.2.5")
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.15")
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.15")
api("org.apache.tomcat:tomcat-util:10.1.15")
@@ -111,7 +111,7 @@ dependencies {
api("org.bouncycastle:bcpkix-jdk18on:1.72")
api("org.codehaus.jettison:jettison:1.5.4")
api("org.dom4j:dom4j:2.1.4")
api("org.eclipse.jetty:jetty-reactive-httpclient:3.0.10")
api("org.eclipse.jetty:jetty-reactive-httpclient:3.0.12")
api("org.eclipse.persistence:org.eclipse.persistence.jpa:3.0.4")
api("org.eclipse:yasson:2.0.4")
api("org.ehcache:ehcache:3.10.8")
@@ -135,7 +135,7 @@ dependencies {
api("org.seleniumhq.selenium:htmlunit-driver:2.70.0")
api("org.seleniumhq.selenium:selenium-java:3.141.59")
api("org.skyscreamer:jsonassert:1.5.1")
api("org.slf4j:slf4j-api:2.0.9")
api("org.slf4j:slf4j-api:2.0.12")
api("org.testng:testng:7.8.0")
api("org.webjars:underscorejs:1.8.3")
api("org.webjars:webjars-locator-core:0.55")
+1 -1
View File
@@ -1,4 +1,4 @@
version=6.0.14-SNAPSHOT
version=6.0.17
org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+10 -10
View File
@@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -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.
@@ -333,12 +333,12 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
Object targetObject = null;
Object thisObject = null;
try {
MethodInvocation mi = ExposeInvocationInterceptor.currentInvocation();
targetObject = mi.getThis();
if (!(mi instanceof ProxyMethodInvocation _pmi)) {
throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
MethodInvocation curr = ExposeInvocationInterceptor.currentInvocation();
targetObject = curr.getThis();
if (!(curr instanceof ProxyMethodInvocation currPmi)) {
throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + curr);
}
pmi = _pmi;
pmi = currPmi;
thisObject = pmi.getProxy();
}
catch (IllegalStateException 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.
@@ -293,7 +293,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() {
@@ -102,6 +102,12 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
*/
private List<Advisor> advisors = new ArrayList<>();
/**
* List of minimal {@link AdvisorKeyEntry} instances,
* to be assigned to the {@link #advisors} field on reduction.
* @since 6.0.10
* @see #reduceToAdvisorKey
*/
private List<Advisor> advisorKey = this.advisors;
@@ -557,18 +563,6 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
}
//---------------------------------------------------------------------
// Serialization support
//---------------------------------------------------------------------
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
// Rely on default serialization; just initialize state after deserialization.
ois.defaultReadObject();
// Initialize transient fields.
this.methodCache = new ConcurrentHashMap<>(32);
}
@Override
public String toProxyConfigString() {
return toString();
@@ -590,6 +584,19 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
}
//---------------------------------------------------------------------
// Serialization support
//---------------------------------------------------------------------
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
// Rely on default serialization; just initialize state after deserialization.
ois.defaultReadObject();
// Initialize transient fields.
this.methodCache = new ConcurrentHashMap<>(32);
}
/**
* Simple wrapper class around a Method. Used as the key when
* caching methods, for efficient equals and hashCode comparisons.
@@ -639,7 +646,7 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
* @see #getConfigurationOnlyCopy()
* @see #getAdvisorKey()
*/
private static class AdvisorKeyEntry implements Advisor {
private static final class AdvisorKeyEntry implements Advisor {
private final Class<?> adviceType;
@@ -649,7 +656,6 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
@Nullable
private final String methodMatcherKey;
public AdvisorKeyEntry(Advisor advisor) {
this.adviceType = advisor.getAdvice().getClass();
if (advisor instanceof PointcutAdvisor pointcutAdvisor) {
@@ -58,6 +58,7 @@ import org.springframework.util.function.SingletonSupplier;
* @author Juergen Hoeller
* @author Stephane Nicoll
* @author He Bo
* @author Sebastien Deleuze
* @since 3.1.2
*/
public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
@@ -290,7 +291,7 @@ public abstract class AsyncExecutionAspectSupport implements BeanFactoryAware {
else if (Future.class.isAssignableFrom(returnType)) {
return executor.submit(task);
}
else if (void.class == returnType) {
else if (void.class == returnType || "kotlin.Unit".equals(returnType.getName())) {
executor.submit(task);
return null;
}
@@ -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,7 +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;
@@ -65,7 +64,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);
@@ -246,14 +245,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); // call to getClassFilter forces resolution
}
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 +275,31 @@ 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));
assertThatExceptionOfType(UnsupportedPointcutPrimitiveException.class)
.isThrownBy(() -> getPointcut(expression).getClassFilter()) // call to getClassFilter forces resolution...
.satisfies(ex -> assertThat(ex.getUnsupportedPrimitive()).isEqualTo(PointcutPrimitive.CALL));
}
@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 +494,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-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.
@@ -42,26 +42,38 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
*/
class ArgumentBindingTests {
@Test
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 annotationArgumentNameBinding() {
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new TransactionalBean());
proxyFactory.addAspect(PointcutWithAnnotationArgument.class);
ITransactionalBean proxiedTestBean = proxyFactory.getProxy();
assertThatIllegalStateException()
.isThrownBy(proxiedTestBean::doInTransaction)
.withMessage("Invoked with @Transactional");
.isThrownBy(proxiedTestBean::doInTransaction)
.withMessage("Invoked with @Transactional");
}
@Test
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
@@ -94,6 +106,7 @@ class ArgumentBindingTests {
}
}
/**
* Mimics Spring's @Transactional annotation without actually introducing the dependency.
*/
@@ -101,16 +114,17 @@ class ArgumentBindingTests {
@interface Transactional {
}
@Aspect
static class PointcutWithAnnotationArgument {
@Around(value = "execution(* org.springframework..*.*(..)) && @annotation(transactional)")
@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 {
@@ -121,7 +135,16 @@ class ArgumentBindingTests {
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]));
}
}
}
@@ -159,6 +159,9 @@ import org.springframework.util.StringUtils;
public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor,
MergedBeanDefinitionPostProcessor, BeanRegistrationAotProcessor, PriorityOrdered, BeanFactoryAware {
private static final Constructor<?>[] EMPTY_CONSTRUCTOR_ARRAY = new Constructor<?>[0];
protected final Log logger = LogFactory.getLog(getClass());
private final Set<Class<? extends Annotation>> autowiredAnnotationTypes = new LinkedHashSet<>(4);
@@ -193,9 +196,10 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
this.autowiredAnnotationTypes.add(Autowired.class);
this.autowiredAnnotationTypes.add(Value.class);
ClassLoader classLoader = AutowiredAnnotationBeanPostProcessor.class.getClassLoader();
try {
this.autowiredAnnotationTypes.add((Class<? extends Annotation>)
ClassUtils.forName("jakarta.inject.Inject", AutowiredAnnotationBeanPostProcessor.class.getClassLoader()));
ClassUtils.forName("jakarta.inject.Inject", classLoader));
logger.trace("'jakarta.inject.Inject' annotation found and supported for autowiring");
}
catch (ClassNotFoundException ex) {
@@ -204,7 +208,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
try {
this.autowiredAnnotationTypes.add((Class<? extends Annotation>)
ClassUtils.forName("javax.inject.Inject", AutowiredAnnotationBeanPostProcessor.class.getClassLoader()));
ClassUtils.forName("javax.inject.Inject", classLoader));
logger.trace("'javax.inject.Inject' annotation found and supported for autowiring");
}
catch (ClassNotFoundException ex) {
@@ -285,9 +289,16 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
// Register externally managed config members on bean definition.
findInjectionMetadata(beanName, beanType, beanDefinition);
}
@Override
public void resetBeanDefinition(String beanName) {
this.lookupMethodsChecked.remove(beanName);
this.injectionMetadataCache.remove(beanName);
}
@Override
@Nullable
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
@@ -323,12 +334,6 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
return metadata;
}
@Override
public void resetBeanDefinition(String beanName) {
this.lookupMethodsChecked.remove(beanName);
this.injectionMetadataCache.remove(beanName);
}
@Override
public Class<?> determineBeanType(Class<?> beanClass, String beanName) throws BeanCreationException {
checkLookupMethods(beanClass, beanName);
@@ -428,7 +433,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
"default constructor to fall back to: " + candidates.get(0));
}
}
candidateConstructors = candidates.toArray(new Constructor<?>[0]);
candidateConstructors = candidates.toArray(EMPTY_CONSTRUCTOR_ARRAY);
}
else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) {
candidateConstructors = new Constructor<?>[] {rawCandidates[0]};
@@ -441,7 +446,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
candidateConstructors = new Constructor<?>[] {primaryConstructor};
}
else {
candidateConstructors = new Constructor<?>[0];
candidateConstructors = EMPTY_CONSTRUCTOR_ARRAY;
}
this.candidateConstructorsCache.put(beanClass, candidateConstructors);
}
@@ -1011,7 +1016,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
hints.reflection().registerField(field);
CodeBlock resolver = CodeBlock.of("$T.$L($S)",
AutowiredFieldValueResolver.class,
(!required) ? "forField" : "forRequiredField", field.getName());
(!required ? "forField" : "forRequiredField"), field.getName());
AccessControl accessControl = AccessControl.forMember(field);
if (!accessControl.isAccessibleFrom(targetClassName)) {
return CodeBlock.of("$L.resolveAndSet($L, $L)", resolver,
@@ -1026,7 +1031,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
CodeBlock.Builder code = CodeBlock.builder();
code.add("$T.$L", AutowiredMethodArgumentsResolver.class,
(!required) ? "forMethod" : "forRequiredMethod");
(!required ? "forMethod" : "forRequiredMethod"));
code.add("($S", method.getName());
if (method.getParameterCount() > 0) {
code.add(", $L", generateParameterTypesCode(method.getParameterTypes()));
@@ -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;
}
}
@@ -191,16 +191,14 @@ public final class AutowiredFieldValueResolver extends AutowiredElementResolver
return value;
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, beanName,
new InjectionPoint(field), ex);
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
}
}
private Field getField(RegisteredBean registeredBean) {
Field field = ReflectionUtils.findField(registeredBean.getBeanClass(),
this.fieldName);
Assert.notNull(field, () -> "No field '" + this.fieldName + "' found on "
+ registeredBean.getBeanClass().getName());
Field field = ReflectionUtils.findField(registeredBean.getBeanClass(), this.fieldName);
Assert.notNull(field, () -> "No field '" + this.fieldName + "' found on " +
registeredBean.getBeanClass().getName());
return field;
}
@@ -92,10 +92,10 @@ import org.springframework.util.function.ThrowingSupplier;
* 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
@@ -493,15 +493,13 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
mbdToUse = new RootBeanDefinition(mbd);
mbdToUse.setBeanClass(resolvedClass);
}
// Prepare method overrides.
try {
mbdToUse.prepareMethodOverrides();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
beanName, "Validation of method overrides failed", ex);
try {
mbdToUse.prepareMethodOverrides();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
beanName, "Validation of method overrides failed", ex);
}
}
try {
@@ -652,7 +650,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 &&
@@ -731,7 +731,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)) {
@@ -1497,7 +1497,11 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
if (mbd.hasBeanClass()) {
return mbd.getBeanClass();
}
return doResolveBeanClass(mbd, typesToMatch);
Class<?> beanClass = doResolveBeanClass(mbd, typesToMatch);
if (mbd.hasBeanClass()) {
mbd.prepareMethodOverrides();
}
return beanClass;
}
catch (ClassNotFoundException ex) {
throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), ex);
@@ -1505,6 +1509,10 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
catch (LinkageError err) {
throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), err);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(mbd.getResourceDescription(),
beanName, "Validation of method overrides failed", ex);
}
}
@Nullable
@@ -120,11 +120,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)
@@ -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.
@@ -261,7 +261,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
if (destroyMethod != null) {
return destroyMethod;
}
for (Class<?> beanInterface : beanClass.getInterfaces()) {
for (Class<?> beanInterface : ClassUtils.getAllInterfacesForClass(beanClass)) {
destroyMethod = findDestroyMethod(beanInterface, methodName);
if (destroyMethod != null) {
return destroyMethod;
@@ -71,7 +71,7 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy {
synchronized (bd.constructorArgumentLock) {
constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;
if (constructorToUse == null) {
final Class<?> clazz = bd.getBeanClass();
Class<?> clazz = bd.getBeanClass();
if (clazz.isInterface()) {
throw new BeanInstantiationException(clazz, "Specified class is an interface");
}
@@ -104,7 +104,7 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy {
@Override
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
final Constructor<?> ctor, Object... args) {
Constructor<?> ctor, Object... args) {
if (!bd.hasMethodOverrides()) {
return BeanUtils.instantiateClass(ctor, args);
@@ -128,7 +128,7 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy {
@Override
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
@Nullable Object factoryBean, final Method factoryMethod, Object... args) {
@Nullable Object factoryBean, Method factoryMethod, Object... args) {
try {
ReflectionUtils.makeAccessible(factoryMethod);
@@ -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.
@@ -78,8 +78,10 @@ public class PathEditor extends PropertyEditorSupport {
if (nioPathCandidate && !text.startsWith("/")) {
try {
URI uri = ResourceUtils.toURI(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());
}
}
}
@@ -2102,7 +2102,7 @@ class DefaultListableBeanFactoryTests {
}
@Test
void beanProviderWithParentBeanFactoryReuseOrder() {
void beanProviderWithParentBeanFactoryDetectsOrder() {
DefaultListableBeanFactory parentBf = new DefaultListableBeanFactory();
parentBf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
parentBf.registerBeanDefinition("regular", new RootBeanDefinition(TestBean.class));
@@ -2110,10 +2110,36 @@ class DefaultListableBeanFactoryTests {
lbf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
lbf.setParentBeanFactory(parentBf);
lbf.registerBeanDefinition("low", new RootBeanDefinition(LowPriorityTestBean.class));
Stream<Class<?>> orderedTypes = lbf.getBeanProvider(TestBean.class).orderedStream().map(Object::getClass);
assertThat(orderedTypes).containsExactly(HighPriorityTestBean.class, LowPriorityTestBean.class, TestBean.class);
}
@Test // gh-28374
void beanProviderWithParentBeanFactoryAndMixedOrder() {
DefaultListableBeanFactory parentBf = new DefaultListableBeanFactory();
parentBf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
lbf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
lbf.setParentBeanFactory(parentBf);
lbf.registerSingleton("plainTestBean", new TestBean());
RootBeanDefinition bd1 = new RootBeanDefinition(PriorityTestBeanFactory.class);
bd1.setFactoryMethodName("lowPriorityTestBean");
lbf.registerBeanDefinition("lowPriorityTestBean", bd1);
RootBeanDefinition bd2 = new RootBeanDefinition(PriorityTestBeanFactory.class);
bd2.setFactoryMethodName("highPriorityTestBean");
parentBf.registerBeanDefinition("highPriorityTestBean", bd2);
ObjectProvider<TestBean> testBeanProvider = lbf.getBeanProvider(ResolvableType.forClass(TestBean.class));
List<TestBean> resolved = testBeanProvider.orderedStream().toList();
assertThat(resolved.size()).isEqualTo(3);
assertThat(resolved.get(0)).isSameAs(lbf.getBean("highPriorityTestBean"));
assertThat(resolved.get(1)).isSameAs(lbf.getBean("lowPriorityTestBean"));
assertThat(resolved.get(2)).isSameAs(lbf.getBean("plainTestBean"));
}
@Test
void autowireExistingBeanByName() {
RootBeanDefinition bd = new RootBeanDefinition(TestBean.class);
@@ -3287,6 +3313,18 @@ class DefaultListableBeanFactoryTests {
}
private static class PriorityTestBeanFactory {
public static LowPriorityTestBean lowPriorityTestBean() {
return new LowPriorityTestBean();
}
public static HighPriorityTestBean highPriorityTestBean() {
return new HighPriorityTestBean();
}
}
private static class NullTestBeanFactoryBean<T> implements FactoryBean<TestBean> {
@Override
@@ -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);
@@ -6,12 +6,12 @@ dependencies {
api(project(":spring-core"))
optional(project(":spring-jdbc")) // for Quartz support
optional(project(":spring-tx")) // for Quartz support
optional("com.github.ben-manes.caffeine:caffeine")
optional("jakarta.activation:jakarta.activation-api")
optional("jakarta.mail:jakarta.mail-api")
optional("javax.cache:cache-api")
optional("com.github.ben-manes.caffeine:caffeine")
optional("org.quartz-scheduler:quartz")
optional("org.freemarker:freemarker")
optional("org.quartz-scheduler:quartz")
testFixturesApi("org.junit.jupiter:junit-jupiter-api")
testFixturesImplementation("org.assertj:assertj-core")
testFixturesImplementation("org.mockito:mockito-core")
@@ -20,10 +20,10 @@ dependencies {
testImplementation(testFixtures(project(":spring-context")))
testImplementation(testFixtures(project(":spring-core")))
testImplementation(testFixtures(project(":spring-tx")))
testImplementation("org.hsqldb:hsqldb")
testImplementation("jakarta.annotation:jakarta.annotation-api")
testRuntimeOnly("org.ehcache:jcache")
testRuntimeOnly("org.ehcache:ehcache")
testRuntimeOnly("org.glassfish:jakarta.el")
testImplementation("org.hsqldb:hsqldb")
testRuntimeOnly("com.sun.mail:jakarta.mail")
testRuntimeOnly("org.ehcache:ehcache")
testRuntimeOnly("org.ehcache:jcache")
testRuntimeOnly("org.glassfish:jakarta.el")
}
@@ -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)
@@ -121,6 +121,7 @@ public class SimpleMailMessageTests {
assertThat(message2.hashCode()).isEqualTo(message1.hashCode());
}
@Test
public final void testEqualsObject() {
SimpleMailMessage message1;
SimpleMailMessage message2;
@@ -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 {};
@@ -70,8 +70,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-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -62,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.
*
@@ -201,7 +203,6 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
* {@link Controller @Controller} stereotype annotations.
* <p>Also supports Jakarta EE's {@link jakarta.annotation.ManagedBean} and
* JSR-330's {@link jakarta.inject.Named} annotations, if available.
*
*/
@SuppressWarnings("unchecked")
protected void registerDefaultFilters() {
@@ -305,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
*/
@@ -319,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
@@ -460,8 +461,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-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.
@@ -387,8 +387,8 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
return;
}
if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (ejbAnnotationType != null && bridgedMethod.isAnnotationPresent(ejbAnnotationType)) {
if (ejbAnnotationType != null && bridgedMethod.isAnnotationPresent(ejbAnnotationType)) {
if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (Modifier.isStatic(method.getModifiers())) {
throw new IllegalStateException("@EJB annotation is not supported on static methods");
}
@@ -398,7 +398,9 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new EjbRefElement(method, bridgedMethod, pd));
}
else if (jakartaResourceType != null && bridgedMethod.isAnnotationPresent(jakartaResourceType)) {
}
else if (jakartaResourceType != null && bridgedMethod.isAnnotationPresent(jakartaResourceType)) {
if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (Modifier.isStatic(method.getModifiers())) {
throw new IllegalStateException("@Resource annotation is not supported on static methods");
}
@@ -411,7 +413,9 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
currElements.add(new ResourceElement(method, bridgedMethod, pd));
}
}
else if (javaxResourceType != null && bridgedMethod.isAnnotationPresent(javaxResourceType)) {
}
else if (javaxResourceType != null && bridgedMethod.isAnnotationPresent(javaxResourceType)) {
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-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.
@@ -44,7 +44,6 @@ import org.springframework.aot.hint.RuntimeHintsRegistrar;
* public MyService myService() {
* return new MyService();
* }
*
* }</pre>
*
* <p>If the configuration class above is processed, {@code MyHints} will be
@@ -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.
@@ -203,7 +203,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. */
@@ -595,7 +595,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();
@@ -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,7 +45,11 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Default implementation of the {@link LifecycleProcessor} strategy.
* Spring's default implementation of the {@link LifecycleProcessor} strategy.
*
* <p>Provides interaction with {@link Lifecycle} and {@link SmartLifecycle} beans in
* groups for specific phases, on startup/shutdown as well as for explicit start/stop
* interactions on a {@link org.springframework.context.ConfigurableApplicationContext}.
*
* @author Mark Fisher
* @author Juergen Hoeller
@@ -314,6 +318,8 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor
/**
* Helper class for maintaining a group of Lifecycle beans that should be started
* and stopped together based on their 'phase' value (or the default value of 0).
* The group is expected to be created in an ad-hoc fashion and group members are
* expected to always have the same 'phase' value.
*/
private class LifecycleGroup {
@@ -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.
@@ -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;
@@ -400,7 +401,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
@@ -561,7 +562,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-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.
@@ -18,12 +18,13 @@ package org.springframework.ejb.config;
import org.w3c.dom.Element;
import org.springframework.beans.BeanUtils;
import org.springframework.jndi.JndiObjectFactoryBean;
/**
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser}
* implementation for parsing '{@code local-slsb}' tags and
* creating plain {@link JndiObjectFactoryBean} definitions.
* creating plain {@link JndiObjectFactoryBean} definitions on 6.0.
*
* @author Rob Harrop
* @author Juergen Hoeller
@@ -36,4 +37,10 @@ class LocalStatelessSessionBeanDefinitionParser extends AbstractJndiLocatingBean
return JndiObjectFactoryBean.class;
}
@Override
protected boolean isEligibleAttribute(String attributeName) {
return (super.isEligibleAttribute(attributeName) &&
BeanUtils.getPropertyDescriptor(JndiObjectFactoryBean.class, extractPropertyName(attributeName)) != null);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,12 +18,13 @@ package org.springframework.ejb.config;
import org.w3c.dom.Element;
import org.springframework.beans.BeanUtils;
import org.springframework.jndi.JndiObjectFactoryBean;
/**
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser}
* implementation for parsing '{@code remote-slsb}' tags and
* creating plain {@link JndiObjectFactoryBean} definitions.
* creating plain {@link JndiObjectFactoryBean} definitions as of 6.0.
*
* @author Rob Harrop
* @author Juergen Hoeller
@@ -36,4 +37,10 @@ class RemoteStatelessSessionBeanDefinitionParser extends AbstractJndiLocatingBea
return JndiObjectFactoryBean.class;
}
@Override
protected boolean isEligibleAttribute(String attributeName) {
return (super.isEligibleAttribute(attributeName) &&
BeanUtils.getPropertyDescriptor(JndiObjectFactoryBean.class, extractPropertyName(attributeName)) != null);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -50,6 +50,9 @@ import org.springframework.util.StringUtils;
*/
public class MetadataNamingStrategy implements ObjectNamingStrategy, InitializingBean {
private static final char[] QUOTABLE_CHARS = new char[] {',', '=', ':', '"'};
/**
* The {@code JmxAttributeSource} implementation to use for reading metadata.
*/
@@ -132,10 +135,23 @@ public class MetadataNamingStrategy implements ObjectNamingStrategy, Initializin
}
Hashtable<String, String> properties = new Hashtable<>();
properties.put("type", ClassUtils.getShortName(managedClass));
properties.put("name", beanKey);
properties.put("name", quoteIfNecessary(beanKey));
return ObjectNameManager.getInstance(domain, properties);
}
}
}
private static String quoteIfNecessary(String value) {
return shouldQuote(value) ? ObjectName.quote(value) : value;
}
private static boolean shouldQuote(String value) {
for (char quotableChar : QUOTABLE_CHARS) {
if (value.indexOf(quotableChar) != -1) {
return true;
}
}
return false;
}
}
@@ -70,7 +70,7 @@ public interface TaskScheduler {
* wrapping a cron expression
* @return a {@link ScheduledFuture} representing pending completion of the task,
* or {@code null} if the given Trigger object never fires (i.e. returns
* {@code null} from {@link Trigger#nextExecutionTime})
* {@code null} from {@link Trigger#nextExecution})
* @throws org.springframework.core.task.TaskRejectedException if the given task was not accepted
* for internal reasons (e.g. a pool overload handling policy or a pool shutdown in progress)
* @see org.springframework.scheduling.support.CronTrigger
@@ -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.
@@ -102,9 +102,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.
@@ -26,7 +26,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;
@@ -430,14 +429,14 @@ public class ScheduledAnnotationBeanPostProcessor
Assert.isTrue(initialDelay.isNegative(), "'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.
@@ -73,7 +73,7 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
/**
* Set the ThreadFactory to use for the ExecutorService's thread pool.
* Default is the underlying ExecutorService's default thread factory.
* The default is the underlying ExecutorService's default thread factory.
* <p>In a Jakarta EE or other managed environment with JSR-236 support,
* consider specifying a JNDI-located ManagedThreadFactory: by default,
* to be found at "java:comp/DefaultManagedThreadFactory".
@@ -108,7 +108,7 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
/**
* Set whether to wait for scheduled tasks to complete on shutdown,
* not interrupting running tasks and executing all tasks in the queue.
* <p>Default is {@code false}, shutting down immediately through interrupting
* <p>The default is {@code false}, shutting down immediately through interrupting
* ongoing tasks and clearing the queue. Switch this flag to {@code true} if
* you prefer fully completed tasks at the expense of a longer shutdown phase.
* <p>Note that Spring's container shutdown continues while ongoing tasks
@@ -119,6 +119,8 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
* property instead of or in addition to this property.
* @see java.util.concurrent.ExecutorService#shutdown()
* @see java.util.concurrent.ExecutorService#shutdownNow()
* @see #shutdown()
* @see #setAwaitTerminationSeconds
*/
public void setWaitForTasksToCompleteOnShutdown(boolean waitForJobsToCompleteOnShutdown) {
this.waitForTasksToCompleteOnShutdown = waitForJobsToCompleteOnShutdown;
@@ -237,7 +239,7 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
}
/**
* Cancel the given remaining task which never commended execution,
* Cancel the given remaining task which never commenced execution,
* as returned from {@link ExecutorService#shutdownNow()}.
* @param task the task to cancel (typically a {@link RunnableFuture})
* @since 5.0.5
@@ -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.
@@ -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,14 @@ final class BitsCronField extends CronField {
}
private void clearBit(int index) {
this.bits &= ~(1L << index);
this.bits &= ~(1L << index);
}
@Override
public boolean equals(Object other) {
return (this == other || (other instanceof BitsCronField that &&
type() == that.type() && this.bits == that.bits));
}
@Override
@@ -255,17 +257,6 @@ final class BitsCronField extends CronField {
return Long.hashCode(this.bits);
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (!(o instanceof BitsCronField other)) {
return false;
}
return type() == other.type() && this.bits == other.bits;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder(type().toString());
@@ -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.
@@ -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
@@ -168,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);
@@ -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-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,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 Instant nextExecution(TriggerContext triggerContext) {
Instant instant = triggerContext.lastCompletion();
if (instant != null) {
Instant timestamp = triggerContext.lastCompletion();
if (timestamp != null) {
Instant scheduled = triggerContext.lastScheduledExecution();
if (scheduled != null && instant.isBefore(scheduled)) {
if (scheduled != null && timestamp.isBefore(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.
instant = scheduled;
timestamp = scheduled;
}
}
else {
instant = triggerContext.getClock().instant();
timestamp = triggerContext.getClock().instant();
}
ZonedDateTime dateTime = ZonedDateTime.ofInstant(instant, this.zoneId);
ZonedDateTime next = this.expression.next(dateTime);
return (next != null ? next.toInstant() : null);
ZoneId zone = (this.zoneId != null ? this.zoneId : triggerContext.getClock().getZone());
ZonedDateTime zonedTimestamp = ZonedDateTime.ofInstant(timestamp, zone);
ZonedDateTime nextTimestamp = this.expression.next(zonedTimestamp);
return (nextTimestamp != null ? nextTimestamp.toInstant() : null);
}
@@ -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.
@@ -30,10 +30,15 @@ import org.springframework.util.Assert;
/**
* Extension of {@link CronField} for
* <a href="https://www.quartz-scheduler.org">Quartz</a>-specific fields.
*
* <p>Created using the {@code parse*} methods, uses a {@link TemporalAdjuster}
* 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
*/
@@ -61,8 +66,9 @@ 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");
@@ -80,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, idx + 1, value.length(), 10);
if (offset >= 0) {
throw new IllegalArgumentException("Offset '" + offset + " should be < 0 '" + value + "'");
@@ -105,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, 0, idx, 10);
dayOfMonth = Type.DAY_OF_MONTH.checkValidValue(dayOfMonth);
TemporalAdjuster adjuster = weekdayNearestTo(dayOfMonth);
@@ -116,7 +122,7 @@ 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("#");
@@ -138,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);
}
@@ -160,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);
}
@@ -170,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);
}
}
@@ -216,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 {
@@ -256,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;
@@ -332,6 +336,7 @@ final class QuartzCronField extends CronField {
}
}
@Override
public <T extends Temporal & Comparable<? super T>> T nextOrSame(T temporal) {
T result = adjust(temporal);
@@ -348,7 +353,6 @@ final class QuartzCronField extends CronField {
return result;
}
@Nullable
@SuppressWarnings("unchecked")
private <T extends Temporal & Comparable<? super T>> T adjust(T temporal) {
@@ -356,27 +360,20 @@ final class QuartzCronField extends CronField {
}
@Override
public boolean equals(@Nullable Object other) {
return (this == other || (other instanceof QuartzCronField that &&
type() == that.type() && this.value.equals(that.value)));
}
@Override
public int hashCode() {
return this.value.hashCode();
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (!(o instanceof QuartzCronField other)) {
return false;
}
return type() == other.type() &&
this.value.equals(other.value);
}
@Override
public String toString() {
return type() + " '" + this.value + "'";
}
}
@@ -95,7 +95,7 @@
</xsd:complexType>
</xsd:element>
<xsd:element name="local-slsb" type="jndiLocatingType">
<xsd:element name="local-slsb" type="ejbType">
<xsd:annotation>
<xsd:documentation source="java:org.springframework.jndi.JndiObjectFactoryBean"><![CDATA[
Exposes a reference to a local EJB Stateless SessionBean.
@@ -103,15 +103,56 @@
</xsd:annotation>
</xsd:element>
<xsd:element name="remote-slsb" type="jndiLocatingType">
<xsd:element name="remote-slsb">
<xsd:annotation>
<xsd:documentation source="java:org.springframework.jndi.JndiObjectFactoryBean"><![CDATA[
Exposes a reference to a remote EJB Stateless SessionBean.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="ejbType">
<xsd:attribute name="home-interface" type="xsd:string">
<xsd:annotation>
<xsd:documentation source="java:java.lang.Class"><![CDATA[
The home interface that will be narrowed to before performing the
parameterless SLSB create() call that returns the actual SLSB proxy.
NOTE: Effectively ignored as of 6.0 in favor of plain JNDI lookups.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="refresh-home-on-connect-failure" type="xsd:boolean" default="false">
<xsd:annotation>
<xsd:documentation><![CDATA[
Controls whether to refresh the EJB home on connect failure.
NOTE: Effectively ignored as of 6.0 in favor of plain JNDI lookups.
Can be turned on to allow for hot restart of the EJB server.
If a cached EJB home throws an RMI exception that indicates a
remote connect failure, a fresh home will be fetched and the
invocation will be retried.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="cache-session-bean" type="xsd:boolean" default="false">
<xsd:annotation>
<xsd:documentation><![CDATA[
Controls whether to cache the actual session bean object.
NOTE: Effectively ignored as of 6.0 in favor of plain JNDI lookups.
Off by default for standard EJB compliance. Turn this flag
on to optimize session bean access for servers that are
known to allow for caching the actual session bean object.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="jndiLocatingType">
<!-- base types -->
<xsd:complexType name="jndiLocatingType" abstract="true">
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:sequence>
@@ -183,6 +224,40 @@
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="ejbType">
<xsd:complexContent>
<xsd:extension base="jndiLocatingType">
<xsd:attribute name="lookup-home-on-startup" type="xsd:boolean" default="true">
<xsd:annotation>
<xsd:documentation><![CDATA[
Controls whether the lookup of the EJB home object is performed
immediately on startup (if true, the default), or on first access
(if false).
NOTE: Effectively ignored as of 6.0 in favor of plain JNDI lookups.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="cache-home" type="xsd:boolean" default="true">
<xsd:annotation>
<xsd:documentation><![CDATA[
Controls whether the EJB home object is cached once it has been located.
On by default; turn this flag off to always reobtain fresh home objects.
NOTE: Effectively ignored as of 6.0 in favor of plain JNDI lookups.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="business-interface" type="xsd:string">
<xsd:annotation>
<xsd:documentation source="java:java.lang.Class"><![CDATA[
The business interface of the EJB being proxied.
NOTE: Effectively ignored as of 6.0 in favor of plain JNDI lookups.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:simpleType name="environmentRefType">
<xsd:annotation>
<xsd:appinfo>
@@ -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.
@@ -220,16 +220,16 @@ class AspectJAutoProxyCreatorTests {
try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(configClass)) {
testBean1 = context.getBean(TestBean.class);
assertThat(AopUtils.isCglibProxy(testBean1)).as("CGLIB proxy").isTrue();
assertThat(testBean1.getClass().getInterfaces())
.containsExactlyInAnyOrder(Factory.class, SpringProxy.class, Advised.class);
assertThat(testBean1.getClass().getInterfaces()).containsExactlyInAnyOrder(
Factory.class, SpringProxy.class, Advised.class);
}
// Round #2
try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(configClass)) {
testBean2 = context.getBean(TestBean.class);
assertThat(AopUtils.isCglibProxy(testBean2)).as("CGLIB proxy").isTrue();
assertThat(testBean2.getClass().getInterfaces())
.containsExactlyInAnyOrder(Factory.class, SpringProxy.class, Advised.class);
assertThat(testBean2.getClass().getInterfaces()).containsExactlyInAnyOrder(
Factory.class, SpringProxy.class, Advised.class);
}
assertThat(testBean1.getClass()).isSameAs(testBean2.getClass());
@@ -344,8 +344,8 @@ class AspectJAutoProxyCreatorTests {
Supplier<String> supplier = context.getBean(Supplier.class);
assertThat(AopUtils.isAopProxy(supplier)).as("AOP proxy").isTrue();
assertThat(AopUtils.isJdkDynamicProxy(supplier)).as("JDK Dynamic proxy").isTrue();
assertThat(supplier.getClass().getInterfaces())
.containsExactlyInAnyOrder(Supplier.class, SpringProxy.class, Advised.class, DecoratingProxy.class);
assertThat(supplier.getClass().getInterfaces()).containsExactlyInAnyOrder(
Supplier.class, SpringProxy.class, Advised.class, DecoratingProxy.class);
assertThat(supplier.get()).isEqualTo("advised: lambda");
}
}
@@ -357,26 +357,14 @@ class AspectJAutoProxyCreatorTests {
MessageGenerator messageGenerator = context.getBean(MessageGenerator.class);
assertThat(AopUtils.isAopProxy(messageGenerator)).as("AOP proxy").isTrue();
assertThat(AopUtils.isJdkDynamicProxy(messageGenerator)).as("JDK Dynamic proxy").isTrue();
assertThat(messageGenerator.getClass().getInterfaces())
.containsExactlyInAnyOrder(MessageGenerator.class, Mixin.class, SpringProxy.class, Advised.class, DecoratingProxy.class);
assertThat(messageGenerator.getClass().getInterfaces()).containsExactlyInAnyOrder(
MessageGenerator.class, Mixin.class, SpringProxy.class, Advised.class, DecoratingProxy.class);
assertThat(messageGenerator.generateMessage()).isEqualTo("mixin: lambda");
}
}
/**
* Returns a new {@link ClassPathXmlApplicationContext} for the file ending in <var>fileSuffix</var>.
*/
private ClassPathXmlApplicationContext newContext(String fileSuffix) {
return new ClassPathXmlApplicationContext(qName(fileSuffix), getClass());
}
/**
* Returns the relatively qualified name for <var>fileSuffix</var>.
* e.g. for a fileSuffix='foo.xml', this method will return
* 'AspectJAutoProxyCreatorTests-foo.xml'
*/
private String qName(String fileSuffix) {
return String.format("%s-%s", getClass().getSimpleName(), fileSuffix);
return new ClassPathXmlApplicationContext(getClass().getSimpleName() + "-" + fileSuffix, getClass());
}
}
@@ -416,7 +404,6 @@ class DummyAspectWithParameter {
public Object test(ProceedingJoinPoint pjp, int age) throws Throwable {
return pjp.proceed();
}
}
class DummyFactoryBean implements FactoryBean<Object> {
@@ -435,7 +422,6 @@ class DummyFactoryBean implements FactoryBean<Object> {
public boolean isSingleton() {
throw new UnsupportedOperationException();
}
}
@Aspect
@@ -591,7 +577,6 @@ class UnreliableBean {
}
return this.calls;
}
}
@SuppressWarnings("serial")
@@ -607,7 +592,6 @@ class TestBeanAdvisor extends StaticMethodMatcherPointcutAdvisor {
public boolean matches(Method method, @Nullable Class<?> targetClass) {
return ITestBean.class.isAssignableFrom(targetClass);
}
}
abstract class AbstractProxyTargetClassConfig {
@@ -661,6 +645,7 @@ class PerTargetProxyTargetClassTrueConfig {
@FunctionalInterface
interface MessageGenerator {
String generateMessage();
}
@@ -678,7 +663,6 @@ class MixinIntroductionInterceptor implements IntroductionInterceptor {
public boolean implementsInterface(Class<?> intf) {
return Mixin.class.isAssignableFrom(intf);
}
}
@SuppressWarnings("serial")
@@ -708,7 +692,6 @@ class MixinAdvisor extends AbstractPointcutAdvisor implements IntroductionAdviso
public void validateInterfaces() {
/* no-op */
}
}
abstract class AbstractMixinConfig {
@@ -722,7 +705,6 @@ abstract class AbstractMixinConfig {
MixinAdvisor mixinAdvisor() {
return new MixinAdvisor();
}
}
@Configuration(proxyBeanMethods = false)
@@ -1086,8 +1086,8 @@ public abstract class AbstractAopProxyTests {
// NameReverter saved it back
assertThat(it.getName()).isEqualTo(name1);
assertThat(saver.names).hasSize(2);
assertThat(saver.names.get(0)).isEqualTo(name2);
assertThat(saver.names.get(1)).isEqualTo(name1);
assertThat(saver.names).element(0).isEqualTo(name2);
assertThat(saver.names).element(1).isEqualTo(name1);
}
@SuppressWarnings("serial")
@@ -1178,7 +1178,7 @@ public abstract class AbstractAopProxyTests {
assertThat(i2).isEqualTo(i1);
assertThat(proxyB).isEqualTo(proxyA);
assertThat(proxyB.hashCode()).isEqualTo(proxyA.hashCode());
assertThat(proxyA.equals(a)).isFalse();
assertThat(proxyA).isNotEqualTo(a);
// Equality checks were handled by the proxy
assertThat(i1.getCount()).isEqualTo(0);
@@ -1187,7 +1187,7 @@ public abstract class AbstractAopProxyTests {
// and won't think it's equal to B's NopInterceptor
proxyA.absquatulate();
assertThat(i1.getCount()).isEqualTo(1);
assertThat(proxyA.equals(proxyB)).isFalse();
assertThat(proxyA).isNotEqualTo(proxyB);
}
@Test
@@ -1874,6 +1874,14 @@ public abstract class AbstractAopProxyTests {
return target.getClass();
}
/**
* @see org.springframework.aop.TargetSource#isStatic()
*/
@Override
public boolean isStatic() {
return false;
}
/**
* @see org.springframework.aop.TargetSource#getTarget()
*/
@@ -1903,19 +1911,10 @@ public abstract class AbstractAopProxyTests {
throw new RuntimeException("Expectation failed: " + gets + " gets and " + releases + " releases");
}
}
/**
* @see org.springframework.aop.TargetSource#isStatic()
*/
@Override
public boolean isStatic() {
return false;
}
}
static abstract class ExposedInvocationTestBean extends TestBean {
abstract static class ExposedInvocationTestBean extends TestBean {
@Override
public String getName() {
@@ -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
@@ -22,8 +22,13 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import org.apache.commons.logging.LogFactory;
import org.junit.jupiter.api.Test;
@@ -56,6 +61,8 @@ import org.springframework.beans.testfixture.beans.ITestBean;
import org.springframework.beans.testfixture.beans.IndexedTestBean;
import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.beans.testfixture.beans.factory.DummyFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.UrlResource;
@@ -1336,6 +1343,15 @@ class XmlBeanFactoryTests {
assertThat(dos.lastArg).isEqualTo(s2);
}
@Test // gh-31826
void replaceNonOverloadedInterfaceMethodWithoutSpecifyingExplicitArgTypes() {
try (ConfigurableApplicationContext context =
new ClassPathXmlApplicationContext(DELEGATION_OVERRIDES_CONTEXT.getPath())) {
EchoService echoService = context.getBean(EchoService.class);
assertThat(echoService.echo("foo", "bar")).containsExactly("bar", "foo");
}
}
@Test
void lookupOverrideOneMethodWithConstructorInjection() {
DefaultListableBeanFactory xbf = new DefaultListableBeanFactory();
@@ -1891,3 +1907,20 @@ class XmlBeanFactoryTests {
}
}
interface EchoService {
String[] echo(Object... objects);
}
class ReverseArrayMethodReplacer implements MethodReplacer {
@Override
public Object reimplement(Object obj, Method method, Object[] args) {
List<String> list = Arrays.stream((Object[]) args[0])
.map(Object::toString)
.collect(Collectors.toCollection(ArrayList::new));
Collections.reverse(list);
return list.toArray(String[]::new);
}
}
@@ -54,7 +54,7 @@ class EnableCachingIntegrationTests {
@AfterEach
public void closeContext() {
void closeContext() {
if (this.context != null) {
this.context.close();
}
@@ -60,6 +60,7 @@ class CacheErrorHandlerTests {
private SimpleService simpleService;
@BeforeEach
void setup() {
this.context = new AnnotationConfigApplicationContext(Config.class);
@@ -69,11 +70,13 @@ class CacheErrorHandlerTests {
this.simpleService = context.getBean(SimpleService.class);
}
@AfterEach
void tearDown() {
void closeContext() {
this.context.close();
}
@Test
void getFail() {
UnsupportedOperationException exception = new UnsupportedOperationException("Test exception on get");
@@ -107,9 +110,9 @@ class CacheErrorHandlerTests {
this.cacheInterceptor.setErrorHandler(new SimpleCacheErrorHandler());
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() ->
this.simpleService.get(0L))
.withMessage("Test exception on get");
assertThatExceptionOfType(UnsupportedOperationException.class)
.isThrownBy(() -> this.simpleService.get(0L))
.withMessage("Test exception on get");
}
@Test
@@ -128,9 +131,9 @@ class CacheErrorHandlerTests {
this.cacheInterceptor.setErrorHandler(new SimpleCacheErrorHandler());
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() ->
this.simpleService.put(0L))
.withMessage("Test exception on put");
assertThatExceptionOfType(UnsupportedOperationException.class)
.isThrownBy(() -> this.simpleService.put(0L))
.withMessage("Test exception on put");
}
@Test
@@ -149,9 +152,9 @@ class CacheErrorHandlerTests {
this.cacheInterceptor.setErrorHandler(new SimpleCacheErrorHandler());
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() ->
this.simpleService.evict(0L))
.withMessage("Test exception on evict");
assertThatExceptionOfType(UnsupportedOperationException.class)
.isThrownBy(() -> this.simpleService.evict(0L))
.withMessage("Test exception on evict");
}
@Test
@@ -170,9 +173,9 @@ class CacheErrorHandlerTests {
this.cacheInterceptor.setErrorHandler(new SimpleCacheErrorHandler());
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() ->
this.simpleService.clear())
.withMessage("Test exception on clear");
assertThatExceptionOfType(UnsupportedOperationException.class)
.isThrownBy(() -> this.simpleService.clear())
.withMessage("Test exception on clear");
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -51,6 +51,7 @@ public class CachePutEvaluationTests {
private SimpleService service;
@BeforeEach
public void setup() {
this.context = new AnnotationConfigApplicationContext(Config.class);
@@ -59,12 +60,11 @@ public class CachePutEvaluationTests {
}
@AfterEach
public void close() {
if (this.context != null) {
this.context.close();
}
public void closeContext() {
this.context.close();
}
@Test
public void mutualGetPutExclusion() {
String key = "1";
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -72,7 +72,7 @@ class CacheResolverCustomizationTests {
}
@AfterEach
void tearDown() {
void closeContext() {
this.context.close();
}
@@ -142,16 +142,17 @@ class CacheResolverCustomizationTests {
@Test
void noCacheResolved() {
Method method = ReflectionUtils.findMethod(SimpleService.class, "noCacheResolved", Object.class);
assertThatIllegalStateException().isThrownBy(() ->
this.simpleService.noCacheResolved(new Object()))
.withMessageContaining(method.toString());
assertThatIllegalStateException()
.isThrownBy(() -> this.simpleService.noCacheResolved(new Object()))
.withMessageContaining(method.toString());
}
@Test
void unknownCacheResolver() {
assertThatExceptionOfType(NoSuchBeanDefinitionException.class).isThrownBy(() ->
this.simpleService.unknownCacheResolver(new Object()))
.satisfies(ex -> assertThat(ex.getBeanName()).isEqualTo("unknownCacheResolver"));
assertThatExceptionOfType(NoSuchBeanDefinitionException.class)
.isThrownBy(() -> this.simpleService.unknownCacheResolver(new Object()))
.satisfies(ex -> assertThat(ex.getBeanName()).isEqualTo("unknownCacheResolver"));
}
@@ -130,8 +130,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
@@ -162,8 +162,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
@@ -245,8 +245,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
@@ -126,16 +126,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
@@ -236,9 +236,9 @@ class GenericApplicationContextTests {
assertThat(context.getBeanNamesForType(BeanB.class)).containsExactly("b");
assertThat(context.getBeanNamesForType(BeanC.class)).containsExactly("c");
assertThat(context.getBeansOfType(BeanA.class)).isEmpty();
assertThat(context.getBeansOfType(BeanB.class).values().iterator().next())
assertThat(context.getBeansOfType(BeanB.class).values()).element(0)
.isSameAs(context.getBean(BeanB.class));
assertThat(context.getBeansOfType(BeanC.class).values().iterator().next())
assertThat(context.getBeansOfType(BeanC.class).values()).element(0)
.isSameAs(context.getBean(BeanC.class));
}
@@ -281,8 +281,8 @@ class GenericApplicationContextTests {
// java.nio.file.InvalidPathException: Illegal char <:> at index 4: ping:foo
if (resourceLoader instanceof FileSystemResourceLoader && OS.WINDOWS.isCurrentOs()) {
assertThatExceptionOfType(InvalidPathException.class)
.isThrownBy(() -> context.getResource(pingLocation))
.withMessageContaining(pingLocation);
.isThrownBy(() -> context.getResource(pingLocation))
.withMessageContaining(pingLocation);
}
else {
resource = context.getResource(pingLocation);
@@ -297,8 +297,8 @@ class GenericApplicationContextTests {
assertThat(resource).isInstanceOf(FileUrlResource.class);
resource = context.getResource(pingLocation);
assertThat(resource).asInstanceOf(type(ByteArrayResource.class))
.extracting(bar -> new String(bar.getByteArray(), UTF_8))
.isEqualTo("pong:foo");
.extracting(bar -> new String(bar.getByteArray(), UTF_8))
.isEqualTo("pong:foo");
}
@Test
@@ -536,7 +536,7 @@ class GenericApplicationContextTests {
}
}
static class BeanB implements ApplicationContextAware {
static class BeanB implements ApplicationContextAware {
ApplicationContext applicationContext;
@@ -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.
@@ -16,9 +16,12 @@
package org.springframework.ejb.config;
import javax.naming.NoInitialContextException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.RuntimeBeanReference;
@@ -29,6 +32,7 @@ import org.springframework.core.io.ClassPathResource;
import org.springframework.jndi.JndiObjectFactoryBean;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Rob Harrop
@@ -93,6 +97,10 @@ public class JeeNamespaceHandlerTests {
BeanDefinition beanDefinition = this.beanFactory.getMergedBeanDefinition("simpleLocalEjb");
assertThat(beanDefinition.getBeanClassName()).isEqualTo(JndiObjectFactoryBean.class.getName());
assertPropertyValue(beanDefinition, "jndiName", "ejb/MyLocalBean");
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> this.beanFactory.getBean("simpleLocalEjb"))
.withCauseInstanceOf(NoInitialContextException.class);
}
@Test
@@ -100,6 +108,32 @@ public class JeeNamespaceHandlerTests {
BeanDefinition beanDefinition = this.beanFactory.getMergedBeanDefinition("simpleRemoteEjb");
assertThat(beanDefinition.getBeanClassName()).isEqualTo(JndiObjectFactoryBean.class.getName());
assertPropertyValue(beanDefinition, "jndiName", "ejb/MyRemoteBean");
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> this.beanFactory.getBean("simpleRemoteEjb"))
.withCauseInstanceOf(NoInitialContextException.class);
}
@Test
public void testComplexLocalSlsb() {
BeanDefinition beanDefinition = this.beanFactory.getMergedBeanDefinition("complexLocalEjb");
assertThat(beanDefinition.getBeanClassName()).isEqualTo(JndiObjectFactoryBean.class.getName());
assertPropertyValue(beanDefinition, "jndiName", "ejb/MyLocalBean");
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> this.beanFactory.getBean("complexLocalEjb"))
.withCauseInstanceOf(NoInitialContextException.class);
}
@Test
public void testComplexRemoteSlsb() {
BeanDefinition beanDefinition = this.beanFactory.getMergedBeanDefinition("complexRemoteEjb");
assertThat(beanDefinition.getBeanClassName()).isEqualTo(JndiObjectFactoryBean.class.getName());
assertPropertyValue(beanDefinition, "jndiName", "ejb/MyRemoteBean");
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> this.beanFactory.getBean("complexRemoteEjb"))
.withCauseInstanceOf(NoInitialContextException.class);
}
@Test
@@ -0,0 +1,96 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.jmx.export.naming;
import java.util.function.Consumer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import org.junit.jupiter.api.Test;
import org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource;
import org.springframework.util.ClassUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
/**
* Tests for {@link MetadataNamingStrategy}.
*
* @author Stephane Nicoll
*/
class MetadataNamingStrategyTests {
private static final TestBean TEST_BEAN = new TestBean();
private final MetadataNamingStrategy strategy;
MetadataNamingStrategyTests() {
this.strategy = new MetadataNamingStrategy();
this.strategy.setDefaultDomain("com.example");
this.strategy.setAttributeSource(new AnnotationJmxAttributeSource());
}
@Test
void getObjectNameWhenBeanNameIsSimple() throws MalformedObjectNameException {
ObjectName name = this.strategy.getObjectName(TEST_BEAN, "myBean");
assertThat(name.getDomain()).isEqualTo("com.example");
assertThat(name).satisfies(hasDefaultProperties(TEST_BEAN, "myBean"));
}
@Test
void getObjectNameWhenBeanNameIsValidObjectName() throws MalformedObjectNameException {
ObjectName name = this.strategy.getObjectName(TEST_BEAN, "com.another:name=myBean");
assertThat(name.getDomain()).isEqualTo("com.another");
assertThat(name.getKeyPropertyList()).containsOnly(entry("name", "myBean"));
}
@Test
void getObjectNameWhenBeanNamContainsComma() throws MalformedObjectNameException {
ObjectName name = this.strategy.getObjectName(TEST_BEAN, "myBean,");
assertThat(name).satisfies(hasDefaultProperties(TEST_BEAN, "\"myBean,\""));
}
@Test
void getObjectNameWhenBeanNamContainsEquals() throws MalformedObjectNameException {
ObjectName name = this.strategy.getObjectName(TEST_BEAN, "my=Bean");
assertThat(name).satisfies(hasDefaultProperties(TEST_BEAN, "\"my=Bean\""));
}
@Test
void getObjectNameWhenBeanNamContainsColon() throws MalformedObjectNameException {
ObjectName name = this.strategy.getObjectName(TEST_BEAN, "my:Bean");
assertThat(name).satisfies(hasDefaultProperties(TEST_BEAN, "\"my:Bean\""));
}
@Test
void getObjectNameWhenBeanNamContainsQuote() throws MalformedObjectNameException {
ObjectName name = this.strategy.getObjectName(TEST_BEAN, "\"myBean\"");
assertThat(name).satisfies(hasDefaultProperties(TEST_BEAN, "\"\\\"myBean\\\"\""));
}
private Consumer<ObjectName> hasDefaultProperties(Object instance, String expectedName) {
return objectName -> assertThat(objectName.getKeyPropertyList()).containsOnly(
entry("type", ClassUtils.getShortName(instance.getClass())),
entry("name", expectedName));
}
static class TestBean {}
}
@@ -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<>(String.format("set bits %s", Arrays.toString(indices))) {
@Override
@@ -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.
@@ -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(""));
@@ -0,0 +1,43 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.scheduling.annotation
import org.aopalliance.intercept.MethodInvocation
import org.assertj.core.api.Assertions
import org.junit.jupiter.api.Test
import org.mockito.BDDMockito.given
import org.mockito.Mockito
/**
* Kotlin tests for [AnnotationAsyncExecutionInterceptor].
*
* @author Sebastien Deleuze
*/
class AnnotationAsyncExecutionInterceptorKotlinTests {
@Test
fun nullableUnitReturnValue() {
val interceptor = AnnotationAsyncExecutionInterceptor(null)
class C { @Async fun nullableUnit(): Unit? = null }
val invocation = Mockito.mock<MethodInvocation>()
given(invocation.method).willReturn(C::class.java.getDeclaredMethod("nullableUnit"))
Assertions.assertThat(interceptor.invoke(invocation)).isNull()
}
}
@@ -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>
@@ -40,14 +40,41 @@
</util:properties>
<!-- Local EJB Tests -->
<jee:local-slsb id="simpleLocalEjb" jndi-name="ejb/MyLocalBean"/>
<jee:local-slsb id="simpleLocalEjb" jndi-name="ejb/MyLocalBean"
business-interface="org.springframework.beans.testfixture.beans.ITestBean"/>
<jee:local-slsb id="complexLocalEjb"
jndi-name="ejb/MyLocalBean"
business-interface="org.springframework.beans.testfixture.beans.ITestBean"
cache-home="true"
lookup-home-on-startup="true"
resource-ref="true">
<jee:environment>foo=bar</jee:environment>
</jee:local-slsb>
<!-- Remote EJB Tests -->
<jee:remote-slsb id="simpleRemoteEjb" jndi-name="ejb/MyRemoteBean"/>
<jee:remote-slsb id="simpleRemoteEjb" jndi-name="ejb/MyRemoteBean"
business-interface="org.springframework.beans.testfixture.beans.ITestBean"/>
<!-- Lazy beans Tests-->
<jee:remote-slsb id="complexRemoteEjb"
jndi-name="ejb/MyRemoteBean"
business-interface="org.springframework.beans.testfixture.beans.ITestBean"
cache-home="true"
lookup-home-on-startup="true"
resource-ref="true"
home-interface="org.springframework.beans.testfixture.beans.ITestBean"
refresh-home-on-connect-failure="true"
cache-session-bean="true">
<jee:environment>foo=bar</jee:environment>
</jee:remote-slsb>
<!-- Lazy Lookup Tests-->
<jee:jndi-lookup id="lazyDataSource" jndi-name="jdbc/MyDataSource" lazy-init="true"/>
<jee:local-slsb id="lazyLocalBean" jndi-name="ejb/MyLocalBean" lazy-init="true"/>
<jee:remote-slsb id="lazyRemoteBean" jndi-name="ejb/MyRemoteBean" lazy-init="true"/>
<jee:local-slsb id="lazyLocalBean" jndi-name="ejb/MyLocalBean"
business-interface="org.springframework.beans.testfixture.beans.ITestBean" lazy-init="true"/>
<jee:remote-slsb id="lazyRemoteBean" jndi-name="ejb/MyRemoteBean"
business-interface="org.springframework.beans.testfixture.beans.ITestBean" lazy-init="true"/>
</beans>
@@ -20,6 +20,7 @@ import org.springframework.core.io.InputStreamSource;
import org.springframework.javapoet.JavaFile;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.function.ThrowingConsumer;
/**
@@ -43,6 +44,7 @@ public interface GeneratedFiles {
* @param javaFile the java file to add
*/
default void addSourceFile(JavaFile javaFile) {
validatePackage(javaFile.packageName, javaFile.typeSpec.name);
String className = javaFile.packageName + "." + javaFile.typeSpec.name;
addSourceFile(className, javaFile::writeTo);
}
@@ -161,11 +163,20 @@ public interface GeneratedFiles {
private static String getClassNamePath(String className) {
Assert.hasLength(className, "'className' must not be empty");
validatePackage(ClassUtils.getPackageName(className), className);
Assert.isTrue(isJavaIdentifier(className),
"'className' must be a valid identifier, got '" + className + "'");
return ClassUtils.convertClassNameToResourcePath(className) + ".java";
}
private static void validatePackage(String packageName, String className) {
if (!StringUtils.hasLength(packageName)) {
throw new IllegalArgumentException("Could not add '" + className + "', "
+ "processing classes in the default package is not supported. "
+ "Did you forget to add a package statement?");
}
}
private static boolean isJavaIdentifier(String className) {
char[] chars = className.toCharArray();
for (int i = 0; i < chars.length; i++) {
@@ -195,9 +195,15 @@ public class BindingReflectionHintsRegistrar {
}
private void registerHintsForClassAttributes(ReflectionHints hints, MergedAnnotation<Annotation> annotation) {
annotation.getRoot().asMap().values().forEach(value -> {
annotation.getRoot().asMap().forEach((key,value) -> {
if (value instanceof Class<?> classValue && value != Void.class) {
hints.registerType(classValue, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
if (key.equals("builder")) {
hints.registerType(classValue, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_DECLARED_METHODS);
}
else {
hints.registerType(classValue, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
}
}
});
}
@@ -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.
@@ -159,7 +159,7 @@ final class SerializableTypeWrapper {
/**
* Return the source of the type, or {@code null} if not known.
* <p>The default implementations returns {@code null}.
* <p>The default implementation returns {@code null}.
*/
@Nullable
default Object getSource() {
@@ -214,7 +214,12 @@ final class SerializableTypeWrapper {
return result;
}
return ReflectionUtils.invokeMethod(method, this.provider.getType(), args);
Type type = this.provider.getType();
if (type instanceof TypeVariable<?> tv && method.getName().equals("getName")) {
// Avoid reflection for common comparison of type variables
return tv.getName();
}
return ReflectionUtils.invokeMethod(method, type, args);
}
}
@@ -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.
@@ -757,7 +757,7 @@ public abstract class AnnotationUtils {
* Google App Engine's late arrival of {@code TypeNotPresentExceptionProxy} for
* {@code Class} values (instead of early {@code Class.getAnnotations() failure}).
* <p>This method not failing indicates that {@link #getAnnotationAttributes(Annotation)}
* won't failure either (when attempted later on).
* won't fail either (when attempted later on).
* @param annotation the annotation to validate
* @throws IllegalStateException if a declared {@code Class} attribute could not be read
* @since 4.3.15
@@ -1056,8 +1056,7 @@ public abstract class AnnotationUtils {
return null;
}
catch (Throwable ex) {
rethrowAnnotationConfigurationException(ex);
handleIntrospectionFailure(annotation.getClass(), ex);
handleValueRetrievalFailure(annotation, ex);
return null;
}
}
@@ -1073,14 +1072,18 @@ public abstract class AnnotationUtils {
* @return the value returned from the method invocation
* @since 5.3.24
*/
static Object invokeAnnotationMethod(Method method, Object annotation) {
@Nullable
static Object invokeAnnotationMethod(Method method, @Nullable Object annotation) {
if (annotation == null) {
return null;
}
if (Proxy.isProxyClass(annotation.getClass())) {
try {
InvocationHandler handler = Proxy.getInvocationHandler(annotation);
return handler.invoke(annotation, method, null);
}
catch (Throwable ex) {
// ignore and fall back to reflection below
// Ignore and fall back to reflection below
}
}
return ReflectionUtils.invokeMethod(method, annotation);
@@ -1114,20 +1117,32 @@ public abstract class AnnotationUtils {
* @see #rethrowAnnotationConfigurationException
* @see IntrospectionFailureLogger
*/
static void handleIntrospectionFailure(@Nullable AnnotatedElement element, Throwable ex) {
static void handleIntrospectionFailure(AnnotatedElement element, Throwable ex) {
rethrowAnnotationConfigurationException(ex);
IntrospectionFailureLogger logger = IntrospectionFailureLogger.INFO;
boolean meta = false;
if (element instanceof Class<?> clazz && Annotation.class.isAssignableFrom(clazz)) {
// Meta-annotation or (default) value lookup on an annotation type
// Meta-annotation introspection failure
logger = IntrospectionFailureLogger.DEBUG;
meta = true;
}
if (logger.isEnabled()) {
String message = meta ?
"Failed to meta-introspect annotation " :
"Failed to introspect annotations on ";
logger.log(message + element + ": " + ex);
logger.log("Failed to " + (meta ? "meta-introspect annotation " : "introspect annotations on ") +
element + ": " + ex);
}
}
/**
* Handle the supplied value retrieval exception.
* @param annotation the annotation instance from which to retrieve the value
* @param ex the exception that we encountered
* @see #handleIntrospectionFailure
*/
private static void handleValueRetrievalFailure(Annotation annotation, Throwable ex) {
rethrowAnnotationConfigurationException(ex);
IntrospectionFailureLogger logger = IntrospectionFailureLogger.INFO;
if (logger.isEnabled()) {
logger.log("Failed to retrieve value from " + annotation + ": " + ex);
}
}
@@ -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.
@@ -336,11 +336,10 @@ abstract class AnnotationsScanner {
Method[] methods = baseTypeMethodsCache.get(baseType);
if (methods == null) {
boolean isInterface = baseType.isInterface();
methods = isInterface ? baseType.getMethods() : ReflectionUtils.getDeclaredMethods(baseType);
methods = ReflectionUtils.getDeclaredMethods(baseType);
int cleared = 0;
for (int i = 0; i < methods.length; i++) {
if ((!isInterface && Modifier.isPrivate(methods[i].getModifiers())) ||
if (Modifier.isPrivate(methods[i].getModifiers()) ||
hasPlainJavaAnnotationsOnly(methods[i]) ||
getDeclaredAnnotations(methods[i], false).length == 0) {
methods[i] = null;
@@ -454,7 +453,7 @@ abstract class AnnotationsScanner {
for (int i = 0; i < annotations.length; i++) {
Annotation annotation = annotations[i];
if (isIgnorable(annotation.annotationType()) ||
!AttributeMethods.forAnnotationType(annotation.annotationType()).isValid(annotation)) {
!AttributeMethods.forAnnotationType(annotation.annotationType()).canLoad(annotation)) {
annotations[i] = null;
}
else {
@@ -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,7 +45,7 @@ final class AttributeMethods {
if (m1 != null && m2 != null) {
return m1.getName().compareTo(m2.getName());
}
return m1 != null ? -1 : 1;
return (m1 != null ? -1 : 1);
};
@@ -87,18 +87,26 @@ final class AttributeMethods {
/**
* Determine if values from the given annotation can be safely accessed without
* causing any {@link TypeNotPresentException TypeNotPresentExceptions}.
* <p>This method is designed to cover Google App Engine's late arrival of such
* exceptions for {@code Class} values (instead of the more typical early
* {@code Class.getAnnotations() failure} on a regular JVM).
* @param annotation the annotation to check
* @return {@code true} if all values are present
* @see #validate(Annotation)
*/
boolean isValid(Annotation annotation) {
boolean canLoad(Annotation annotation) {
assertAnnotation(annotation);
for (int i = 0; i < size(); i++) {
if (canThrowTypeNotPresentException(i)) {
try {
AnnotationUtils.invokeAnnotationMethod(get(i), annotation);
}
catch (IllegalStateException ex) {
// Plain invocation failure to expose -> leave up to attribute retrieval
// (if any) where such invocation failure will be logged eventually.
}
catch (Throwable ex) {
// TypeNotPresentException etc. -> annotation type not actually loadable.
return false;
}
}
@@ -108,13 +116,13 @@ final class AttributeMethods {
/**
* Check if values from the given annotation can be safely accessed without causing
* any {@link TypeNotPresentException TypeNotPresentExceptions}. In particular,
* this method is designed to cover Google App Engine's late arrival of such
* any {@link TypeNotPresentException TypeNotPresentExceptions}.
* <p>This method is designed to cover Google App Engine's late arrival of such
* exceptions for {@code Class} values (instead of the more typical early
* {@code Class.getAnnotations() failure}).
* {@code Class.getAnnotations() failure} on a regular JVM).
* @param annotation the annotation to validate
* @throws IllegalStateException if a declared {@code Class} attribute could not be read
* @see #isValid(Annotation)
* @see #canLoad(Annotation)
*/
void validate(Annotation annotation) {
assertAnnotation(annotation);
@@ -123,6 +131,9 @@ final class AttributeMethods {
try {
AnnotationUtils.invokeAnnotationMethod(get(i), annotation);
}
catch (IllegalStateException ex) {
throw ex;
}
catch (Throwable ex) {
throw new IllegalStateException("Could not obtain annotation attribute value for " +
get(i).getName() + " declared on " + annotation.annotationType(), ex);
@@ -147,7 +158,7 @@ final class AttributeMethods {
@Nullable
Method get(String name) {
int index = indexOf(name);
return index != -1 ? this.attributeMethods[index] : null;
return (index != -1 ? this.attributeMethods[index] : null);
}
/**
@@ -475,7 +475,7 @@ public class TypeDescriptor implements Serializable {
ObjectUtils.nullSafeEquals(getMapValueTypeDescriptor(), otherDesc.getMapValueTypeDescriptor()));
}
else {
return true;
return Arrays.equals(getResolvableType().getGenerics(), otherDesc.getResolvableType().getGenerics());
}
}
@@ -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.
@@ -152,8 +152,7 @@ public interface Resource extends InputStreamSource {
}
/**
* Returns the contents of this resource as a string, using the specified
* charset.
* Return the contents of this resource as a string, using the specified charset.
* @param charset the charset to use for decoding
* @return the contents of this resource as a {@code String}
* @throws java.io.FileNotFoundException if the resource cannot be resolved as

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