Compare commits

...

261 Commits

Author SHA1 Message Date
rstoyanchev 8d16a50907 Efficient ETag parsing
Closes gh-33372
2024-08-14 07:53:23 +03:00
Juergen Hoeller ab161ca796 Upgrade to Netty 4.1.112 and Undertow 2.3.15 2024-08-13 19:34:50 +02:00
Juergen Hoeller b5377ee9cf Polishing 2024-08-13 19:34:30 +02:00
Stéphane Nicoll eff1ca92f0 Upgrade to Reactor 2022.0.22
Closes gh-33324
2024-08-13 17:24:20 +02:00
Sam Brannen 79c7bfdbad Introduce withAssignmentDisabled() option for SimpleEvaluationContext
To support additional use cases, this commit introduces a
withAssignmentDisabled() method in the Builder for
SimpleEvaluationContext.

See gh-33319
Closes gh-33321

(cherry picked from commit e74406afd0)
2024-08-13 17:49:44 +03:00
Riley Park f78b09fddf Fix incorrect weak ETag assertion
Closes gh-33376
2024-08-13 17:02:11 +03:00
Stéphane Nicoll b7ca746493 Sync GHA setup 2024-08-09 16:05:42 +02:00
Stéphane Nicoll 6c062399ea Start building against Reactor 2022.0.22 snapshots
See gh-33324
2024-08-07 09:55:31 +02:00
Sam Brannen e1ab306506 Enforce read-only semantics in SpEL's SimpleEvaluationContext
SimpleEvaluationContext.forReadOnlyDataBinding() documents that it
creates a SimpleEvaluationContext for read-only access to public
properties; however, prior to this commit write access was not disabled
for indexed structures when using the assignment operator, the
increment operator, or the decrement operator.

In order to better align with the documented contract for
forReadOnlyDataBinding(), this commit makes it possible to disable
assignment in general in order to enforce read-only semantics for
SpEL's SimpleEvaluationContext when created via the
forReadOnlyDataBinding() factory method. Specifically:

- This commit introduces a new isAssignmentEnabled() "default" method
  in the EvaluationContext API, which returns true by default.

- SimpleEvaluationContext overrides isAssignmentEnabled(), returning
  false if the context was created via the forReadOnlyDataBinding()
  factory method.

- The Assign, OpDec, and OpInc AST nodes -- representing the assignment
  (=), increment (++), and decrement (--) operators, respectively --
  now throw a SpelEvaluationException if assignment is disabled for the
  current EvaluationContext.

See gh-33319
Closes gh-33321

(cherry picked from commit 0127de5a7a)
2024-08-06 14:12:03 +03:00
Sam Brannen 2165b1651b Throw exception for failure to set property as index in SpEL
Prior to this commit, the Indexer in the Spring Expression Language
(SpEL) silently ignored a failure to set a property via the indexed
property syntax (['<property name>'] = <new value>) – for example, if
property write access was disabled in the EvaluationContext.

This commit addresses this issue by properly throwing a
SpelEvaluationException in PropertyIndexingValueRef.setValue(Object) if
the property could not be set.

See gh-33310
Closes gh-33311

(cherry picked from commit c57c2272a1)
2024-08-05 14:27:03 +03:00
Sam Brannen bf72e92145 Polishing 2024-08-05 14:25:17 +03:00
Sam Brannen 3e7372491c Support conversion from primitive array to Object[] in ConversionService
Prior to this commit, the ConversionService failed to convert a primitive
array (such as int[]) to an Object[] due to an error in the logic in
ArrayToArrayConverter.

This commit addresses this by augmenting the "can bypass conversion"
check in ArrayToArrayConverter to ensure that the supplied source object
is an instance of the target type (i.e., that the source array can be
cast to the target type array without conversion).

Closes gh-33212

(cherry picked from commit cb6a5baac5)
2024-08-05 10:23:29 +03:00
Sam Brannen e11a419cc3 Revert "Support single String argument for varargs invocations in SpEL"
This reverts commit d33f66d9b5.

See gh-33013
See gh-33189
2024-08-04 16:43:07 +03:00
Sam Brannen d33f66d9b5 Support single String argument for varargs invocations in SpEL
Prior to this commit, the Spring Expression Language (SpEL) incorrectly
split single String arguments by comma for Object... varargs method and
constructor invocations.

This commit addresses this by checking if the single argument type is
already "assignable" to the varargs component type instead of "equal"
to the varargs component type.

See gh-33013
Closes gh-33189
2024-07-26 17:46:19 +03:00
Stéphane Nicoll 8da2963644 Remove useless permissions on build-and-deploy-snapshot workflow
Closes gh-33239
2024-07-19 11:47:06 +02:00
Stéphane Nicoll 9a11c12740 Upgrade to Gradle 8.9
Closes gh-33215
2024-07-14 11:13:35 +02:00
Stéphane Nicoll 743c8f755b Add missing artifact properties for staging
This commit makes sure that docs artifacts have their attributes set
for staging as well. Previously they were not and deployment of Javadoc
did not occur.

Closes gh-33207
2024-07-12 08:57:19 +02:00
Stéphane Nicoll 0376ebeaf3 Remove concourse configuration now that CI is using GitHub Actions 2024-07-11 16:58:01 +02:00
Stéphane Nicoll a2a793e324 Release from GitHub Actions
Closes gh-33201
2024-07-11 16:54:40 +02:00
Juergen Hoeller fd7e7633d4 Polishing 2024-07-10 15:45:02 +02:00
Stéphane Nicoll be7029c35d Retain previous factory method in case of nested invocation with AOT
This commit harmonizes the invocation of a bean supplier with what
SimpleInstantiationStrategy does. Previously, the current factory method
was set to `null` once the invocation completes. This did not take
into account recursive scenarios where an instance supplier triggers
another instance supplier.

For consistency, the thread local is removed now if we attempt to set
the current method to null. SimpleInstantiationStrategy itself uses
the shortcut to align the code as much as possible.

Closes gh-33185
2024-07-10 12:40:24 +02:00
Juergen Hoeller 84efba682d Upgrade to Groovy 4.0.22, Jetty 11.0.22, Undertow 2.3.14
Includes downgrade to Awaitility 4.2.0 (aligned with 6.1.x branch)
2024-07-08 23:25:34 +02:00
Stéphane Nicoll e66be91005 Backport "Harmonize CI configuration"
This commit applies the harmonization changes in eacfd77 whilst keeping
the doc attributes specific changes to 6.0.x
2024-07-05 10:05:04 +02:00
Stéphane Nicoll c7fd5c9c34 Upgrade to Gradle 8.8
Closes gh-33147
2024-07-04 10:31:50 +02:00
Juergen Hoeller 09123def57 Detect ajc markers in superclasses as well (for weaving check)
Closes gh-33113

(cherry picked from commit 100da83913)
2024-07-03 17:12:08 +02:00
Juergen Hoeller 069d0819dd Apply fallback resolution for non-hierarchical URIs such as "file:."
Includes meaningful exception message for file system resolution.

Closes gh-33124

(cherry picked from commit daea3f0eae)
2024-07-03 16:43:14 +02:00
Brian Clozel a352d884c9 Re-enable async dispatches in Observation Filter
Prior to this commit, the fix for gh-32730 disabled the involvment of
the osbervation filter for async dispatches. Instead of relying on ASYNC
dispatches to close the observation for async requests, this is now
using an async listener instead: async dispatches are not guaranteed to
happen once the async request is handled.

This change caused another side-effect: because async dispatches are not
considered anymore by this filter, the observation scope is not
reinstated for async dispatches. For example, `ResponseBodyAdvice`
implementations do not have the observation scope opened during their
execution.

This commit re-enables async dispatches for this filter, but ensures
that observations are not closed during such dispatches as this will be
done by the async listener.

Fixes gh-33128
2024-07-01 11:49:42 +02:00
Stéphane Nicoll 3498a35377 Fix name of GitHub actions bot
See gh-gh-33077
2024-06-21 14:15:58 +02:00
Stéphane Nicoll 06fc5bc3ea Exclude GitHub Actions bot from changelog
Closes gh-33077
2024-06-20 14:17:02 +02:00
Brian Clozel f59b56c4c9 Use Sonatype S01 token in release pipeline 2024-06-18 19:28:28 +02:00
Juergen Hoeller 5a83fc8f86 Polishing 2024-06-17 19:07:04 +02:00
Juergen Hoeller db8d2d1626 Backported test for @Autowired @Bean method on configuration subclass
See gh-33030
2024-06-17 19:06:49 +02:00
Juergen Hoeller 12949becfc Correct and consistent event class names in constructor javadoc
Closes gh-33032

(cherry picked from commit e79a9a5bff)
2024-06-17 18:58:20 +02:00
Stéphane Nicoll debf571722 Fix typo
Closes gh-33054
2024-06-17 13:39:44 +02:00
github-actions[bot] f87b1bd6ce Update Antora Spring UI to v0.4.16 2024-06-17 10:06:47 +00:00
Stéphane Nicoll e17afed0fe Fix property name in Container Extension Points section
Closes gh-33038
2024-06-16 16:49:18 +02:00
Juergen Hoeller 77964374f9 Do not attempt to load pre-enhanced class for reloadable classes
Closes gh-33024

(cherry picked from commit 089e4e69f1)
2024-06-14 22:12:11 +02:00
Stéphane Nicoll 67686d776e Fix invalid character in Javadoc of BeanFactory 2024-06-13 14:10:00 +02:00
Spring Builds 2d83fca571 Next development version (v6.0.23-SNAPSHOT) 2024-06-13 09:24:34 +00:00
Juergen Hoeller f285df692c Upgrade to SLF4J 2.0.13, JRuby 9.4.7, Awaitility 4.2.1 2024-06-12 14:42:49 +02:00
Juergen Hoeller b9eeee8341 Polishing 2024-06-12 13:59:33 +02:00
Juergen Hoeller df1f4812aa Upgrade to Netty 4.1.111 2024-06-12 13:58:57 +02:00
Juergen Hoeller 230848c8e5 Upgrade to spring-javaformat-checkstyle 0.0.42 2024-06-12 13:58:27 +02:00
Stéphane Nicoll eda868792a Upgrade to Reactor 2022.0.20
Closes gh-33008
2024-06-11 14:35:56 +02:00
Brian Clozel 76604db8da Stop observations for async requests in Servlet filter
Prior to this commit, the `ServerHttpObservationFilter` would support
async dispatches and would do the following:

1. start the observation
2. call the filter chain
3. if async has started, do nothing
4. if not in async mode, stop the observation

This behavior would effectively rely on Async implementations to
complete and dispatch the request back to the container for an async
dispatch. This is what Spring web frameworks do and guarantee.

Some implementations complete the async request but do not dispatch
back; as a result, observations could leak as they are never stopped.

This commit changes the support of async requests. The filter now
opts-out of async dispatches - the filter will not be called for those
anymore. Instead, if the application started async mode during the
initial container dispatch, the filter will register an AsyncListener to
be notified of the outcome of the async handling.

Fixes gh-32986
2024-06-07 19:00:26 +02:00
Sébastien Deleuze 38b7209dc5 Polishing
See gh-32984
2024-06-07 13:44:30 +02:00
Sébastien Deleuze df0764db5d Remove outdated copyright from index.adoc
Closes gh-32984
2024-06-07 13:38:25 +02:00
Sébastien Deleuze 1e2c4635a3 Exclude node_modules from NoHttp checks
Closes gh-32981
2024-06-07 11:49:43 +02:00
Juergen Hoeller 628b0504ec Skip ajc-compiled aspects for ajc-compiled target classes
Includes defensive ignoring of incompatible aspect types.

Closes gh-32970

(cherry picked from commit 0ea96b4806)
2024-06-06 21:01:42 +02:00
Juergen Hoeller 8b589db028 Avoid NoSuchMethodException for annotation attribute checks
Closes gh-32921

(cherry picked from commit b08883b65c)
2024-06-03 12:51:47 +02:00
Sam Brannen aed1d5f762 Support compilation of map indexing with primitive in SpEL
Prior to this commit, the Spring Expression Language (SpEL) failed to
compile an expression that indexed into a Map using a primitive literal
(boolean, int, long, float, or double).

This commit adds support for compilation of such expressions by
ensuring that primitive literals are boxed into their corresponding
wrapper types in the compiled bytecode.

Closes gh-32903

(cherry picked from commit e9de426eb5)
2024-05-28 10:39:54 +02:00
Sam Brannen 079d53c8d6 Support compilation of array and list indexing with Integer in SpEL
Prior to this commit, the Spring Expression Language (SpEL) failed to
compile an expression that indexed into an array or list using an
Integer.

This commit adds support for compilation of such expressions by
ensuring that an Integer is unboxed into an int in the compiled
bytecode.

See gh-32694
Closes gh-32908

(cherry picked from commit cda577d1aa)
2024-05-27 17:28:54 +02:00
Juergen Hoeller 206a89017c Test detection of original generic method for CGLIB bridge method
See gh-32888
2024-05-24 12:29:31 +02:00
Juergen Hoeller 8d1bf9607b Polishing
(cherry picked from commit 6c08d93992)
2024-05-23 17:12:22 +02:00
Juergen Hoeller e12440a7af Defensive handling of incompatible advice methods
This covers AspectJ transaction and caching aspects when encountered by Spring AOP.

Closes gh-32882
See gh-32793

(cherry picked from commit 6d7cd9c7dc)
2024-05-23 17:12:18 +02:00
Spring Builds 093e6a8e8d Next development version (v6.0.22-SNAPSHOT) 2024-05-22 16:07:26 +00:00
Stéphane Nicoll c993615f98 Remove outdated Javadoc links
Closes gh-32873
2024-05-22 14:49:07 +02:00
Rob Winch d068f5a4c6 Modernize Antora Build
- Use same playbook as docs-build
- Use Env Variables to cause partial build (same as docs-build)
- Use package.json so that dependencies can be updated with dependabot
2024-05-22 10:20:33 +02:00
Juergen Hoeller e73d68b0a8 Select most specific advice method in case of override
Closes gh-32865

(cherry picked from commit ea596aa211)
2024-05-22 10:07:31 +02:00
Juergen Hoeller 0ca47e5e03 Polishing 2024-05-21 18:24:04 +02:00
Juergen Hoeller 33d3496a16 Polishing
(cherry picked from commit 20dea0dae2)
2024-05-21 17:59:39 +02:00
Juergen Hoeller 84a5a8a61e Default fallback parsing for UTC without milliseconds
Closes gh-32856

(cherry picked from commit fee17e11ba)
2024-05-21 17:52:11 +02:00
Juergen Hoeller 2b8a1faeaa Polishing
(cherry picked from commit 65e1337d35)
2024-05-21 11:42:30 +02:00
Juergen Hoeller 4c9de3cbbd Avoid creation of SAXParserFactory for every read operation
Includes JAXBContext locking revision (avoiding synchronization) and consistent treatment of DocumentBuilderFactory (in terms of caching as well as locking).

Closes gh-32851

(cherry picked from commit a4c2f291d9)
2024-05-21 11:35:08 +02:00
Stéphane Nicoll a0f07af375 Avoid reader on empty content to be shared by multiple requests
This commit avoids several instances of MockHttpServletRequest to
have a common reader for empty content as closing it will have an
unwanted side effect on the others.

Closes gh-32848
2024-05-20 13:59:50 +02:00
Juergen Hoeller 97e12bd0e8 Defensively catch and log pointcut parsing exceptions
Closes gh-32838
See gh-32793

(cherry picked from commit 617833bec9)
2024-05-17 12:50:12 +02:00
Spring Builds c374c46cdc Next development version (v6.0.21-SNAPSHOT) 2024-05-16 08:24:45 +00:00
rstoyanchev ea208dc304 Polishing contribution
Closes gh-32799
2024-05-15 20:16:39 +01:00
Juergen Hoeller c8c95e360f Polishing (aligned with 6.1.x) 2024-05-15 14:31:48 +02:00
Juergen Hoeller 9fb36a5dcb Upgrade to Reactor 2022.0.19
Includes AspectJ 1.9.22.1, Mockito 5.12, plugin alignment with 6.1.x

Closes gh-32787
2024-05-14 22:45:33 +02:00
Juergen Hoeller 4caf6bc5b8 Polishing 2024-05-14 13:45:38 +02:00
Juergen Hoeller ee3e1591de Polishing 2024-05-14 13:03:35 +02:00
Juergen Hoeller e81c788274 Accept ajc-compiled @Aspect classes for Spring AOP proxy usage
AspectJExpressionPointcut leniently ignores unsupported expression.

Closes gh-32793
2024-05-14 13:03:29 +02:00
rstoyanchev 09f23a578b Update docs on HandlerInterceptor
Closes gh-32729
2024-05-13 12:10:52 +01:00
rstoyanchev 5288504ceb Use instance field for ProblemDetail in ErrorResponse's
Closes gh-32644
2024-05-13 12:10:52 +01:00
rstoyanchev 8b6a54c45f Update MockMvc section on Streaming in the docs
Closes gh-32687
2024-05-13 12:10:52 +01:00
Sam Brannen f3f3063091 Fix Dokka links to Spring Framework and Servlet APIs
This commit fixes links from Spring Framework's Dokka HTML to Javadoc
for Spring Framework and Servlet APIs by explicitly configuring the
`element-list` page as the `package-list` in the Dokka Gradle plugin.

Closes gh-32797

(cherry picked from commit 7536980be9)
2024-05-12 12:33:00 +02:00
Stéphane Nicoll c1f3e37acd Polish 2024-05-08 15:35:41 +02:00
Stéphane Nicoll 427a96befc Adapt docs deployment properties
This commit fixes the artifact properties we set for "framework-docs"
artifacts. These have a different name as of 6.1.x and were backported
as is.

Closes gh-32780
2024-05-08 15:21:47 +02:00
Juergen Hoeller 77951dc622 Consistent RuntimeHintsRegistrar signature (plus related polishing) 2024-05-07 15:43:17 +02:00
Juergen Hoeller 43dd22ba31 Polishing
(cherry picked from commit 05d9b52b19)
2024-05-06 20:28:41 +02:00
Juergen Hoeller dfe437ac88 Unwrap raw target Query instance in case of proxy mismatch
Closes gh-32766

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

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

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

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

See gh-29685
Closes gh-32731

(cherry picked from commit 4baad16437)
2024-05-03 12:19:27 +03:00
Juergen Hoeller 9a31f3b5a5 Consistently propagate ApplicationStartup to BeanFactory
Closes gh-32747

(cherry picked from commit 25cedcfb99)
2024-05-01 18:11:55 +02:00
Juergen Hoeller b11d118376 Upgrade to Groovy 4.0.21, Netty 4.1.109, Undertow 2.3.13 2024-05-01 15:56:28 +02:00
Juergen Hoeller 0931769822 Polishing 2024-05-01 15:49:14 +02:00
Juergen Hoeller f7b7e29fd7 Ignore non-String keys in PropertiesPropertySource.getPropertyNames()
Closes gh-32742

(cherry picked from commit 610626aec6)
2024-05-01 15:45:21 +02:00
Brian Clozel 08a63a4180 Fix build warnings
See gh-32726
2024-04-30 19:09:44 +02:00
Arjen Poutsma 4e3b834310 Fix guard against multiple subscriptions
This commit changes the guard against multiple subscriptions, as the
previously used doOnSubscribe hook could not function as guard in
certain scenarios.

See gh-32727
Closes gh-32732
2024-04-30 16:20:20 +02:00
Brian Clozel fb67f97a41 Upgrade to gradle-enterprise-conventions 0.0.17
Closes gh-32726
2024-04-29 13:16:15 +02:00
Stéphane Nicoll cafb5cfbbe Refine preDetermineBeanTypes algorithm
This commit refines the preDetermineBeanTypes algorithm that AOT uses
to approximate the order of preInstantiateSingletons more closely.

Previously, the algorithm assumed that all beans where non-lazy
singletons in terms of initialization order, which led to inconsistent
order in CGLIB proxy generation.

We now explicitly park lazy beans so that their types are determined
during a second phase, matching the order of regular initialization
order.

Closes gh-32701

Co-authored-by: Juergen Hoeller <juergen.hoeller@broadcom.com>
2024-04-24 11:09:44 +02:00
Juergen Hoeller 9d2c6f80b8 Polishing 2024-04-23 16:25:24 +02:00
Juergen Hoeller d9330bcac7 Skip close lock if acquired by other thread already
Closes gh-32445

(cherry picked from commit d151931f86)
2024-04-23 16:11:16 +02:00
Stéphane Nicoll 1fb179b057 Relax scope of DataIntegrityViolationException
This commit updates the Javadoc of DataIntegrityViolationException to
broaden its scope as it was too specific before.

Closes gh-32686
2024-04-22 14:53:06 +02:00
Juergen Hoeller 5c9f364352 Polishing
(cherry picked from commit ec1f5ca600)
2024-04-22 14:07:51 +02:00
Juergen Hoeller 55f9581743 Try early initialization for all user-declared methods (including interfaces)
Closes gh-32682

(cherry picked from commit 62efdfb89c)
2024-04-22 13:51:15 +02:00
Stéphane Nicoll 190397d5e0 Upgrade to Java 17.0.11 2024-04-22 12:12:11 +02:00
Juergen Hoeller 94097fb112 Polishing (aligned with 6.1.x) 2024-04-18 12:46:55 +02:00
yhao3 e4ab2aa775 Update links to HttpOnly documentation at OWASP in ResponseCookie
See gh-32663
Closes gh-32667

(cherry picked from commit 7f27ba3902)
2024-04-18 12:00:10 +02:00
Simon Baslé 40bf550d56 Ensure multipart data is deleted in WebFlux when connection terminates
Before this change temporary files would not consistently be deleted
when the connection which uploads the multipart files closes naturally.

This change uses the usingWhen Reactor operator to ensure that the
termination of the connection doesn't prevent individual file parts
from being deleted due to a cancellation signal.

See gh-31217
Closes gh-32638
2024-04-15 14:22:42 +02:00
Spring Builds f3bdce455e Next development version (v6.0.20-SNAPSHOT) 2024-04-11 07:56:46 +00:00
Brian Clozel 402246df28 Refine UriComponentsBuilder parsing
This commit refines the expressions for the scheme, user info, host and
port parts of the URL in UriComponentsBuilder to better conform to
RFC 3986.

Fixes gh-32617
2024-04-11 08:50:23 +02:00
Stéphane Nicoll 88a68ddbd2 Upgrade to Reactor 2022.0.18
Closes gh-32593
2024-04-09 19:54:22 +02:00
Sam Brannen 7d178c6771 Detect bridge methods across ApplicationContexts in MethodIntrospector
Prior to this commit, MethodIntrospector failed to properly detect
bridge methods for subsequent invocations of selectMethods() with the
same targetType and MetadataLookup, if such subsequent invocations
occurred after the ApplicationContext had been refreshed.

The reason this occurs is due to the following.

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

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

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

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

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

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

Closes gh-32586

(cherry picked from commit e702733c7b)
2024-04-09 19:05:53 +02:00
Juergen Hoeller ba776d7201 Log column type for limited support message in getResultSetValue
Closes gh-32601

(cherry picked from commit c5590ae9e6)
2024-04-09 16:20:51 +02:00
Juergen Hoeller aba5f421fc Remove accidental backport of 6.1 class 2024-04-08 23:51:13 +02:00
Juergen Hoeller 2bac1629e6 Upgrade to Groovy 4.0.20, Netty 4.1.108, OpenPDF 1.3.43 2024-04-08 23:26:19 +02:00
Juergen Hoeller 9412d782ce Revised tests for generic FactoryBean type matching (backported)
See gh-32489
2024-04-08 23:00:33 +02:00
Juergen Hoeller a0bd13ceb1 Do not extract FactoryBean generic in case of targetType mismatch
Closes gh-32489
2024-04-08 10:16:48 +02:00
Stéphane Nicoll 46a0821205 Stop linking to Websphere's Javadoc
Closes gh-32582
2024-04-05 15:58:42 +02:00
Stéphane Nicoll 2c531344c6 Upgrade to Gradle 8.7
Closes gh-32567
2024-04-02 18:02:34 +02:00
Stéphane Nicoll 6610ee360e Upgrade actions that use deprecated features 2024-03-20 10:35:46 +01:00
Minsung Oh afde96c540 Fix link to vavr in the reference guide
Closes gh-32495
2024-03-20 10:03:29 +01:00
Sam Brannen da799bc519 Wrap InvalidMimeTypeException in HttpMediaTypeNotAcceptableException
The fix for #31254 resulted in an InvalidMimeTypeException being thrown
by MimeTypeUtils.sortBySpecificity() instead of an
IllegalArgumentException. However, InvalidMimeTypeException extends
IllegalArgumentException. Consequently, the change from
IllegalArgumentException to InvalidMimeTypeException did not result in
the desired effect in HeaderContentNegotiationStrategy.

HeaderContentNegotiationStrategy.resolveMediaTypes() still allows the
InvalidMimeTypeException to propagate as-is without wrapping it in an
HttpMediaTypeNotAcceptableException.

To address this issue, this commit catches InvalidMediaTypeException
and InvalidMimeTypeException in HeaderContentNegotiationStrategy and
wraps the exception in an HttpMediaTypeNotAcceptableException.

See gh-31254
See gh-31769
Closes gh-32483

(cherry picked from commit ef02f0bad8)
2024-03-19 15:55:15 +01:00
Sam Brannen 755968fd2c Polishing
(cherry picked from commit 836a0b3a40)
2024-03-19 15:55:08 +01:00
Juergen Hoeller eea00628f9 Polishing 2024-03-18 16:22:03 +01:00
Juergen Hoeller 8d745462b4 Propagate JMS IllegalStateException from commit/rollbackIfNecessary
Closes gh-32473

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

(cherry picked from commit 42a4f28962)
2024-03-18 15:18:43 +01:00
Juergen Hoeller 8460a2d285 Restore original toString representation (revert accidental backport)
See gh-32405
2024-03-17 20:43:07 +01:00
Juergen Hoeller c3d186b4d0 Remove superfluous @NonNull declarations 2024-03-16 14:23:12 +01:00
Juergen Hoeller f8926d6a66 Avoid cloning empty Annotation array in TypeDescriptor (backport)
Closes gh-32405
2024-03-16 14:23:06 +01:00
Juergen Hoeller 19b21b15c1 Polishing 2024-03-15 21:22:49 +01:00
Juergen Hoeller 547425275c Consistently apply TaskDecorator to ManagedExecutorService as well
Closes gh-32455
2024-03-15 21:17:46 +01:00
Stéphane Nicoll 658194c155 Harmonize Concourse configuration 2024-03-15 09:15:24 +01:00
Stéphane Nicoll 769f73ebf1 Polish "Move CI to GitHub Actions"
See gh-32447
2024-03-15 09:14:22 +01:00
Stéphane Nicoll 80809bbc14 Move CI to GitHub Actions
Closes gh-32447
2024-03-15 09:00:28 +01:00
Spring Builds 6d323d710a Next development version (v6.0.19-SNAPSHOT) 2024-03-14 09:30:16 +00:00
rstoyanchev f2fd2f1226 Extract reusable checkSchemeAndPort method
Closes gh-32440
2024-03-14 08:48:42 +00:00
Juergen Hoeller 072ebb6ffc Additional unit tests for operations on empty UriTemplate
See gh-32432

(cherry picked from commit 54a6d89da7)
2024-03-13 18:18:52 +01:00
Kasper Bisgaard 08e7f7efa4 Allow UriTemplate to be built with an empty template
Closes gh-32437
2024-03-13 17:32:48 +01:00
Juergen Hoeller b1fafbf7e1 Upgrade to Reactor 2022.0.17
Includes Groovy 4.0.19, OpenPDF 1.3.42, Mockito 5.11

Closes gh-32421
2024-03-12 20:26:44 +01:00
Juergen Hoeller a3647a8c5e Polishing
(cherry picked from commit 723c94e5ac)
2024-03-12 20:26:24 +01:00
ZeroCyan 5c8d9cd0b2 Fix order of sections in Validation chapter of reference manual
Closes gh-32408
2024-03-10 17:31:30 +01:00
Juergen Hoeller 40d5196243 Polishing 2024-03-08 19:40:45 +01:00
Brian Clozel ec2c9b5d0e Set error on observation in WebClient instrumentation
Prior to this commit, error signals flowing from the client response
publisher in `WebClient` would be set on the `Observation.Context`. This
is enough for the observation convention to collect data about the error
but observation handlers are not notified of this error.

This commit sets the error instead on the observation directly to fix
this issue.

Fixes gh-32399
2024-03-08 11:36:20 +01:00
rstoyanchev 36539bdaa9 Use wrapped response in HandlerFunctionAdapter
webmvc.fn now also uses the StandardServletAsyncWebRequest wrapped response
to enforce lifecycle rules from Servlet spec (section 2.3.3.4).

See gh-32341
2024-03-07 14:51:01 +00:00
rstoyanchev 67ba7dd1da DisconnectedClientHelper recognizes AsyncRequestNotUsableException
See gh-32341
2024-03-06 18:20:31 +00:00
rstoyanchev 7b3fcf2647 Fix Javadoc error 2024-03-05 13:06:12 +00:00
rstoyanchev fd76c33589 Fix Javadoc error 2024-03-05 12:40:32 +00:00
rstoyanchev 1a7a6f421f Backport tests for wrapping of response for async requests
This is a backport of commits 4b96cd and ef0717.

Closes gh-32341
2024-03-05 12:03:48 +00:00
Juergen Hoeller 9ac1feceb5 Restore ability to return original method for proxy-derived method
Closes gh-32365
2024-03-04 23:32:27 +01:00
Juergen Hoeller 7029042e44 Polishing
(cherry picked from commit e9110c0729)
2024-03-04 23:31:51 +01:00
rstoyanchev 1a5661d426 Improve concurrent handling of result in WebAsyncManager
1. Use state transitions
2. Increase synchronized scope in setConcurrentResultAndDispatch

See gh-32341
2024-03-03 20:32:00 +00:00
rstoyanchev b208c63414 Add state and response wrapping to StandardServletAsyncWebRequest
The wrapped response prevents use after AsyncListener onError or completion
to ensure compliance with Servlet Spec 2.3.3.4.

The wrapped response is applied in RequestMappingHandlerAdapter.

The wrapped response raises AsyncRequestNotUsableException that is now
handled in DefaultHandlerExceptionResolver.

See gh-32341
2024-03-03 20:31:45 +00:00
rstoyanchev 814c003b43 Align 5.3.x with 6.1.x
In preparation for a larger update, start by aligning with
6.1.x, which includes changes for gh-32042 and gh-30232.

See gh-32341
2024-02-29 17:46:18 +00:00
Juergen Hoeller 5c34e1d11a Upgrade to Undertow 2.3.12, OpenPDF 1.3.41, JRuby 9.4.6 2024-02-28 21:38:57 +01:00
Juergen Hoeller d57775bbb2 Polishing 2024-02-28 21:38:38 +01:00
Juergen Hoeller b598ad3f33 Polishing 2024-02-28 19:26:11 +01:00
Juergen Hoeller b44ef70bc3 Direct reference to PushBuilder API on Servlet 5.0 baseline
See gh-29435
2024-02-28 19:17:13 +01:00
Sam Brannen d1b3107398 Do not cache Content-Type in ContentCachingResponseWrapper
Based on feedback from several members of the community, we have
decided to revert the caching of the Content-Type header that was
introduced in ContentCachingResponseWrapper in 375e0e6827.

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

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

See gh-32039
See gh-32317
Closes gh-32321
2024-02-28 10:51:48 +01:00
Sam Brannen 629c560316 Polish ShallowEtagHeaderFilterTests 2024-02-28 10:49:13 +01:00
Sébastien Deleuze 7bf07ef393 Refine *HttpMessageConverter#getContentLength null safety
Closes gh-32333
2024-02-27 15:48:36 +01:00
Sam Brannen ca602ef874 Honor Content-[Type|Length] headers from wrapped response again
Commit 375e0e6827 introduced a regression in
ContentCachingResponseWrapper (CCRW). Specifically, CCRW no longer
honors Content-Type and Content-Length headers that have been set in
the wrapped response and now incorrectly returns null for those header
values if they have not been set directly in the CCRW.

This commit fixes this regression as follows.

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

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

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

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

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

See gh-32039
See gh-32317
Closes gh-32321
2024-02-25 17:35:54 +01:00
Sam Brannen e9bf5f5569 Polish ContentCachingResponseWrapper[Tests] 2024-02-25 17:35:54 +01:00
Juergen Hoeller 9a6f636e17 Consistent nullability for internal field access 2024-02-24 08:31:56 +01:00
Sébastien Deleuze 7686f5467e Adapt Hibernate native support for HHH-17643
This commit adapts Hibernate native support to handle
the changes performed as part of HHH-17643 which impacts
Hibernate versions 6.4.3+ and 6.2.21+.

It ignores the BytecodeProvider services loaded by the
service loader feature in order to default to the
"no-op" provider with native.

gh-32314 is expected to remove the need for such
substitutions which are not great for maintainability
by design.

Closes gh-32312
2024-02-22 17:06:53 +01:00
Juergen Hoeller 429c477f6a Polishing 2024-02-22 16:59:53 +01:00
Juergen Hoeller 5187281b50 Polishing 2024-02-21 22:56:21 +01:00
Juergen Hoeller 24a4487050 Add test for cleanup after configuration class creation failure
See gh-23343
2024-02-21 22:47:23 +01:00
Juergen Hoeller 8fba4a448a Polishing 2024-02-16 22:41:50 +01:00
Spring Builds 8f286de773 Next development version (v6.0.18-SNAPSHOT) 2024-02-15 12:34:07 +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
483 changed files with 10004 additions and 5298 deletions
@@ -0,0 +1,20 @@
name: Await HTTP Resource
description: Waits for an HTTP resource to be available (a HEAD request succeeds)
inputs:
url:
description: 'The URL of the resource to await'
required: true
runs:
using: composite
steps:
- name: Await HTTP resource
shell: bash
run: |
url=${{ inputs.url }}
echo "Waiting for $url"
until curl --fail --head --silent ${{ inputs.url }} > /dev/null
do
echo "."
sleep 60
done
echo "$url is available"
+56
View File
@@ -0,0 +1,56 @@
name: 'Build'
description: 'Builds the project, optionally publishing it to a local deployment repository'
inputs:
java-version:
required: false
default: '17'
description: 'The Java version to compile and test with'
java-early-access:
required: false
default: 'false'
description: 'Whether the Java version is in early access'
java-toolchain:
required: false
default: 'false'
description: 'Whether a Java toolchain should be used'
publish:
required: false
default: 'false'
description: 'Whether to publish artifacts ready for deployment to Artifactory'
develocity-access-key:
required: false
description: 'The access key for authentication with ge.spring.io'
outputs:
build-scan-url:
description: 'The URL, if any, of the build scan produced by the build'
value: ${{ (inputs.publish == 'true' && steps.publish.outputs.build-scan-url) || steps.build.outputs.build-scan-url }}
version:
description: 'The version that was built'
value: ${{ steps.read-version.outputs.version }}
runs:
using: composite
steps:
- name: Prepare Gradle Build
uses: ./.github/actions/prepare-gradle-build
with:
develocity-access-key: ${{ inputs.develocity-access-key }}
java-version: ${{ inputs.java-version }}
java-early-access: ${{ inputs.java-early-access }}
java-toolchain: ${{ inputs.java-toolchain }}
- name: Build
id: build
if: ${{ inputs.publish == 'false' }}
shell: bash
run: ./gradlew check antora
- name: Publish
id: publish
if: ${{ inputs.publish == 'true' }}
shell: bash
run: ./gradlew -PdeploymentRepository=$(pwd)/deployment-repository build publishAllPublicationsToDeploymentRepository
- name: Read Version From gradle.properties
id: read-version
shell: bash
run: |
version=$(sed -n 's/version=\(.*\)/\1/p' gradle.properties)
echo "Version is $version"
echo "version=$version" >> $GITHUB_OUTPUT
@@ -0,0 +1,23 @@
name: Create GitHub Release
description: Create the release on GitHub with a changelog
inputs:
milestone:
description: Name of the GitHub milestone for which a release will be created
required: true
token:
description: Token to use for authentication with GitHub
required: true
runs:
using: composite
steps:
- name: Generate Changelog
uses: spring-io/github-changelog-generator@185319ad7eaa75b0e8e72e4b6db19c8b2cb8c4c1 #v0.0.11
with:
milestone: ${{ inputs.milestone }}
token: ${{ inputs.token }}
config-file: .github/actions/create-github-release/changelog-generator.yml
- name: Create GitHub Release
env:
GITHUB_TOKEN: ${{ inputs.token }}
shell: bash
run: gh release create ${{ format('v{0}', inputs.milestone) }} --notes-file changelog.md
@@ -17,4 +17,12 @@ changelog:
- "type: dependency-upgrade"
contributors:
exclude:
names: ["bclozel", "jhoeller", "poutsma", "rstoyanchev", "sbrannen", "sdeleuze", "snicoll", "simonbasle"]
names:
- "bclozel"
- "jhoeller"
- "poutsma"
- "rstoyanchev"
- "sbrannen"
- "sdeleuze"
- "simonbasle"
- "snicoll"
@@ -0,0 +1,49 @@
name: 'Prepare Gradle Build'
description: 'Prepares a Gradle build. Sets up Java and Gradle and configures Gradle properties'
inputs:
java-version:
required: false
default: '17'
description: 'The Java version to use for the build'
java-early-access:
required: false
default: 'false'
description: 'Whether the Java version is in early access'
java-toolchain:
required: false
default: 'false'
description: 'Whether a Java toolchain should be used'
develocity-access-key:
required: false
description: 'The access key for authentication with ge.spring.io'
runs:
using: composite
steps:
- name: Set Up Java
uses: actions/setup-java@v4
with:
distribution: ${{ inputs.java-early-access == 'true' && 'temurin' || 'liberica' }}
java-version: |
${{ inputs.java-early-access == 'true' && format('{0}-ea', inputs.java-version) || inputs.java-version }}
${{ inputs.java-toolchain == 'true' && '17' || '' }}
- name: Set Up Gradle
uses: gradle/actions/setup-gradle@d9c87d481d55275bb5441eef3fe0e46805f9ef70 # v3.5.0
with:
cache-read-only: false
develocity-access-key: ${{ inputs.develocity-access-key }}
- name: Configure Gradle Properties
shell: bash
run: |
mkdir -p $HOME/.gradle
echo 'systemProp.user.name=spring-builds+github' >> $HOME/.gradle/gradle.properties
echo 'systemProp.org.gradle.internal.launcher.welcomeMessageEnabled=false' >> $HOME/.gradle/gradle.properties
echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties
echo 'org.gradle.daemon=4' >> $HOME/.gradle/gradle.properties
- name: Configure Toolchain Properties
if: ${{ inputs.java-toolchain == 'true' }}
shell: bash
run: |
echo toolchainVersion=${{ inputs.java-version }} >> $HOME/.gradle/gradle.properties
echo systemProp.org.gradle.java.installations.auto-detect=false >> $HOME/.gradle/gradle.properties
echo systemProp.org.gradle.java.installations.auto-download=false >> $HOME/.gradle/gradle.properties
echo systemProp.org.gradle.java.installations.paths=${{ format('$JAVA_HOME_{0}_X64', inputs.java-version) }} >> $HOME/.gradle/gradle.properties
@@ -0,0 +1,33 @@
name: Send Notification
description: Sends a Google Chat message as a notification of the job's outcome
inputs:
webhook-url:
description: 'Google Chat Webhook URL'
required: true
status:
description: 'Status of the job'
required: true
build-scan-url:
description: 'URL of the build scan to include in the notification'
run-name:
description: 'Name of the run to include in the notification'
default: ${{ format('{0} {1}', github.ref_name, github.job) }}
runs:
using: composite
steps:
- shell: bash
run: |
echo "BUILD_SCAN=${{ inputs.build-scan-url == '' && ' [build scan unavailable]' || format(' [<{0}|Build Scan>]', inputs.build-scan-url) }}" >> "$GITHUB_ENV"
echo "RUN_URL=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" >> "$GITHUB_ENV"
- shell: bash
if: ${{ inputs.status == 'success' }}
run: |
curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: "<${{ env.RUN_URL }}|${{ inputs.run-name }}> was successful ${{ env.BUILD_SCAN }}"}' || true
- shell: bash
if: ${{ inputs.status == 'failure' }}
run: |
curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: "<users/all> *<${{ env.RUN_URL }}|${{ inputs.run-name }}> failed* ${{ env.BUILD_SCAN }}"}' || true
- shell: bash
if: ${{ inputs.status == 'cancelled' }}
run: |
curl -X POST '${{ inputs.webhook-url }}' -H 'Content-Type: application/json' -d '{ text: "<${{ env.RUN_URL }}|${{ inputs.run-name }}> was cancelled"}' || true
@@ -0,0 +1,43 @@
name: Sync to Maven Central
description: Syncs a release to Maven Central and waits for it to be available for use
inputs:
jfrog-cli-config-token:
description: 'Config token for the JFrog CLI'
required: true
spring-framework-version:
description: 'The version of Spring Framework that is being synced to Central'
required: true
ossrh-s01-token-username:
description: 'Username for authentication with s01.oss.sonatype.org'
required: true
ossrh-s01-token-password:
description: 'Password for authentication with s01.oss.sonatype.org'
required: true
ossrh-s01-staging-profile:
description: 'Staging profile to use when syncing to Central'
required: true
runs:
using: composite
steps:
- name: Set Up JFrog CLI
uses: jfrog/setup-jfrog-cli@105617d23456a69a92485207c4f28ae12297581d # v4.2.1
env:
JF_ENV_SPRING: ${{ inputs.jfrog-cli-config-token }}
- name: Download Release Artifacts
shell: bash
run: jf rt download --spec ${{ format('{0}/artifacts.spec', github.action_path) }} --spec-vars 'buildName=${{ format('spring-framework-{0}', inputs.spring-framework-version) }};buildNumber=${{ github.run_number }}'
- name: Sync
uses: spring-io/nexus-sync-action@42477a2230a2f694f9eaa4643fa9e76b99b7ab84 # v0.0.1
with:
username: ${{ inputs.ossrh-s01-token-username }}
password: ${{ inputs.ossrh-s01-token-password }}
staging-profile-name: ${{ inputs.ossrh-s01-staging-profile }}
create: true
upload: true
close: true
release: true
generate-checksums: true
- name: Await
uses: ./.github/actions/await-http-resource
with:
url: ${{ format('https://repo.maven.apache.org/maven2/org/springframework/spring-context/{0}/spring-context-{0}.jar', inputs.spring-framework-version) }}
@@ -0,0 +1,20 @@
{
"files": [
{
"aql": {
"items.find": {
"$and": [
{
"@build.name": "${buildName}",
"@build.number": "${buildNumber}",
"path": {
"$nmatch": "org/springframework/framework-docs/*"
}
}
]
}
},
"target": "nexus/"
}
]
}
+6 -4
View File
@@ -18,11 +18,13 @@ jobs:
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
- name: Check out code
uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
distribution: 'liberica'
java-version: 17
- name: Download BackportBot
run: wget https://github.com/spring-io/backport-bot/releases/download/latest/backport-bot-0.0.1-SNAPSHOT.jar
- name: Backport
@@ -0,0 +1,59 @@
name: Build and Deploy Snapshot
on:
push:
branches:
- 6.0.x
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
build-and-deploy-snapshot:
name: Build and Deploy Snapshot
runs-on: ubuntu-latest
timeout-minutes: 60
if: ${{ github.repository == 'spring-projects/spring-framework' }}
steps:
- name: Check Out Code
uses: actions/checkout@v4
- name: Build and Publish
id: build-and-publish
uses: ./.github/actions/build
with:
develocity-access-key: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
publish: true
- name: Deploy
uses: spring-io/artifactory-deploy-action@26bbe925a75f4f863e1e529e85be2d0093cac116 # v0.0.1
with:
uri: 'https://repo.spring.io'
username: ${{ secrets.ARTIFACTORY_USERNAME }}
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
build-name: 'spring-framework-6.0.x'
repository: 'libs-snapshot-local'
folder: 'deployment-repository'
signing-key: ${{ secrets.GPG_PRIVATE_KEY }}
signing-passphrase: ${{ secrets.GPG_PASSPHRASE }}
artifact-properties: |
/**/framework-docs-*.zip::zip.name=spring-framework,zip.deployed=false
/**/framework-docs-*-docs.zip::zip.type=docs
/**/framework-docs-*-dist.zip::zip.type=dist
/**/framework-docs-*-schema.zip::zip.type=schema
- name: Send Notification
uses: ./.github/actions/send-notification
if: always()
with:
webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
status: ${{ job.status }}
build-scan-url: ${{ steps.build-and-publish.outputs.build-scan-url }}
run-name: ${{ format('{0} | Linux | Java 17', github.ref_name) }}
outputs:
version: ${{ steps.build-and-publish.outputs.version }}
verify:
name: Verify
needs: build-and-deploy-snapshot
uses: ./.github/workflows/verify.yml
secrets:
google-chat-webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
repository-password: ${{ secrets.ARTIFACTORY_PASSWORD }}
repository-username: ${{ secrets.ARTIFACTORY_USERNAME }}
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
with:
version: ${{ needs.build-and-deploy-snapshot.outputs.version }}
+53
View File
@@ -0,0 +1,53 @@
name: CI
on:
push:
branches:
- 6.0.x
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
ci:
name: '${{ matrix.os.name}} | Java ${{ matrix.java.version}}'
runs-on: ${{ matrix.os.id }}
timeout-minutes: 60
if: ${{ github.repository == 'spring-projects/spring-framework' }}
strategy:
matrix:
os:
- id: ubuntu-latest
name: Linux
java:
- version: 17
toolchain: false
- version: 21
toolchain: true
exclude:
- os:
name: Linux
java:
version: 17
steps:
- name: Prepare Windows runner
if: ${{ runner.os == 'Windows' }}
run: |
git config --global core.autocrlf true
git config --global core.longPaths true
Stop-Service -name Docker
- name: Check Out Code
uses: actions/checkout@v4
- name: Build
id: build
uses: ./.github/actions/build
with:
java-version: ${{ matrix.java.version }}
java-early-access: ${{ matrix.java.early-access || 'false' }}
java-toolchain: ${{ matrix.java.toolchain }}
develocity-access-key: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
- name: Send Notification
uses: ./.github/actions/send-notification
if: always()
with:
webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
status: ${{ job.status }}
build-scan-url: ${{ steps.build.outputs.build-scan-url }}
run-name: ${{ format('{0} | {1} | Java {2}', github.ref_name, matrix.os.name, matrix.java.version) }}
+8 -6
View File
@@ -1,12 +1,14 @@
name: Deploy Docs
on:
push:
branches-ignore: [ gh-pages ]
tags: '**'
branches:
- 'main'
- '*.x'
- '!gh-pages'
tags:
- 'v*'
repository_dispatch:
types: request-build-reference # legacy
schedule:
- cron: '0 10 * * *' # Once per day at 10am UTC
workflow_dispatch:
permissions:
actions: write
@@ -15,8 +17,8 @@ jobs:
runs-on: ubuntu-latest
if: github.repository_owner == 'spring-projects'
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Check out code
uses: actions/checkout@v4
with:
ref: docs-build
fetch-depth: 1
@@ -1,13 +0,0 @@
name: "Validate Gradle Wrapper"
on: [push, pull_request]
permissions:
contents: read
jobs:
validation:
name: "Validation"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: gradle/wrapper-validation-action@v1
+95
View File
@@ -0,0 +1,95 @@
name: Release
on:
push:
tags:
- v6.0.[0-9]+
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
build-and-stage-release:
if: ${{ github.repository == 'spring-projects/spring-framework' }}
name: Build and Stage Release
runs-on: ubuntu-latest
steps:
- name: Check Out Code
uses: actions/checkout@v4
- name: Build and Publish
id: build-and-publish
uses: ./.github/actions/build
with:
develocity-access-key: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
publish: true
- name: Stage Release
uses: spring-io/artifactory-deploy-action@26bbe925a75f4f863e1e529e85be2d0093cac116 # v0.0.1
with:
uri: 'https://repo.spring.io'
username: ${{ secrets.ARTIFACTORY_USERNAME }}
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
build-name: ${{ format('spring-framework-{0}', steps.build-and-publish.outputs.version)}}
repository: 'libs-staging-local'
folder: 'deployment-repository'
signing-key: ${{ secrets.GPG_PRIVATE_KEY }}
signing-passphrase: ${{ secrets.GPG_PASSPHRASE }}
artifact-properties: |
/**/framework-docs-*.zip::zip.name=spring-framework,zip.deployed=false
/**/framework-docs-*-docs.zip::zip.type=docs
/**/framework-docs-*-dist.zip::zip.type=dist
/**/framework-docs-*-schema.zip::zip.type=schema
outputs:
version: ${{ steps.build-and-publish.outputs.version }}
verify:
name: Verify
needs: build-and-stage-release
uses: ./.github/workflows/verify.yml
with:
staging: true
version: ${{ needs.build-and-stage-release.outputs.version }}
secrets:
google-chat-webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
repository-password: ${{ secrets.ARTIFACTORY_PASSWORD }}
repository-username: ${{ secrets.ARTIFACTORY_USERNAME }}
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
sync-to-maven-central:
name: Sync to Maven Central
needs:
- build-and-stage-release
- verify
runs-on: ubuntu-latest
steps:
- name: Check Out Code
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Sync to Maven Central
uses: ./.github/actions/sync-to-maven-central
with:
jfrog-cli-config-token: ${{ secrets.JF_ARTIFACTORY_SPRING }}
ossrh-s01-staging-profile: ${{ secrets.OSSRH_S01_STAGING_PROFILE }}
ossrh-s01-token-password: ${{ secrets.OSSRH_S01_TOKEN_PASSWORD }}
ossrh-s01-token-username: ${{ secrets.OSSRH_S01_TOKEN_USERNAME }}
spring-framework-version: ${{ needs.build-and-stage-release.outputs.version }}
promote-release:
name: Promote Release
needs:
- build-and-stage-release
- sync-to-maven-central
runs-on: ubuntu-latest
steps:
- name: Set up JFrog CLI
uses: jfrog/setup-jfrog-cli@105617d23456a69a92485207c4f28ae12297581d # v4.2.1
env:
JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }}
- name: Promote build
run: jfrog rt build-promote ${{ format('spring-framework-{0}', needs.build-and-stage-release.outputs.version)}} ${{ github.run_number }} libs-release-local
create-github-release:
name: Create GitHub Release
needs:
- build-and-stage-release
- promote-release
runs-on: ubuntu-latest
steps:
- name: Check Out Code
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Create GitHub Release
uses: ./.github/actions/create-github-release
with:
milestone: ${{ needs.build-and-stage-release.outputs.version }}
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
@@ -0,0 +1,11 @@
name: "Validate Gradle Wrapper"
on: [push, pull_request]
permissions:
contents: read
jobs:
validation:
name: "Validate Gradle Wrapper"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: gradle/actions/wrapper-validation@d9c87d481d55275bb5441eef3fe0e46805f9ef70 # v3.5.0
+71
View File
@@ -0,0 +1,71 @@
name: Verify
on:
workflow_call:
inputs:
version:
required: true
type: string
staging:
required: false
default: false
type: boolean
secrets:
repository-username:
required: false
repository-password:
required: false
google-chat-webhook-url:
required: true
token:
required: true
jobs:
verify:
name: Verify
runs-on: ubuntu-latest
steps:
- name: Check Out Release Verification Tests
uses: actions/checkout@v4
with:
repository: spring-projects/spring-framework-release-verification
ref: 'v0.0.2'
token: ${{ secrets.token }}
- name: Check Out Send Notification Action
uses: actions/checkout@v4
with:
path: spring-framework
sparse-checkout: .github/actions/send-notification
- name: Set Up Java
uses: actions/setup-java@v4
with:
distribution: 'liberica'
java-version: 17
- name: Set Up Gradle
uses: gradle/actions/setup-gradle@d9c87d481d55275bb5441eef3fe0e46805f9ef70 # v3.5.0
with:
cache-read-only: false
- name: Configure Gradle Properties
shell: bash
run: |
mkdir -p $HOME/.gradle
echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties
- name: Run Release Verification Tests
env:
RVT_VERSION: ${{ inputs.version }}
RVT_RELEASE_TYPE: oss
RVT_STAGING: ${{ inputs.staging }}
RVT_OSS_REPOSITORY_USERNAME: ${{ secrets.repository-username }}
RVT_OSS_REPOSITORY_PASSWORD: ${{ secrets.repository-password }}
run: ./gradlew spring-framework-release-verification-tests:test
- name: Upload Build Reports on Failure
uses: actions/upload-artifact@v4
if: failure()
with:
name: build-reports
path: '**/build/reports/'
- name: Send Notification
uses: ./spring-framework/.github/actions/send-notification
if: failure()
with:
webhook-url: ${{ secrets.google-chat-webhook-url }}
status: ${{ job.status }}
run-name: ${{ format('{0} | Verification | {1}', github.ref_name, inputs.version) }}
+2
View File
@@ -51,3 +51,5 @@ atlassian-ide-plugin.xml
.vscode/
cached-antora-playbook.yml
node_modules
-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>
+1 -1
View File
@@ -1,3 +1,3 @@
# Enable auto-env through the sdkman_auto_env config
# Add key=value pairs of SDKs to use below
java=17.0.8.1-librca
java=17.0.11-librca
+1 -1
View File
@@ -1,4 +1,4 @@
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [![Build Status](https://ci.spring.io/api/v1/teams/spring-framework/pipelines/spring-framework-6.0.x/jobs/build/badge)](https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-6.0.x?groups=Build") [![Revved up by Gradle Enterprise](https://img.shields.io/badge/Revved%20up%20by-Gradle%20Enterprise-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.spring.io/scans?search.rootProjectNames=spring)
# <img src="framework-docs/src/docs/spring-framework.png" width="80" height="80"> Spring Framework [![Build Status](https://github.com/spring-projects/spring-framework/actions/workflows/build-and-deploy-snapshot.yml/badge.svg?branch=6.0.x)](https://github.com/spring-projects/spring-framework/actions/workflows/build-and-deploy-snapshot.yml?query=branch%3A6.0.x) [![Revved up by Develocity](https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.spring.io/scans?search.rootProjectNames=spring)
This is the home of the Spring Framework: the foundation for all [Spring projects](https://spring.io/projects). Collectively the Spring Framework and the family of Spring projects are often referred to simply as "Spring".
+6 -10
View File
@@ -3,12 +3,12 @@ plugins {
id 'io.freefair.aspectj' version '8.0.1' apply false
// kotlinVersion is managed in gradle.properties
id 'org.jetbrains.kotlin.plugin.serialization' version "${kotlinVersion}" apply false
id 'org.jetbrains.dokka' version '1.8.10'
id 'org.jetbrains.dokka' version '1.8.20'
id 'org.unbroken-dome.xjc' version '2.0.0' apply false
id 'com.github.ben-manes.versions' version '0.49.0'
id 'com.github.ben-manes.versions' version '0.51.0'
id 'com.github.johnrengelman.shadow' version '8.1.1' apply false
id 'de.undercouch.download' version '5.4.0'
id 'me.champeau.jmh' version '0.7.1' apply false
id 'me.champeau.jmh' version '0.7.2' apply false
}
ext {
@@ -75,7 +75,7 @@ configure([rootProject] + javaProjects) { project ->
}
checkstyle {
toolVersion = "10.12.5"
toolVersion = "10.12.7"
configDirectory.set(rootProject.file("src/checkstyle"))
}
@@ -106,15 +106,12 @@ 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.42")
}
ext.javadocLinks = [
"https://docs.oracle.com/en/java/javase/17/docs/api/",
"https://jakarta.ee/specifications/platform/9/apidocs/",
"https://docs.oracle.com/cd/E13222_01/wls/docs90/javadocs/", // CommonJ and weblogic.* packages
"https://www.ibm.com/docs/api/v1/content/SSEQTP_8.5.5/com.ibm.websphere.javadoc.doc/web/apidocs/", // com.ibm.*
"https://docs.jboss.org/jbossas/javadoc/4.0.5/connector/", // org.jboss.resource.*
"https://docs.jboss.org/hibernate/orm/5.6/javadocs/",
"https://eclipse.dev/aspectj/doc/released/aspectj5rt-api",
"https://www.quartz-scheduler.org/api/2.3.0/",
@@ -146,7 +143,6 @@ configure(rootProject) {
description = "Spring Framework"
apply plugin: "io.spring.nohttp"
apply plugin: 'org.springframework.build.api-diff'
nohttp {
source.exclude "**/test-output/**"
@@ -155,7 +151,7 @@ configure(rootProject) {
def rootPath = file(rootDir).toPath()
def projectDirs = allprojects.collect { it.projectDir } + "${rootDir}/buildSrc"
projectDirs.forEach { dir ->
[ 'bin', 'build', 'out', '.settings' ]
[ 'bin', 'build', 'out', '.settings', 'node_modules' ]
.collect { rootPath.relativize(new File(dir, it).toPath()) }
.forEach { source.exclude "$it/**" }
[ '.classpath', '.project' ]
-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());
}
}
-57
View File
@@ -1,57 +0,0 @@
== Spring Framework Concourse pipeline
The Spring Framework uses https://concourse-ci.org/[Concourse] for its CI build and other automated tasks.
The Spring team has a dedicated Concourse instance available at https://ci.spring.io with a build pipeline
for https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-6.0.x[Spring Framework 6.0.x].
=== Setting up your development environment
If you're part of the Spring Framework project on GitHub, you can get access to CI management features.
First, you need to go to https://ci.spring.io and install the client CLI for your platform (see bottom right of the screen).
You can then login with the instance using:
[source]
----
$ fly -t spring login -n spring-framework -c https://ci.spring.io
----
Once logged in, you should get something like:
[source]
----
$ fly ts
name url team expiry
spring https://ci.spring.io spring-framework Wed, 25 Mar 2020 17:45:26 UTC
----
=== Pipeline configuration and structure
The build pipelines are described in `pipeline.yml` file.
This file is listing Concourse resources, i.e. build inputs and outputs such as container images, artifact repositories, source repositories, notification services, etc.
It also describes jobs (a job is a sequence of inputs, tasks and outputs); jobs are organized by groups.
The `pipeline.yml` definition contains `((parameters))` which are loaded from the `parameters.yml` file or from our https://docs.cloudfoundry.org/credhub/[credhub instance].
You'll find in this folder the following resources:
* `pipeline.yml` the build pipeline
* `parameters.yml` the build parameters used for the pipeline
* `images/` holds the container images definitions used in this pipeline
* `scripts/` holds the build scripts that ship within the CI container images
* `tasks` contains the task definitions used in the main `pipeline.yml`
=== Updating the build pipeline
Updating files on the repository is not enough to update the build pipeline, as changes need to be applied.
The pipeline can be deployed using the following command:
[source]
----
$ fly -t spring set-pipeline -p spring-framework-6.0.x -c ci/pipeline.yml -l ci/parameters.yml
----
NOTE: This assumes that you have credhub integration configured with the appropriate secrets.
-10
View File
@@ -1,10 +0,0 @@
logging:
level:
io.spring.concourse: DEBUG
spring:
main:
banner-mode: off
sonatype:
exclude:
- 'build-info\.json'
- '.*\.zip'
-21
View File
@@ -1,21 +0,0 @@
== CI Images
These images are used by CI to run the actual builds.
To build the image locally run the following from this directory:
----
$ docker build --no-cache -f <image-folder>/Dockerfile .
----
For example
----
$ docker build --no-cache -f spring-framework-ci-image/Dockerfile .
----
To test run:
----
$ docker run -it --entrypoint /bin/bash <SHA>
----
-11
View File
@@ -1,11 +0,0 @@
FROM ubuntu:jammy-20231004
ADD setup.sh /setup.sh
ADD get-jdk-url.sh /get-jdk-url.sh
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
-14
View File
@@ -1,14 +0,0 @@
#!/bin/bash
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 $"Unknown java version"
exit 1
esac
-40
View File
@@ -1,40 +0,0 @@
#!/bin/bash
set -ex
###########################################################
# UTILS
###########################################################
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install --no-install-recommends -y tzdata ca-certificates net-tools libxml2-utils git curl libudev1 libxml2-utils iptables iproute2 jq fontconfig
ln -fs /usr/share/zoneinfo/UTC /etc/localtime
dpkg-reconfigure --frontend noninteractive tzdata
rm -rf /var/lib/apt/lists/*
curl https://raw.githubusercontent.com/spring-io/concourse-java-scripts/v0.0.4/concourse-java.sh > /opt/concourse-java.sh
###########################################################
# JAVA
###########################################################
mkdir -p /opt/openjdk
pushd /opt/openjdk > /dev/null
for jdk in java17 java20
do
JDK_URL=$( /get-jdk-url.sh $jdk )
mkdir $jdk
pushd $jdk > /dev/null
curl -L ${JDK_URL} | tar zx --strip-components=1
test -f bin/java
test -f bin/javac
popd > /dev/null
done
popd
###########################################################
# GRADLE ENTERPRISE
###########################################################
cd /
mkdir ~/.gradle
echo 'systemProp.user.name=concourse' > ~/.gradle/gradle.properties
-11
View File
@@ -1,11 +0,0 @@
github-repo: "https://github.com/spring-projects/spring-framework.git"
github-repo-name: "spring-projects/spring-framework"
sonatype-staging-profile: "org.springframework"
docker-hub-organization: "springci"
artifactory-server: "https://repo.spring.io"
branch: "6.0.x"
milestone: "6.0.x"
build-name: "spring-framework"
pipeline-name: "spring-framework"
concourse-url: "https://ci.spring.io"
task-timeout: 1h00m
-403
View File
@@ -1,403 +0,0 @@
anchors:
git-repo-resource-source: &git-repo-resource-source
uri: ((github-repo))
username: ((github-username))
password: ((github-ci-release-token))
branch: ((branch))
gradle-enterprise-task-params: &gradle-enterprise-task-params
GRADLE_ENTERPRISE_ACCESS_KEY: ((gradle_enterprise_secret_access_key))
GRADLE_ENTERPRISE_CACHE_USERNAME: ((gradle_enterprise_cache_user.username))
GRADLE_ENTERPRISE_CACHE_PASSWORD: ((gradle_enterprise_cache_user.password))
sonatype-task-params: &sonatype-task-params
SONATYPE_USERNAME: ((sonatype-username))
SONATYPE_PASSWORD: ((sonatype-password))
SONATYPE_URL: ((sonatype-url))
SONATYPE_STAGING_PROFILE: ((sonatype-staging-profile))
artifactory-task-params: &artifactory-task-params
ARTIFACTORY_SERVER: ((artifactory-server))
ARTIFACTORY_USERNAME: ((artifactory-username))
ARTIFACTORY_PASSWORD: ((artifactory-password))
build-project-task-params: &build-project-task-params
BRANCH: ((branch))
<<: *gradle-enterprise-task-params
docker-resource-source: &docker-resource-source
username: ((docker-hub-username))
password: ((docker-hub-password))
slack-fail-params: &slack-fail-params
text: >
:concourse-failed: <https://ci.spring.io/teams/${BUILD_TEAM_NAME}/pipelines/${BUILD_PIPELINE_NAME}/jobs/${BUILD_JOB_NAME}/builds/${BUILD_NAME}|${BUILD_PIPELINE_NAME} ${BUILD_JOB_NAME} failed!>
[$TEXT_FILE_CONTENT]
text_file: git-repo/build/build-scan-uri.txt
silent: true
icon_emoji: ":concourse:"
username: concourse-ci
changelog-task-params: &changelog-task-params
name: generated-changelog/tag
tag: generated-changelog/tag
body: generated-changelog/changelog.md
github-task-params: &github-task-params
GITHUB_USERNAME: ((github-username))
GITHUB_TOKEN: ((github-ci-release-token))
resource_types:
- name: registry-image
type: registry-image
source:
<<: *docker-resource-source
repository: concourse/registry-image-resource
tag: 1.5.0
- name: artifactory-resource
type: registry-image
source:
<<: *docker-resource-source
repository: springio/artifactory-resource
tag: 0.0.18
- name: github-release
type: registry-image
source:
<<: *docker-resource-source
repository: concourse/github-release-resource
tag: 1.5.5
- name: github-status-resource
type: registry-image
source:
<<: *docker-resource-source
repository: dpb587/github-status-resource
tag: master
- name: slack-notification
type: registry-image
source:
<<: *docker-resource-source
repository: cfcommunity/slack-notification-resource
tag: latest
resources:
- name: git-repo
type: git
icon: github
source:
<<: *git-repo-resource-source
- name: ci-images-git-repo
type: git
icon: github
source:
uri: ((github-repo))
branch: ((branch))
paths: ["ci/images/*"]
- name: ci-image
type: registry-image
icon: docker
source:
<<: *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
source:
uri: ((artifactory-server))
username: ((artifactory-username))
password: ((artifactory-password))
build_name: ((build-name))
- name: repo-status-build
type: github-status-resource
icon: eye-check-outline
source:
repository: ((github-repo-name))
access_token: ((github-ci-status-token))
branch: ((branch))
context: build
- name: repo-status-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
source:
url: ((slack-webhook-url))
- name: github-pre-release
type: github-release
icon: briefcase-download-outline
source:
owner: spring-projects
repository: spring-framework
access_token: ((github-ci-release-token))
pre_release: true
release: false
- name: github-release
type: github-release
icon: briefcase-download
source:
owner: spring-projects
repository: spring-framework
access_token: ((github-ci-release-token))
pre_release: false
jobs:
- name: build-ci-images
plan:
- get: git-repo
- get: ci-images-git-repo
trigger: true
- task: build-ci-image
privileged: true
file: git-repo/ci/tasks/build-ci-image.yml
output_mapping:
image: ci-image
vars:
ci-image-name: ci-image
<<: *docker-resource-source
- put: ci-image
params:
image: ci-image/image.tar
- name: build
serial: true
public: true
plan:
- get: ci-image
- get: git-repo
trigger: true
- put: repo-status-build
params: { state: "pending", commit: "git-repo" }
- do:
- task: build-project
image: ci-image
file: git-repo/ci/tasks/build-project.yml
privileged: true
timeout: ((task-timeout))
params:
<<: *build-project-task-params
on_failure:
do:
- put: repo-status-build
params: { state: "failure", commit: "git-repo" }
- put: slack-alert
params:
<<: *slack-fail-params
- put: repo-status-build
params: { state: "success", commit: "git-repo" }
- put: artifactory-repo
params: &artifactory-params
signing_key: ((signing-key))
signing_passphrase: ((signing-passphrase))
repo: libs-snapshot-local
folder: distribution-repository
build_uri: "https://ci.spring.io/teams/${BUILD_TEAM_NAME}/pipelines/${BUILD_PIPELINE_NAME}/jobs/${BUILD_JOB_NAME}/builds/${BUILD_NAME}"
build_number: "${BUILD_PIPELINE_NAME}-${BUILD_JOB_NAME}-${BUILD_NAME}"
disable_checksum_uploads: true
threads: 8
artifact_set:
- include:
- "/**/framework-docs-*.zip"
properties:
"zip.name": "spring-framework"
"zip.displayname": "Spring Framework"
"zip.deployed": "false"
- include:
- "/**/framework-docs-*-docs.zip"
properties:
"zip.type": "docs"
- include:
- "/**/framework-docs-*-dist.zip"
properties:
"zip.type": "dist"
- include:
- "/**/framework-docs-*-schema.zip"
properties:
"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:
- get: ci-image
- get: git-repo
trigger: false
- task: stage
image: ci-image
file: git-repo/ci/tasks/stage-version.yml
params:
RELEASE_TYPE: M
<<: *gradle-enterprise-task-params
- put: artifactory-repo
params:
<<: *artifactory-params
repo: libs-staging-local
- put: git-repo
params:
repository: stage-git-repo
- name: promote-milestone
serial: true
plan:
- get: ci-image
- get: git-repo
trigger: false
- get: artifactory-repo
trigger: false
passed: [stage-milestone]
params:
download_artifacts: false
save_build_info: true
- task: promote
file: git-repo/ci/tasks/promote-version.yml
params:
RELEASE_TYPE: M
<<: *artifactory-task-params
- task: generate-changelog
file: git-repo/ci/tasks/generate-changelog.yml
params:
RELEASE_TYPE: M
<<: *github-task-params
<<: *docker-resource-source
- put: github-pre-release
params:
<<: *changelog-task-params
- name: stage-rc
serial: true
plan:
- get: ci-image
- get: git-repo
trigger: false
- task: stage
image: ci-image
file: git-repo/ci/tasks/stage-version.yml
params:
RELEASE_TYPE: RC
<<: *gradle-enterprise-task-params
- put: artifactory-repo
params:
<<: *artifactory-params
repo: libs-staging-local
- put: git-repo
params:
repository: stage-git-repo
- name: promote-rc
serial: true
plan:
- get: ci-image
- get: git-repo
trigger: false
- get: artifactory-repo
trigger: false
passed: [stage-rc]
params:
download_artifacts: false
save_build_info: true
- task: promote
file: git-repo/ci/tasks/promote-version.yml
params:
RELEASE_TYPE: RC
<<: *docker-resource-source
<<: *artifactory-task-params
- task: generate-changelog
file: git-repo/ci/tasks/generate-changelog.yml
params:
RELEASE_TYPE: RC
<<: *github-task-params
- put: github-pre-release
params:
<<: *changelog-task-params
- name: stage-release
serial: true
plan:
- get: ci-image
- get: git-repo
trigger: false
- task: stage
image: ci-image
file: git-repo/ci/tasks/stage-version.yml
params:
RELEASE_TYPE: RELEASE
<<: *gradle-enterprise-task-params
- put: artifactory-repo
params:
<<: *artifactory-params
repo: libs-staging-local
- put: git-repo
params:
repository: stage-git-repo
- name: promote-release
serial: true
plan:
- get: ci-image
- get: git-repo
trigger: false
- get: artifactory-repo
trigger: false
passed: [stage-release]
params:
download_artifacts: true
save_build_info: true
- task: promote
file: git-repo/ci/tasks/promote-version.yml
params:
RELEASE_TYPE: RELEASE
<<: *docker-resource-source
<<: *artifactory-task-params
<<: *sonatype-task-params
- name: create-github-release
serial: true
plan:
- get: ci-image
- get: git-repo
- get: artifactory-repo
trigger: true
passed: [promote-release]
params:
download_artifacts: false
save_build_info: true
- task: generate-changelog
file: git-repo/ci/tasks/generate-changelog.yml
params:
RELEASE_TYPE: RELEASE
<<: *docker-resource-source
<<: *github-task-params
- put: github-release
params:
<<: *changelog-task-params
groups:
- name: "builds"
jobs: ["build", "jdk20-build"]
- name: "releases"
jobs: ["stage-milestone", "stage-rc", "stage-release", "promote-milestone", "promote-rc", "promote-release", "create-github-release"]
- name: "ci-images"
jobs: ["build-ci-images"]
-8
View File
@@ -1,8 +0,0 @@
#!/bin/bash
set -e
source $(dirname $0)/common.sh
pushd git-repo > /dev/null
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false --no-daemon --max-workers=4 check
popd > /dev/null
-9
View File
@@ -1,9 +0,0 @@
#!/bin/bash
set -e
source $(dirname $0)/common.sh
repository=$(pwd)/distribution-repository
pushd git-repo > /dev/null
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false --no-daemon --max-workers=4 -PdeploymentRepository=${repository} build publishAllPublicationsToDeploymentRepository
popd > /dev/null
-9
View File
@@ -1,9 +0,0 @@
#!/bin/bash
set -e
source $(dirname $0)/common.sh
pushd git-repo > /dev/null
./gradlew -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Porg.gradle.java.installations.fromEnv=JDK17,JDK20 \
-PmainToolchain=${MAIN_TOOLCHAIN} -PtestToolchain=${TEST_TOOLCHAIN} --no-daemon --max-workers=4 check antora
popd > /dev/null
-2
View File
@@ -1,2 +0,0 @@
source /opt/concourse-java.sh
setup_symlinks
-12
View File
@@ -1,12 +0,0 @@
#!/bin/bash
set -e
CONFIG_DIR=git-repo/ci/config
version=$( cat artifactory-repo/build-info.json | jq -r '.buildInfo.modules[0].id' | sed 's/.*:.*:\(.*\)/\1/' )
java -jar /github-changelog-generator.jar \
--spring.config.location=${CONFIG_DIR}/changelog-generator.yml \
${version} generated-changelog/changelog.md
echo ${version} > generated-changelog/version
echo v${version} > generated-changelog/tag
-17
View File
@@ -1,17 +0,0 @@
#!/bin/bash
CONFIG_DIR=git-repo/ci/config
version=$( cat artifactory-repo/build-info.json | jq -r '.buildInfo.modules[0].id' | sed 's/.*:.*:\(.*\)/\1/' )
export BUILD_INFO_LOCATION=$(pwd)/artifactory-repo/build-info.json
java -jar /concourse-release-scripts.jar \
--spring.config.location=${CONFIG_DIR}/release-scripts.yml \
publishToCentral $RELEASE_TYPE $BUILD_INFO_LOCATION artifactory-repo || { exit 1; }
java -jar /concourse-release-scripts.jar \
--spring.config.location=${CONFIG_DIR}/release-scripts.yml \
promote $RELEASE_TYPE $BUILD_INFO_LOCATION || { exit 1; }
echo "Promotion complete"
echo $version > version/version
-50
View File
@@ -1,50 +0,0 @@
#!/bin/bash
set -e
source $(dirname $0)/common.sh
repository=$(pwd)/distribution-repository
pushd git-repo > /dev/null
git fetch --tags --all > /dev/null
popd > /dev/null
git clone git-repo stage-git-repo > /dev/null
pushd stage-git-repo > /dev/null
snapshotVersion=$( awk -F '=' '$1 == "version" { print $2 }' gradle.properties )
if [[ $RELEASE_TYPE = "M" ]]; then
stageVersion=$( get_next_milestone_release $snapshotVersion)
nextVersion=$snapshotVersion
elif [[ $RELEASE_TYPE = "RC" ]]; then
stageVersion=$( get_next_rc_release $snapshotVersion)
nextVersion=$snapshotVersion
elif [[ $RELEASE_TYPE = "RELEASE" ]]; then
stageVersion=$( get_next_release $snapshotVersion)
nextVersion=$( bump_version_number $snapshotVersion)
else
echo "Unknown release type $RELEASE_TYPE" >&2; exit 1;
fi
echo "Staging $stageVersion (next version will be $nextVersion)"
sed -i "s/version=$snapshotVersion/version=$stageVersion/" gradle.properties
git config user.name "Spring Builds" > /dev/null
git config user.email "spring-builds@users.noreply.github.com" > /dev/null
git add gradle.properties > /dev/null
git commit -m"Release v$stageVersion" > /dev/null
git tag -a "v$stageVersion" -m"Release v$stageVersion" > /dev/null
./gradlew --no-daemon --max-workers=4 -PdeploymentRepository=${repository} build publishAllPublicationsToDeploymentRepository
git reset --hard HEAD^ > /dev/null
if [[ $nextVersion != $snapshotVersion ]]; then
echo "Setting next development version (v$nextVersion)"
sed -i "s/version=$snapshotVersion/version=$nextVersion/" gradle.properties
git add gradle.properties > /dev/null
git commit -m"Next development version (v$nextVersion)" > /dev/null
fi;
echo "Staging Complete"
popd > /dev/null
-30
View File
@@ -1,30 +0,0 @@
---
platform: linux
image_resource:
type: registry-image
source:
repository: concourse/oci-build-task
tag: 0.10.0
username: ((docker-hub-username))
password: ((docker-hub-password))
inputs:
- name: ci-images-git-repo
outputs:
- name: image
caches:
- path: ci-image-cache
params:
CONTEXT: ci-images-git-repo/ci/images
DOCKERFILE: ci-images-git-repo/ci/images/ci-image/Dockerfile
DOCKER_HUB_AUTH: ((docker-hub-auth))
run:
path: /bin/sh
args:
- "-c"
- |
mkdir -p /root/.docker
cat > /root/.docker/config.json <<EOF
{ "auths": { "https://index.docker.io/v1/": { "auth": "$DOCKER_HUB_AUTH" }}}
EOF
build
-19
View File
@@ -1,19 +0,0 @@
---
platform: linux
inputs:
- name: git-repo
caches:
- path: gradle
params:
BRANCH:
CI: true
GRADLE_ENTERPRISE_ACCESS_KEY:
GRADLE_ENTERPRISE_CACHE_USERNAME:
GRADLE_ENTERPRISE_CACHE_PASSWORD:
GRADLE_ENTERPRISE_URL: https://ge.spring.io
run:
path: bash
args:
- -ec
- |
${PWD}/git-repo/ci/scripts/build-pr.sh
-22
View File
@@ -1,22 +0,0 @@
---
platform: linux
inputs:
- name: git-repo
outputs:
- name: distribution-repository
- name: git-repo
caches:
- path: gradle
params:
BRANCH:
CI: true
GRADLE_ENTERPRISE_ACCESS_KEY:
GRADLE_ENTERPRISE_CACHE_USERNAME:
GRADLE_ENTERPRISE_CACHE_PASSWORD:
GRADLE_ENTERPRISE_URL: https://ge.spring.io
run:
path: bash
args:
- -ec
- |
${PWD}/git-repo/ci/scripts/build-project.sh
-24
View File
@@ -1,24 +0,0 @@
---
platform: linux
inputs:
- name: git-repo
outputs:
- name: distribution-repository
- name: git-repo
caches:
- path: gradle
params:
BRANCH:
CI: true
MAIN_TOOLCHAIN:
TEST_TOOLCHAIN:
GRADLE_ENTERPRISE_ACCESS_KEY:
GRADLE_ENTERPRISE_CACHE_USERNAME:
GRADLE_ENTERPRISE_CACHE_PASSWORD:
GRADLE_ENTERPRISE_URL: https://ge.spring.io
run:
path: bash
args:
- -ec
- |
${PWD}/git-repo/ci/scripts/check-project.sh
-22
View File
@@ -1,22 +0,0 @@
---
platform: linux
image_resource:
type: registry-image
source:
repository: springio/github-changelog-generator
tag: '0.0.8'
username: ((docker-hub-username))
password: ((docker-hub-password))
inputs:
- name: git-repo
- name: artifactory-repo
outputs:
- name: generated-changelog
params:
GITHUB_ORGANIZATION:
GITHUB_REPO:
GITHUB_USERNAME:
GITHUB_TOKEN:
RELEASE_TYPE:
run:
path: git-repo/ci/scripts/generate-changelog.sh
-25
View File
@@ -1,25 +0,0 @@
---
platform: linux
image_resource:
type: registry-image
source:
repository: springio/concourse-release-scripts
tag: '0.4.0-SNAPSHOT'
username: ((docker-hub-username))
password: ((docker-hub-password))
inputs:
- name: git-repo
- name: artifactory-repo
outputs:
- name: version
params:
RELEASE_TYPE:
ARTIFACTORY_SERVER:
ARTIFACTORY_USERNAME:
ARTIFACTORY_PASSWORD:
SONATYPE_USER:
SONATYPE_PASSWORD:
SONATYPE_URL:
SONATYPE_STAGING_PROFILE:
run:
path: git-repo/ci/scripts/promote-version.sh
-17
View File
@@ -1,17 +0,0 @@
---
platform: linux
inputs:
- name: git-repo
outputs:
- name: stage-git-repo
- name: distribution-repository
params:
RELEASE_TYPE:
CI: true
GRADLE_ENTERPRISE_CACHE_USERNAME:
GRADLE_ENTERPRISE_CACHE_PASSWORD:
GRADLE_ENTERPRISE_URL: https://ge.spring.io
caches:
- path: gradle
run:
path: git-repo/ci/scripts/stage-version.sh
+39
View File
@@ -0,0 +1,39 @@
antora:
extensions:
- require: '@springio/antora-extensions'
root_component_name: 'framework'
site:
title: Spring Framework
url: https://docs.spring.io/spring-framework/reference
robots: allow
git:
ensure_git_suffix: false
content:
sources:
- url: https://github.com/spring-projects/spring-framework
# Refname matching:
# https://docs.antora.org/antora/latest/playbook/content-refname-matching/
branches: ['main', '{6..9}.+({0..9}).x']
tags: ['v{6..9}.+({0..9}).+({0..9})?(-{RC,M}*)', '!(v6.0.{0..8})', '!(v6.0.0-{RC,M}{0..9})']
start_path: framework-docs
asciidoc:
extensions:
- '@asciidoctor/tabs'
- '@springio/asciidoctor-extensions'
- '@springio/asciidoctor-extensions/include-code-extension'
attributes:
page-stackoverflow-url: https://stackoverflow.com/questions/tagged/spring
page-pagination: ''
hide-uri-scheme: '@'
tabs-sync-option: '@'
include-java: 'example$docs-src/main/java/org/springframework/docs'
urls:
latest_version_segment_strategy: redirect:to
latest_version_segment: ''
redirect_facility: httpd
runtime:
log:
failure_level: warn
ui:
bundle:
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.16/ui-bundle.zip
+3 -20
View File
@@ -10,27 +10,10 @@ apply from: "${rootDir}/gradle/publications.gradle"
antora {
version = '3.2.0-alpha.2'
playbook = 'cached-antora-playbook.yml'
playbookProvider {
repository = 'spring-projects/spring-framework'
branch = 'docs-build'
path = 'lib/antora/templates/per-branch-antora-playbook.yml'
checkLocalBranch = true
}
options = ['--clean', '--stacktrace']
options = [clean: true, fetch: !project.gradle.startParameter.offline, stacktrace: true]
environment = [
'ALGOLIA_API_KEY': '82c7ead946afbac3cf98c32446154691',
'ALGOLIA_APP_ID': '244V8V9FGG',
'ALGOLIA_INDEX_NAME': 'framework-docs'
]
dependencies = [
'@antora/atlas-extension': '1.0.0-alpha.1',
'@antora/collector-extension': '1.0.0-alpha.3',
'@asciidoctor/tabs': '1.0.0-beta.3',
'@opendevise/antora-release-line-extension': '1.0.0-alpha.2',
'@springio/antora-extensions': '1.3.0',
'@springio/asciidoctor-extensions': '1.0.0-alpha.9'
'BUILD_REFNAME': 'HEAD',
'BUILD_VERSION': project.version,
]
}
+2 -2
View File
@@ -39,8 +39,8 @@
** xref:core/resources.adoc[]
** xref:core/validation.adoc[]
*** xref:core/validation/validator.adoc[]
*** xref:core/validation/conversion.adoc[]
*** xref:core/validation/beans-beans.adoc[]
*** xref:core/validation/conversion.adoc[]
*** xref:core/validation/convert.adoc[]
*** xref:core/validation/format.adoc[]
*** xref:core/validation/format-configuring-formatting-globaldatetimeformat.adoc[]
@@ -431,4 +431,4 @@
** xref:languages/groovy.adoc[]
** xref:languages/dynamic.adoc[]
* xref:appendix.adoc[]
* https://github.com/spring-projects/spring-framework/wiki[Wiki]
* https://github.com/spring-projects/spring-framework/wiki[Wiki]
@@ -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
@@ -153,15 +153,16 @@ Letting qualifier values select against target bean names, within the type-match
candidates, does not require a `@Qualifier` annotation at the injection point.
If there is no other resolution indicator (such as a qualifier or a primary marker),
for a non-unique dependency situation, Spring matches the injection point name
(that is, the field name or parameter name) against the target bean names and chooses the
same-named candidate, if any.
(that is, the field name or parameter name) against the target bean names and chooses
the same-named candidate, if any (either by bean name or by associated alias).
This requires the `-parameters` Java compiler flag to be present. As a fallback,
version 6.0 still supports parameter names from debug symbols via `-debug` as well.
====
That said, if you intend to express annotation-driven injection by name, do not
primarily use `@Autowired`, even if it is capable of selecting by bean name among
type-matching candidates. Instead, use the JSR-250 `@Resource` annotation, which is
semantically defined to identify a specific target component by its unique name, with
the declared type being irrelevant for the matching process. `@Autowired` has rather
As an alternative for injection by name, consider the JSR-250 `@Resource` annotation
which is semantically defined to identify a specific target component by its unique name,
with the declared type being irrelevant for the matching process. `@Autowired` has rather
different semantics: After selecting candidate beans by type, the specified `String`
qualifier value is considered within those type-selected candidates only (for example,
matching an `account` qualifier against beans marked with the same qualifier label).
@@ -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].
@@ -416,8 +416,8 @@ Kotlin::
======
This approach shows that the factory bean itself can be managed and configured through
dependency injection (DI). See xref:core/beans/dependencies/factory-properties-detailed.adoc[Dependencies and Configuration in Detail]
.
dependency injection (DI).
See xref:core/beans/dependencies/factory-properties-detailed.adoc[Dependencies and Configuration in Detail].
NOTE: In Spring documentation, "factory bean" refers to a bean that is configured in the
Spring container and that creates objects through an
@@ -444,5 +444,3 @@ cases into account and returns the type of object that a `BeanFactory.getBean` c
going to return for the same bean name.
@@ -439,7 +439,7 @@ dataSource.url=jdbc:mysql:mydb
----
This example file can be used with a container definition that contains a bean called
`dataSource` that has `driver` and `url` properties.
`dataSource` that has `driverClassName` and `url` properties.
Compound property names are also supported, as long as every component of the path
except the final property being overridden is already non-null (presumably initialized
@@ -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]`.
@@ -211,19 +211,20 @@ the JDBC driver. If the count is not available, the JDBC driver returns a value
====
In such a scenario, with automatic setting of values on an underlying `PreparedStatement`,
the corresponding JDBC type for each value needs to be derived from the given Java type.
While this usually works well, there is a potential for issues (for example, with Map-contained
`null` values). Spring, by default, calls `ParameterMetaData.getParameterType` in such a
case, which can be expensive with your JDBC driver. You should use a recent driver
While this usually works well, there is a potential for issues (for example, with
Map-contained `null` values). Spring, by default, calls `ParameterMetaData.getParameterType`
in such a case, which can be expensive with your JDBC driver. You should use a recent driver
version and consider setting the `spring.jdbc.getParameterType.ignore` property to `true`
(as a JVM system property or via the
xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism) if you encounter
a performance issue (as reported on Oracle 12c, JBoss, and PostgreSQL).
xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism)
if you encounter a specific performance issue for your application.
Alternatively, you might consider specifying the corresponding JDBC types explicitly,
either through a `BatchPreparedStatementSetter` (as shown earlier), through an explicit type
array given to a `List<Object[]>` based call, through `registerSqlType` calls on a
custom `MapSqlParameterSource` instance, or through a `BeanPropertySqlParameterSource`
that derives the SQL type from the Java-declared property type even for a null value.
Alternatively, you could consider specifying the corresponding JDBC types explicitly,
either through a `BatchPreparedStatementSetter` (as shown earlier), through an explicit
type array given to a `List<Object[]>` based call, through `registerSqlType` calls on a
custom `MapSqlParameterSource` instance, through a `BeanPropertySqlParameterSource`
that derives the SQL type from the Java-declared property type even for a null value, or
through providing individual `SqlParameterValue` instances instead of plain null values.
====
@@ -268,8 +268,8 @@ The actual JPA provider bootstrapping is handed off to the specified executor an
running in parallel, to the application bootstrap thread. The exposed `EntityManagerFactory`
proxy can be injected into other application components and is even able to respond to
`EntityManagerFactoryInfo` configuration inspection. However, once the actual JPA provider
is being accessed by other components (for example, calling `createEntityManager`), those calls
block until the background bootstrapping has completed. In particular, when you use
is being accessed by other components (for example, calling `createEntityManager`), those
calls block until the background bootstrapping has completed. In particular, when you use
Spring Data JPA, make sure to set up deferred bootstrapping for its repositories as well.
@@ -284,9 +284,9 @@ to a newly created `EntityManager` per operation, in effect making its usage thr
It is possible to write code against the plain JPA without any Spring dependencies, by
using an injected `EntityManagerFactory` or `EntityManager`. Spring can understand the
`@PersistenceUnit` and `@PersistenceContext` annotations both at the field and the method level
if a `PersistenceAnnotationBeanPostProcessor` is enabled. The following example shows a plain
JPA DAO implementation that uses the `@PersistenceUnit` annotation:
`@PersistenceUnit` and `@PersistenceContext` annotations both at the field and the method
level if a `PersistenceAnnotationBeanPostProcessor` is enabled. The following example
shows a plain JPA DAO implementation that uses the `@PersistenceUnit` annotation:
[tabs]
======
@@ -23,7 +23,7 @@ As of Spring Framework 5.2, the default configuration also provides support for
Vavr's `Try` method to trigger transaction rollbacks when it returns a 'Failure'.
This allows you to handle functional-style errors using Try and have the transaction
automatically rolled back in case of a failure. For more information on Vavr's Try,
refer to the [official Vavr documentation](https://www.vavr.io/vavr-docs/#_try).
refer to the https://docs.vavr.io/#_try[official Vavr documentation].
Here's an example of how to use Vavr's Try with a transactional method:
[tabs]
+1 -3
View File
@@ -29,8 +29,6 @@ Brannen, Ramnivas Laddad, Arjen Poutsma, Chris Beams, Tareq Abedrabbo, Andy Clem
Syer, Oliver Gierke, Rossen Stoyanchev, Phillip Webb, Rob Winch, Brian Clozel, Stephane
Nicoll, Sebastien Deleuze, Jay Bryant, Mark Paluch
Copyright © 2002 - 2023 VMware, Inc. All Rights Reserved.
Copies of this document may be made for your own use and for distribution to others,
provided that you do not charge any fee for such copies and further provided that each
copy contains this Copyright Notice, whether distributed in print or electronically.
copy contains the Copyright Notice, whether distributed in print or electronically.
@@ -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(
@@ -58,7 +58,7 @@ your `@Configuration` classes, as the following example shows:
By default, the infrastructure looks for a bean named `jmsListenerContainerFactory`
as the source for the factory to use to create message listener containers. In this
case (and ignoring the JMS infrastructure setup), you can invoke the `processOrder`
method with a core poll size of three threads and a maximum pool size of ten threads.
method with a core pool size of three threads and a maximum pool size of ten threads.
You can customize the listener container factory to use for each annotation or you can
configure an explicit default by implementing the `JmsListenerConfigurer` interface.
@@ -152,7 +152,7 @@ Kotlin::
@Autowired
lateinit var accountService: AccountService
lateinit mockMvc: MockMvc
lateinit var mockMvc: MockMvc
@BeforeEach
fun setup(wac: WebApplicationContext) {
@@ -1,7 +1,7 @@
[[spring-mvc-test-vs-end-to-end-integration-tests]]
= MockMvc vs End-to-End Tests
MockMVc is built on Servlet API mock implementations from the
MockMvc is built on Servlet API mock implementations from the
`spring-test` module and does not rely on a running container. Therefore, there are
some differences when compared to full end-to-end integration tests with an actual
client and a live server running.
@@ -1,38 +1,15 @@
[[spring-mvc-test-vs-streaming-response]]
= Streaming Responses
The best way to test streaming responses such as Server-Sent Events is through the
<<WebTestClient>> which can be used as a test client to connect to a `MockMvc` instance
to perform tests on Spring MVC controllers without a running server. For example:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
WebTestClient client = MockMvcWebTestClient.bindToController(new SseController()).build();
FluxExchangeResult<Person> exchangeResult = client.get()
.uri("/persons")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType("text/event-stream")
.returnResult(Person.class);
// Use StepVerifier from Project Reactor to test the streaming response
StepVerifier.create(exchangeResult.getResponseBody())
.expectNext(new Person("N0"), new Person("N1"), new Person("N2"))
.expectNextCount(4)
.consumeNextWith(person -> assertThat(person.getName()).endsWith("7"))
.thenCancel()
.verify();
----
======
`WebTestClient` can also connect to a live server and perform full end-to-end integration
tests. This is also supported in Spring Boot where you can
{docs-spring-boot}/html/spring-boot-features.html#boot-features-testing-spring-boot-applications-testing-with-running-server[test a running server].
You can use `WebTestClient` to test xref:testing/webtestclient.adoc#webtestclient-stream[streaming responses]
such as Server-Sent Events. However, `MockMvcWebTestClient` doesn't support infinite
streams because there is no way to cancel the server stream from the client side.
To test infinite streams, you'll need to
xref:testing/webtestclient.adoc#webtestclient-server-config[bind to] a running server,
or when using Spring Boot,
{docs-spring-boot}/spring-boot-features.html#boot-features-testing-spring-boot-applications-testing-with-running-server[test with a running server].
`MockMvcWebTestClient` does support asynchronous responses, and even streaming responses.
The limitation is that it can't influence the server to stop, and therefore the server
must finish writing the response on its own.
@@ -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
@@ -52,14 +52,10 @@ The following example shows how to achieve the same configuration in XML:
</mvc:interceptors>
----
NOTE: Interceptors are not ideally suited as a security layer due to the potential
for a mismatch with annotated controller path matching, which can also match trailing
slashes and path extensions transparently, along with other path matching options. Many
of these options have been deprecated but the potential for a mismatch remains.
Generally, we recommend using Spring Security which includes a dedicated
https://docs.spring.io/spring-security/reference/servlet/integrations/mvc.html#mvc-requestmatcher[MvcRequestMatcher]
to align with Spring MVC path matching and also has a security firewall that blocks many
unwanted characters in URL paths.
WARNING: Interceptors are not ideally suited as a security layer due to the potential for
a mismatch with annotated controller path matching. Generally, we recommend using Spring
Security, or alternatively a similar approach integrated with the Servlet filter chain,
and applied as early as possible.
NOTE: The XML config declares interceptors as `MappedInterceptor` beans, and those are in
turn detected by any `HandlerMapping` bean, including those from other frameworks.
@@ -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.
@@ -1,34 +1,29 @@
[[mvc-handlermapping-interceptor]]
= Interception
All `HandlerMapping` implementations support handler interceptors that are useful when
you want to apply specific functionality to certain requests -- for example, checking for
a principal. Interceptors must implement `HandlerInterceptor` from the
`org.springframework.web.servlet` package with three methods that should provide enough
flexibility to do all kinds of pre-processing and post-processing:
All `HandlerMapping` implementations support handler interception which is useful when
you want to apply functionality across requests. A `HandlerInterceptor` can implement the
following:
* `preHandle(..)`: Before the actual handler is run
* `postHandle(..)`: After the handler is run
* `afterCompletion(..)`: After the complete request has finished
* `preHandle(..)` -- callback before the actual handler is run that returns a boolean.
If the method returns `true`, execution continues; if it returns `false`, the rest of the
execution chain is bypassed and the handler is not called.
* `postHandle(..)` -- callback after the handler is run.
* `afterCompletion(..)` -- callback after the complete request has finished.
The `preHandle(..)` method returns a boolean value. You can use this method to break or
continue the processing of the execution chain. When this method returns `true`, the
handler execution chain continues. When it returns false, the `DispatcherServlet`
assumes the interceptor itself has taken care of requests (and, for example, rendered an
appropriate view) and does not continue executing the other interceptors and the actual
handler in the execution chain.
NOTE: For `@ResponseBody` and `ResponseEntity` controller methods, the response is written
and committed within the `HandlerAdapter`, before `postHandle` is called. That means it is
too late to change the response, such as to add an extra header. You can implement
`ResponseBodyAdvice` and declare it as an
xref:web/webmvc/mvc-controller/ann-advice.adoc[Controller Advice] bean or configure it
directly on `RequestMappingHandlerAdapter`.
See xref:web/webmvc/mvc-config/interceptors.adoc[Interceptors] in the section on MVC configuration for examples of how to
configure interceptors. You can also register them directly by using setters on individual
`HandlerMapping` implementations.
`postHandle` method is less useful with `@ResponseBody` and `ResponseEntity` methods for
which the response is written and committed within the `HandlerAdapter` and before
`postHandle`. That means it is too late to make any changes to the response, such as adding
an extra header. For such scenarios, you can implement `ResponseBodyAdvice` and either
declare it as an xref:web/webmvc/mvc-controller/ann-advice.adoc[Controller Advice] bean or configure it directly on
`RequestMappingHandlerAdapter`.
WARNING: Interceptors are not ideally suited as a security layer due to the potential for
a mismatch with annotated controller path matching. Generally, we recommend using Spring
Security, or alternatively a similar approach integrated with the Servlet filter chain,
and applied as early as possible.
@@ -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);
}
}
----
+10
View File
@@ -0,0 +1,10 @@
{
"dependencies": {
"antora": "3.2.0-alpha.4",
"@antora/atlas-extension": "1.0.0-alpha.2",
"@antora/collector-extension": "1.0.0-alpha.3",
"@asciidoctor/tabs": "1.0.0-beta.6",
"@springio/antora-extensions": "1.11.1",
"@springio/asciidoctor-extensions": "1.0.0-alpha.10"
}
}
+19 -19
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.112.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.22"))
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.22"))
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.22"))
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.12.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.43")
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.15.Final")
api("io.undertow:undertow-servlet:2.3.15.Final")
api("io.undertow:undertow-websockets-jsr:2.3.15.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,22 +96,22 @@ 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")
api("org.apache.tomcat:tomcat-websocket:10.1.15")
api("org.aspectj:aspectjrt:1.9.20.1")
api("org.aspectj:aspectjtools:1.9.20.1")
api("org.aspectj:aspectjweaver:1.9.20.1")
api("org.aspectj:aspectjrt:1.9.22.1")
api("org.aspectj:aspectjtools:1.9.22.1")
api("org.aspectj:aspectjweaver:1.9.22.1")
api("org.assertj:assertj-core:3.24.2")
api("org.awaitility:awaitility:4.2.0")
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")
@@ -126,7 +126,7 @@ dependencies {
api("org.hibernate:hibernate-validator:7.0.5.Final")
api("org.hsqldb:hsqldb:2.7.2")
api("org.javamoney:moneta:1.4.2")
api("org.jruby:jruby:9.4.5.0")
api("org.jruby:jruby:9.4.7.0")
api("org.junit.support:testng-engine:1.0.4")
api("org.mozilla:rhino:1.7.14")
api("org.ogce:xpp3:1.1.6")
@@ -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.13")
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.23-SNAPSHOT
org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m
+2
View File
@@ -6,6 +6,7 @@ tasks.findByName("dokkaHtmlPartial")?.configure {
classpath.from(sourceSets["main"].runtimeClasspath)
externalDocumentationLink {
url.set(new URL("https://docs.spring.io/spring-framework/docs/current/javadoc-api/"))
packageListUrl.set(new URL("https://docs.spring.io/spring-framework/docs/current/javadoc-api/element-list"))
}
externalDocumentationLink {
url.set(new URL("https://projectreactor.io/docs/core/release/api/"))
@@ -21,6 +22,7 @@ tasks.findByName("dokkaHtmlPartial")?.configure {
}
externalDocumentationLink {
url.set(new URL("https://javadoc.io/doc/jakarta.servlet/jakarta.servlet-api/latest/"))
packageListUrl.set(new URL("https://javadoc.io/doc/jakarta.servlet/jakarta.servlet-api/latest/element-list"))
}
externalDocumentationLink {
url.set(new URL("https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/"))
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.9-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+5 -2
View File
@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -84,7 +86,8 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
Vendored
+12 -10
View File
@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@@ -43,11 +45,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 +59,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
@@ -63,7 +63,7 @@ class EnableCachingIntegrationTests {
// attempt was made to look up the AJ aspect. It's due to classpath issues
// in integration-tests that it's not found.
assertThatException().isThrownBy(ctx::refresh)
.withMessageContaining("AspectJCachingConfiguration");
.withMessageContaining("AspectJCachingConfiguration");
}
@@ -542,8 +542,7 @@ public class EnvironmentSystemIntegrationTests {
{
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setRequiredProperties("foo", "bar");
assertThatExceptionOfType(MissingRequiredPropertiesException.class).isThrownBy(
ctx::refresh);
assertThatExceptionOfType(MissingRequiredPropertiesException.class).isThrownBy(ctx::refresh);
}
{
@@ -98,9 +98,8 @@ class EnableTransactionManagementIntegrationTests {
// this test is a bit fragile, but gets the job done, proving that an
// attempt was made to look up the AJ aspect. It's due to classpath issues
// in integration-tests that it's not found.
assertThatException()
.isThrownBy(ctx::refresh)
.withMessageContaining("AspectJJtaTransactionManagementConfiguration");
assertThatException().isThrownBy(ctx::refresh)
.withMessageContaining("AspectJJtaTransactionManagementConfiguration");
}
@Test
+3 -3
View File
@@ -7,8 +7,8 @@ pluginManagement {
}
plugins {
id "com.gradle.enterprise" version "3.12.6"
id "io.spring.ge.conventions" version "0.0.13"
id "com.gradle.develocity" version "3.17.2"
id "io.spring.ge.conventions" version "0.0.17"
}
include "spring-aop"
@@ -45,7 +45,7 @@ rootProject.children.each {project ->
}
settings.gradle.projectsLoaded {
gradleEnterprise {
develocity {
buildScan {
File buildDir = settings.gradle.rootProject
.getLayout().getBuildDirectory().getAsFile().get()
@@ -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.
@@ -18,6 +18,7 @@ package org.springframework.aop.aspectj;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
@@ -41,6 +42,7 @@ import org.aspectj.weaver.tools.PointcutParameter;
import org.aspectj.weaver.tools.PointcutParser;
import org.aspectj.weaver.tools.PointcutPrimitive;
import org.aspectj.weaver.tools.ShadowMatch;
import org.aspectj.weaver.tools.UnsupportedPointcutPrimitiveException;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.IntroductionAwareMethodMatcher;
@@ -84,6 +86,8 @@ import org.springframework.util.StringUtils;
public class AspectJExpressionPointcut extends AbstractExpressionPointcut
implements ClassFilter, IntroductionAwareMethodMatcher, BeanFactoryAware {
private static final String AJC_MAGIC = "ajc$";
private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = Set.of(
PointcutPrimitive.EXECUTION,
PointcutPrimitive.ARGS,
@@ -101,6 +105,8 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
@Nullable
private Class<?> pointcutDeclarationScope;
private boolean aspectCompiledByAjc;
private String[] pointcutParameterNames = new String[0];
private Class<?>[] pointcutParameterTypes = new Class<?>[0];
@@ -114,6 +120,8 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
@Nullable
private transient PointcutExpression pointcutExpression;
private transient boolean pointcutParsingFailed = false;
private transient Map<Method, ShadowMatch> shadowMatchCache = new ConcurrentHashMap<>(32);
@@ -130,7 +138,7 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
* @param paramTypes the parameter types for the pointcut
*/
public AspectJExpressionPointcut(Class<?> declarationScope, String[] paramNames, Class<?>[] paramTypes) {
this.pointcutDeclarationScope = declarationScope;
setPointcutDeclarationScope(declarationScope);
if (paramNames.length != paramTypes.length) {
throw new IllegalStateException(
"Number of pointcut parameter names must match number of pointcut parameter types");
@@ -145,6 +153,7 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
*/
public void setPointcutDeclarationScope(Class<?> pointcutDeclarationScope) {
this.pointcutDeclarationScope = pointcutDeclarationScope;
this.aspectCompiledByAjc = compiledByAjc(pointcutDeclarationScope);
}
/**
@@ -169,25 +178,30 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
@Override
public ClassFilter getClassFilter() {
obtainPointcutExpression();
checkExpression();
return this;
}
@Override
public MethodMatcher getMethodMatcher() {
obtainPointcutExpression();
checkExpression();
return this;
}
/**
* Check whether this pointcut is ready to match,
* lazily building the underlying AspectJ pointcut expression.
* Check whether this pointcut is ready to match.
*/
private PointcutExpression obtainPointcutExpression() {
private void checkExpression() {
if (getExpression() == null) {
throw new IllegalStateException("Must set property 'expression' before attempting to match");
}
}
/**
* Lazily build the underlying AspectJ pointcut expression.
*/
private PointcutExpression obtainPointcutExpression() {
if (this.pointcutExpression == null) {
this.pointcutClassLoader = determinePointcutClassLoader();
this.pointcutExpression = buildPointcutExpression(this.pointcutClassLoader);
@@ -264,10 +278,18 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
@Override
public boolean matches(Class<?> targetClass) {
PointcutExpression pointcutExpression = obtainPointcutExpression();
if (this.pointcutParsingFailed) {
// Pointcut parsing failed before below -> avoid trying again.
return false;
}
if (this.aspectCompiledByAjc && compiledByAjc(targetClass)) {
// ajc-compiled aspect class for ajc-compiled target class -> already weaved.
return false;
}
try {
try {
return pointcutExpression.couldMatchJoinPointsInType(targetClass);
return obtainPointcutExpression().couldMatchJoinPointsInType(targetClass);
}
catch (ReflectionWorldException ex) {
logger.debug("PointcutExpression matching rejected target class - trying fallback expression", ex);
@@ -278,6 +300,12 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
}
}
}
catch (IllegalArgumentException | IllegalStateException | UnsupportedPointcutPrimitiveException ex) {
this.pointcutParsingFailed = true;
if (logger.isDebugEnabled()) {
logger.debug("Pointcut parser rejected expression [" + getExpression() + "]: " + ex);
}
}
catch (Throwable ex) {
logger.debug("PointcutExpression matching rejected target class", ex);
}
@@ -286,7 +314,6 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
@Override
public boolean matches(Method method, Class<?> targetClass, boolean hasIntroductions) {
obtainPointcutExpression();
ShadowMatch shadowMatch = getTargetShadowMatch(method, targetClass);
// Special handling for this, target, @this, @target, @annotation
@@ -324,7 +351,6 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
obtainPointcutExpression();
ShadowMatch shadowMatch = getTargetShadowMatch(method, targetClass);
// Bind Spring AOP proxy to AspectJ "this" and Spring AOP target to AspectJ target,
@@ -333,12 +359,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) {
@@ -513,6 +539,16 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
return shadowMatch;
}
private static boolean compiledByAjc(Class<?> clazz) {
for (Field field : clazz.getDeclaredFields()) {
if (field.getName().startsWith(AJC_MAGIC)) {
return true;
}
}
Class<?> superclass = clazz.getSuperclass();
return (superclass != null && compiledByAjc(superclass));
}
@Override
public boolean equals(@Nullable Object other) {
@@ -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.
@@ -18,7 +18,6 @@ package org.springframework.aop.aspectj.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.StringTokenizer;
@@ -56,8 +55,6 @@ import org.springframework.lang.Nullable;
*/
public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFactory {
private static final String AJC_MAGIC = "ajc$";
private static final Class<?>[] ASPECTJ_ANNOTATION_CLASSES = new Class<?>[] {
Pointcut.class, Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class};
@@ -68,37 +65,11 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
protected final ParameterNameDiscoverer parameterNameDiscoverer = new AspectJAnnotationParameterNameDiscoverer();
/**
* We consider something to be an AspectJ aspect suitable for use by the Spring AOP system
* if it has the @Aspect annotation, and was not compiled by ajc. The reason for this latter test
* is that aspects written in the code-style (AspectJ language) also have the annotation present
* when compiled by ajc with the -1.5 flag, yet they cannot be consumed by Spring AOP.
*/
@Override
public boolean isAspect(Class<?> clazz) {
return (hasAspectAnnotation(clazz) && !compiledByAjc(clazz));
}
private boolean hasAspectAnnotation(Class<?> clazz) {
return (AnnotationUtils.findAnnotation(clazz, Aspect.class) != null);
}
/**
* We need to detect this as "code-style" AspectJ aspects should not be
* interpreted by Spring AOP.
*/
private boolean compiledByAjc(Class<?> clazz) {
// The AJTypeSystem goes to great lengths to provide a uniform appearance between code-style and
// annotation-style aspects. Therefore there is no 'clean' way to tell them apart. Here we rely on
// an implementation detail of the AspectJ compiler.
for (Field field : clazz.getDeclaredFields()) {
if (field.getName().startsWith(AJC_MAGIC)) {
return true;
}
}
return false;
}
@Override
public void validate(Class<?> aspectClass) throws AopConfigException {
AjType<?> ajType = AjTypeSystem.getAjType(aspectClass);
@@ -115,6 +86,7 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
}
}
/**
* Find and return the first AspectJ annotation on the given method
* (there <i>should</i> only be one anyway...).
@@ -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.
@@ -124,10 +124,16 @@ public class AspectMetadata implements Serializable {
* Extract contents from String of form {@code pertarget(contents)}.
*/
private String findPerClause(Class<?> aspectClass) {
String str = aspectClass.getAnnotation(Aspect.class).value();
int beginIndex = str.indexOf('(') + 1;
int endIndex = str.length() - 1;
return str.substring(beginIndex, endIndex);
Aspect ann = aspectClass.getAnnotation(Aspect.class);
if (ann == null) {
return "";
}
String value = ann.value();
int beginIndex = value.indexOf('(');
if (beginIndex < 0) {
return "";
}
return value.substring(beginIndex + 1, value.length() - 1);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-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.
@@ -22,9 +22,12 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.reflect.PerClauseKind;
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.AopConfigException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.lang.Nullable;
@@ -40,6 +43,8 @@ import org.springframework.util.Assert;
*/
public class BeanFactoryAspectJAdvisorsBuilder {
private static final Log logger = LogFactory.getLog(BeanFactoryAspectJAdvisorsBuilder.class);
private final ListableBeanFactory beanFactory;
private final AspectJAdvisorFactory advisorFactory;
@@ -102,30 +107,37 @@ public class BeanFactoryAspectJAdvisorsBuilder {
continue;
}
if (this.advisorFactory.isAspect(beanType)) {
aspectNames.add(beanName);
AspectMetadata amd = new AspectMetadata(beanType, beanName);
if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
MetadataAwareAspectInstanceFactory factory =
new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
if (this.beanFactory.isSingleton(beanName)) {
this.advisorsCache.put(beanName, classAdvisors);
try {
AspectMetadata amd = new AspectMetadata(beanType, beanName);
if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
MetadataAwareAspectInstanceFactory factory =
new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
if (this.beanFactory.isSingleton(beanName)) {
this.advisorsCache.put(beanName, classAdvisors);
}
else {
this.aspectFactoryCache.put(beanName, factory);
}
advisors.addAll(classAdvisors);
}
else {
// Per target or per this.
if (this.beanFactory.isSingleton(beanName)) {
throw new IllegalArgumentException("Bean with name '" + beanName +
"' is a singleton, but aspect instantiation model is not singleton");
}
MetadataAwareAspectInstanceFactory factory =
new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
this.aspectFactoryCache.put(beanName, factory);
advisors.addAll(this.advisorFactory.getAdvisors(factory));
}
advisors.addAll(classAdvisors);
aspectNames.add(beanName);
}
else {
// Per target or per this.
if (this.beanFactory.isSingleton(beanName)) {
throw new IllegalArgumentException("Bean with name '" + beanName +
"' is a singleton, but aspect instantiation model is not singleton");
catch (IllegalArgumentException | IllegalStateException | AopConfigException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Ignoring incompatible aspect [" + beanType.getName() + "]: " + ex);
}
MetadataAwareAspectInstanceFactory factory =
new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
this.aspectFactoryCache.put(beanName, factory);
advisors.addAll(this.advisorFactory.getAdvisors(factory));
}
}
}
@@ -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() {
@@ -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.
@@ -50,6 +50,7 @@ import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConvertingComparator;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.MethodFilter;
import org.springframework.util.StringUtils;
@@ -133,17 +134,19 @@ public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFacto
List<Advisor> advisors = new ArrayList<>();
for (Method method : getAdvisorMethods(aspectClass)) {
// Prior to Spring Framework 5.2.7, advisors.size() was supplied as the declarationOrderInAspect
// to getAdvisor(...) to represent the "current position" in the declared methods list.
// However, since Java 7 the "current position" is not valid since the JDK no longer
// returns declared methods in the order in which they are declared in the source code.
// Thus, we now hard code the declarationOrderInAspect to 0 for all advice methods
// discovered via reflection in order to support reliable advice ordering across JVM launches.
// Specifically, a value of 0 aligns with the default value used in
// AspectJPrecedenceComparator.getAspectDeclarationOrder(Advisor).
Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, 0, aspectName);
if (advisor != null) {
advisors.add(advisor);
if (method.equals(ClassUtils.getMostSpecificMethod(method, aspectClass))) {
// Prior to Spring Framework 5.2.7, advisors.size() was supplied as the declarationOrderInAspect
// to getAdvisor(...) to represent the "current position" in the declared methods list.
// However, since Java 7 the "current position" is not valid since the JDK no longer
// returns declared methods in the order in which they are declared in the source code.
// Thus, we now hard code the declarationOrderInAspect to 0 for all advice methods
// discovered via reflection in order to support reliable advice ordering across JVM launches.
// Specifically, a value of 0 aligns with the default value used in
// AspectJPrecedenceComparator.getAspectDeclarationOrder(Advisor).
Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, 0, aspectName);
if (advisor != null) {
advisors.add(advisor);
}
}
}
@@ -210,8 +213,16 @@ public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFacto
return null;
}
return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
try {
return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}
catch (IllegalArgumentException | IllegalStateException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Ignoring incompatible advice method: " + candidateAdviceMethod, ex);
}
return null;
}
}
@Nullable
@@ -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,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;
@@ -362,8 +368,7 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
private void validateIntroductionAdvisor(IntroductionAdvisor advisor) {
advisor.validateInterfaces();
// If the advisor passed validation, we can make the change.
Class<?>[] ifcs = advisor.getInterfaces();
for (Class<?> ifc : ifcs) {
for (Class<?> ifc : advisor.getInterfaces()) {
addInterface(ifc);
}
}
@@ -557,18 +562,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 +583,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 +645,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 +655,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-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.
@@ -54,6 +54,7 @@ class ScopedProxyBeanRegistrationAotProcessor implements BeanRegistrationAotProc
@Override
@Nullable
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
Class<?> beanClass = registeredBean.getBeanClass();
if (beanClass.equals(ScopedProxyFactoryBean.class)) {
@@ -79,8 +80,8 @@ class ScopedProxyBeanRegistrationAotProcessor implements BeanRegistrationAotProc
}
@Nullable
private BeanDefinition getTargetBeanDefinition(ConfigurableBeanFactory beanFactory,
@Nullable String targetBeanName) {
private BeanDefinition getTargetBeanDefinition(
ConfigurableBeanFactory beanFactory, @Nullable String targetBeanName) {
if (targetBeanName != null && beanFactory.containsBean(targetBeanName)) {
return beanFactory.getMergedBeanDefinition(targetBeanName);
@@ -123,42 +124,32 @@ class ScopedProxyBeanRegistrationAotProcessor implements BeanRegistrationAotProc
@Override
public CodeBlock generateSetBeanDefinitionPropertiesCode(
GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode,
GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode,
RootBeanDefinition beanDefinition, Predicate<String> attributeFilter) {
RootBeanDefinition processedBeanDefinition = new RootBeanDefinition(
beanDefinition);
processedBeanDefinition
.setTargetType(this.targetBeanDefinition.getResolvableType());
processedBeanDefinition.getPropertyValues()
.removePropertyValue("targetBeanName");
RootBeanDefinition processedBeanDefinition = new RootBeanDefinition(beanDefinition);
processedBeanDefinition.setTargetType(this.targetBeanDefinition.getResolvableType());
processedBeanDefinition.getPropertyValues().removePropertyValue("targetBeanName");
return super.generateSetBeanDefinitionPropertiesCode(generationContext,
beanRegistrationCode, processedBeanDefinition, attributeFilter);
}
@Override
public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode,
Executable constructorOrFactoryMethod,
boolean allowDirectSupplierShortcut) {
public CodeBlock generateInstanceSupplierCode(
GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode,
Executable constructorOrFactoryMethod, boolean allowDirectSupplierShortcut) {
GeneratedMethod generatedMethod = beanRegistrationCode.getMethods()
.add("getScopedProxyInstance", method -> {
method.addJavadoc(
"Create the scoped proxy bean instance for '$L'.",
method.addJavadoc("Create the scoped proxy bean instance for '$L'.",
this.registeredBean.getBeanName());
method.addModifiers(Modifier.PRIVATE, Modifier.STATIC);
method.returns(ScopedProxyFactoryBean.class);
method.addParameter(RegisteredBean.class,
REGISTERED_BEAN_PARAMETER_NAME);
method.addParameter(RegisteredBean.class, REGISTERED_BEAN_PARAMETER_NAME);
method.addStatement("$T factory = new $T()",
ScopedProxyFactoryBean.class,
ScopedProxyFactoryBean.class);
method.addStatement("factory.setTargetBeanName($S)",
this.targetBeanName);
method.addStatement(
"factory.setBeanFactory($L.getBeanFactory())",
ScopedProxyFactoryBean.class, ScopedProxyFactoryBean.class);
method.addStatement("factory.setTargetBeanName($S)", this.targetBeanName);
method.addStatement("factory.setBeanFactory($L.getBeanFactory())",
REGISTERED_BEAN_PARAMETER_NAME);
method.addStatement("return factory");
});
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -186,10 +186,10 @@ public abstract class AopUtils {
* this method resolves bridge methods in order to retrieve attributes from
* the <i>original</i> method definition.
* @param method the method to be invoked, which may come from an interface
* @param targetClass the target class for the current invocation.
* May be {@code null} or may not even implement the method.
* @param targetClass the target class for the current invocation
* (can be {@code null} or may not even implement the method)
* @return the specific target method, or the original method if the
* {@code targetClass} doesn't implement it or is {@code null}
* {@code targetClass} does not implement it
* @see org.springframework.util.ClassUtils#getMostSpecificMethod
*/
public static Method getMostSpecificMethod(Method method, @Nullable Class<?> targetClass) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,9 +23,6 @@ import java.util.Map;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.aspectj.weaver.tools.PointcutExpression;
import org.aspectj.weaver.tools.PointcutPrimitive;
import org.aspectj.weaver.tools.UnsupportedPointcutPrimitiveException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import test.annotation.EmptySpringAnnotation;
@@ -42,14 +39,13 @@ import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.beans.testfixture.beans.subpkg.DeepBean;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* @author Rob Harrop
* @author Rod Johnson
* @author Chris Beams
* @author Juergen Hoeller
*/
public class AspectJExpressionPointcutTests {
@@ -65,7 +61,7 @@ public class AspectJExpressionPointcutTests {
@BeforeEach
public void setUp() throws NoSuchMethodException {
public void setup() throws NoSuchMethodException {
getAge = TestBean.class.getMethod("getAge");
setAge = TestBean.class.getMethod("setAge", int.class);
setSomeNumber = TestBean.class.getMethod("setSomeNumber", Number.class);
@@ -175,25 +171,25 @@ public class AspectJExpressionPointcutTests {
@Test
public void testFriendlyErrorOnNoLocationClassMatching() {
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
assertThatIllegalStateException().isThrownBy(() ->
pc.matches(ITestBean.class))
.withMessageContaining("expression");
assertThatIllegalStateException()
.isThrownBy(() -> pc.getClassFilter().matches(ITestBean.class))
.withMessageContaining("expression");
}
@Test
public void testFriendlyErrorOnNoLocation2ArgMatching() {
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
assertThatIllegalStateException().isThrownBy(() ->
pc.matches(getAge, ITestBean.class))
.withMessageContaining("expression");
assertThatIllegalStateException()
.isThrownBy(() -> pc.getMethodMatcher().matches(getAge, ITestBean.class))
.withMessageContaining("expression");
}
@Test
public void testFriendlyErrorOnNoLocation3ArgMatching() {
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
assertThatIllegalStateException().isThrownBy(() ->
pc.matches(getAge, ITestBean.class, (Object[]) null))
.withMessageContaining("expression");
assertThatIllegalStateException()
.isThrownBy(() -> pc.getMethodMatcher().matches(getAge, ITestBean.class, (Object[]) null))
.withMessageContaining("expression");
}
@@ -210,8 +206,10 @@ public class AspectJExpressionPointcutTests {
// not currently testable in a reliable fashion
//assertDoesNotMatchStringClass(classFilter);
assertThat(methodMatcher.matches(setSomeNumber, TestBean.class, 12D)).as("Should match with setSomeNumber with Double input").isTrue();
assertThat(methodMatcher.matches(setSomeNumber, TestBean.class, 11)).as("Should not match setSomeNumber with Integer input").isFalse();
assertThat(methodMatcher.matches(setSomeNumber, TestBean.class, 12D))
.as("Should match with setSomeNumber with Double input").isTrue();
assertThat(methodMatcher.matches(setSomeNumber, TestBean.class, 11))
.as("Should not match setSomeNumber with Integer input").isFalse();
assertThat(methodMatcher.matches(getAge, TestBean.class)).as("Should not match getAge").isFalse();
assertThat(methodMatcher.isRuntime()).as("Should be a runtime match").isTrue();
}
@@ -246,14 +244,13 @@ public class AspectJExpressionPointcutTests {
@Test
public void testInvalidExpression() {
String expression = "execution(void org.springframework.beans.testfixture.beans.TestBean.setSomeNumber(Number) && args(Double)";
assertThatIllegalArgumentException().isThrownBy(
getPointcut(expression)::getClassFilter); // call to getClassFilter forces resolution
assertThat(getPointcut(expression).getClassFilter().matches(Object.class)).isFalse();
}
private TestBean getAdvisedProxy(String pointcutExpression, CallCountingInterceptor interceptor) {
TestBean target = new TestBean();
Pointcut pointcut = getPointcut(pointcutExpression);
AspectJExpressionPointcut pointcut = getPointcut(pointcutExpression);
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
advisor.setAdvice(interceptor);
@@ -277,40 +274,29 @@ public class AspectJExpressionPointcutTests {
@Test
public void testWithUnsupportedPointcutPrimitive() {
String expression = "call(int org.springframework.beans.testfixture.beans.TestBean.getAge())";
assertThatExceptionOfType(UnsupportedPointcutPrimitiveException.class).isThrownBy(() ->
getPointcut(expression).getClassFilter()) // call to getClassFilter forces resolution...
.satisfies(ex -> assertThat(ex.getUnsupportedPrimitive()).isEqualTo(PointcutPrimitive.CALL));
assertThat(getPointcut(expression).getClassFilter().matches(Object.class)).isFalse();
}
@Test
public void testAndSubstitution() {
Pointcut pc = getPointcut("execution(* *(..)) and args(String)");
PointcutExpression expr = ((AspectJExpressionPointcut) pc).getPointcutExpression();
assertThat(expr.getPointcutExpression()).isEqualTo("execution(* *(..)) && args(String)");
AspectJExpressionPointcut pc = getPointcut("execution(* *(..)) and args(String)");
String expr = pc.getPointcutExpression().getPointcutExpression();
assertThat(expr).isEqualTo("execution(* *(..)) && args(String)");
}
@Test
public void testMultipleAndSubstitutions() {
Pointcut pc = getPointcut("execution(* *(..)) and args(String) and this(Object)");
PointcutExpression expr = ((AspectJExpressionPointcut) pc).getPointcutExpression();
assertThat(expr.getPointcutExpression()).isEqualTo("execution(* *(..)) && args(String) && this(Object)");
AspectJExpressionPointcut pc = getPointcut("execution(* *(..)) and args(String) and this(Object)");
String expr = pc.getPointcutExpression().getPointcutExpression();
assertThat(expr).isEqualTo("execution(* *(..)) && args(String) && this(Object)");
}
private Pointcut getPointcut(String expression) {
private AspectJExpressionPointcut getPointcut(String expression) {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(expression);
return pointcut;
}
public static class OtherIOther implements IOther {
@Override
public void absquatulate() {
// Empty
}
}
@Test
public void testMatchGenericArgument() {
String expression = "execution(* set*(java.util.List<org.springframework.beans.testfixture.beans.TestBean>) )";
@@ -505,6 +491,15 @@ public class AspectJExpressionPointcutTests {
}
public static class OtherIOther implements IOther {
@Override
public void absquatulate() {
// Empty
}
}
public static class HasGeneric {
public void setFriends(List<TestBean> friends) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-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.
@@ -38,7 +38,6 @@ import org.aspectj.lang.annotation.DeclareParents;
import org.aspectj.lang.annotation.DeclarePrecedence;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.aop.Advisor;
@@ -84,15 +83,15 @@ abstract class AbstractAspectJAdvisorFactoryTests {
@Test
void rejectsPerCflowAspect() {
assertThatExceptionOfType(AopConfigException.class)
.isThrownBy(() -> getAdvisorFactory().getAdvisors(aspectInstanceFactory(new PerCflowAspect(), "someBean")))
.withMessageContaining("PERCFLOW");
.isThrownBy(() -> getAdvisorFactory().getAdvisors(aspectInstanceFactory(new PerCflowAspect(), "someBean")))
.withMessageContaining("PERCFLOW");
}
@Test
void rejectsPerCflowBelowAspect() {
assertThatExceptionOfType(AopConfigException.class)
.isThrownBy(() -> getAdvisorFactory().getAdvisors(aspectInstanceFactory(new PerCflowBelowAspect(), "someBean")))
.withMessageContaining("PERCFLOWBELOW");
.isThrownBy(() -> getAdvisorFactory().getAdvisors(aspectInstanceFactory(new PerCflowBelowAspect(), "someBean")))
.withMessageContaining("PERCFLOWBELOW");
}
@Test
@@ -363,7 +362,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
assertThat(lockable.locked()).as("Already locked").isTrue();
lockable.lock();
assertThat(lockable.locked()).as("Real target ignores locking").isTrue();
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> lockable.unlock());
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(lockable::unlock);
}
@Test
@@ -389,9 +388,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
assertThat(lockable.locked()).isTrue();
}
// TODO: Why does this test fail? It hasn't been run before, so it maybe never actually passed...
@Test
@Disabled
void introductionWithArgumentBinding() {
TestBean target = new TestBean();
@@ -648,7 +645,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
static class NamedPointcutAspectWithFQN {
@SuppressWarnings("unused")
private ITestBean fieldThatShouldBeIgnoredBySpringAtAspectJProcessing = new TestBean();
private final ITestBean fieldThatShouldBeIgnoredBySpringAtAspectJProcessing = new TestBean();
@Around("org.springframework.aop.aspectj.annotation.AbstractAspectJAdvisorFactoryTests.CommonPointcuts.getAge()()")
int changeReturnValue(ProceedingJoinPoint pjp) {
@@ -765,7 +762,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
@Aspect
class DoublingAspect {
static class DoublingAspect {
@Around("execution(* getAge())")
public Object doubleAge(ProceedingJoinPoint pjp) throws Throwable {
@@ -773,8 +770,14 @@ abstract class AbstractAspectJAdvisorFactoryTests {
}
}
@Aspect
class IncrementingAspect extends DoublingAspect {
static class IncrementingAspect extends DoublingAspect {
@Override
public Object doubleAge(ProceedingJoinPoint pjp) throws Throwable {
return ((int) pjp.proceed()) * 2;
}
@Around("execution(* getAge())")
public int incrementAge(ProceedingJoinPoint pjp) throws Throwable {
@@ -783,7 +786,6 @@ abstract class AbstractAspectJAdvisorFactoryTests {
}
@Aspect
private static class InvocationTrackingAspect {
@@ -1083,7 +1085,7 @@ class PerThisAspect {
// Just to check that this doesn't cause problems with introduction processing
@SuppressWarnings("unused")
private ITestBean fieldThatShouldBeIgnoredBySpringAtAspectJProcessing = new TestBean();
private final ITestBean fieldThatShouldBeIgnoredBySpringAtAspectJProcessing = new TestBean();
@Around("execution(int *.getAge())")
int returnCountAsAge() {
@@ -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]));
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,29 +17,35 @@
package org.springframework.aop.support;
import java.lang.reflect.Method;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.interceptor.ExposeInvocationInterceptor;
import org.springframework.aop.target.EmptyTargetSource;
import org.springframework.aop.testfixture.interceptor.NopInterceptor;
import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.core.ResolvableType;
import org.springframework.core.testfixture.io.SerializationTestUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Rod Johnson
* @author Chris Beams
* @author Sebastien Deleuze
* @author Juergen Hoeller
*/
public class AopUtilsTests {
class AopUtilsTests {
@Test
public void testPointcutCanNeverApply() {
void testPointcutCanNeverApply() {
class TestPointcut extends StaticMethodMatcherPointcut {
@Override
public boolean matches(Method method, @Nullable Class<?> clazzy) {
@@ -52,13 +58,13 @@ public class AopUtilsTests {
}
@Test
public void testPointcutAlwaysApplies() {
void testPointcutAlwaysApplies() {
assertThat(AopUtils.canApply(new DefaultPointcutAdvisor(new NopInterceptor()), Object.class)).isTrue();
assertThat(AopUtils.canApply(new DefaultPointcutAdvisor(new NopInterceptor()), TestBean.class)).isTrue();
}
@Test
public void testPointcutAppliesToOneMethodOnObject() {
void testPointcutAppliesToOneMethodOnObject() {
class TestPointcut extends StaticMethodMatcherPointcut {
@Override
public boolean matches(Method method, @Nullable Class<?> clazz) {
@@ -78,7 +84,7 @@ public class AopUtilsTests {
* that's subverted the singleton construction limitation.
*/
@Test
public void testCanonicalFrameworkClassesStillCanonicalOnDeserialization() throws Exception {
void testCanonicalFrameworkClassesStillCanonicalOnDeserialization() throws Exception {
assertThat(SerializationTestUtils.serializeAndDeserialize(MethodMatcher.TRUE)).isSameAs(MethodMatcher.TRUE);
assertThat(SerializationTestUtils.serializeAndDeserialize(ClassFilter.TRUE)).isSameAs(ClassFilter.TRUE);
assertThat(SerializationTestUtils.serializeAndDeserialize(Pointcut.TRUE)).isSameAs(Pointcut.TRUE);
@@ -88,4 +94,45 @@ public class AopUtilsTests {
assertThat(SerializationTestUtils.serializeAndDeserialize(ExposeInvocationInterceptor.INSTANCE)).isSameAs(ExposeInvocationInterceptor.INSTANCE);
}
@Test
void testInvokeJoinpointUsingReflection() throws Throwable {
String name = "foo";
TestBean testBean = new TestBean(name);
Method method = ReflectionUtils.findMethod(TestBean.class, "getName");
Object result = AopUtils.invokeJoinpointUsingReflection(testBean, method, new Object[0]);
assertThat(result).isEqualTo(name);
}
@Test // gh-32365
void mostSpecificMethodBetweenJdkProxyAndTarget() throws Exception {
Class<?> proxyClass = new ProxyFactory(new WithInterface()).getProxyClass(getClass().getClassLoader());
Method specificMethod = AopUtils.getMostSpecificMethod(proxyClass.getMethod("handle", List.class), WithInterface.class);
assertThat(ResolvableType.forMethodParameter(specificMethod, 0).getGeneric().toClass()).isEqualTo(String.class);
}
@Test // gh-32365
void mostSpecificMethodBetweenCglibProxyAndTarget() throws Exception {
Class<?> proxyClass = new ProxyFactory(new WithoutInterface()).getProxyClass(getClass().getClassLoader());
Method specificMethod = AopUtils.getMostSpecificMethod(proxyClass.getMethod("handle", List.class), WithoutInterface.class);
assertThat(ResolvableType.forMethodParameter(specificMethod, 0).getGeneric().toClass()).isEqualTo(String.class);
}
interface ProxyInterface {
void handle(List<String> list);
}
static class WithInterface implements ProxyInterface {
public void handle(List<String> list) {
}
}
static class WithoutInterface {
public void handle(List<String> list) {
}
}
}

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