Compare commits

...

130 Commits

Author SHA1 Message Date
Spring Builds 0fbc94fae0 Release v6.0.3 2022-12-15 09:01:48 +00:00
Sam Brannen 6e08c56076 Improve Javadoc for RepeatableContainers 2022-12-14 13:40:43 +01:00
Sam Brannen fb6d3f5b5c Remove duplicated test code 2022-12-14 13:26:51 +01:00
Arjen Poutsma 6fe5652783 Support non-standard HTTP methods in FrameworkServlet
This commit ensures that HTTP methods not supported by HttpServlet, for
instance WebDAV methods, are still supported in FrameworkServlet.

Closes gh-29689
2022-12-14 12:54:41 +01:00
Brian Clozel ca68bbc9ff Upgrade to Reactor 2022.0.1
Closes gh-29679
2022-12-13 22:52:24 +01:00
Sam Brannen e7bcb48e57 Remove obsolete AttributeMethods.hasOnlyValueAttribute() method
See gh-29685
2022-12-13 16:01:05 +01:00
Sam Brannen 433b1c480c Support repeatable annotation containers with multiple attributes
Prior to this commit, there was a bug in the implementation of
StandardRepeatableContainers.computeRepeatedAnnotationsMethod() which
has existed since Spring Framework 5.2 (when
StandardRepeatableContainers was introduced). Specifically,
StandardRepeatableContainers ignored any repeatable container
annotation if it declared attributes other than `value()`. However,
Java permits any number of attributes in a repeatable container
annotation.

In addition, the changes made in conjunction with gh-20279 made the bug
in StandardRepeatableContainers apparent when using the
getMergedRepeatableAnnotations() or findMergedRepeatableAnnotations()
method in AnnotatedElementUtils, resulting in regressions for the
behavior of those two methods.

This commit fixes the regressions and bug by altering the logic in
StandardRepeatableContainers.computeRepeatedAnnotationsMethod() so that
it explicitly looks for the `value()` method and ignores any other
methods declared in a repeatable container annotation candidate.

Closes gh-29685
2022-12-13 16:00:55 +01:00
Sam Brannen 0b08246760 Revise RepeatableContainersTests 2022-12-13 15:54:43 +01:00
Sam Brannen c7bdfbea4f Add missing Javadoc
See gh-29574
2022-12-13 13:46:31 +01:00
Sam Brannen 618989d317 Update copyright date 2022-12-13 13:44:18 +01:00
Sam Brannen 7fe78b745f Polish Javadoc 2022-12-13 13:44:03 +01:00
Juergen Hoeller 98f152e8d5 Polishing 2022-12-13 11:07:46 +01:00
Juergen Hoeller d74191427e Avoid NPE on BeanDescriptor access with SimpleBeanInfoFactory
Closes gh-29681
2022-12-13 11:07:22 +01:00
Juergen Hoeller 4c69892f39 Detect SQL state 23505/40001 as DuplicateKeyException/CannotAcquireLockException
Favors PessimisticLockingFailureException over plain ConcurrencyFailureException.
Deprecates CannotSerializeTransactionException and DeadlockLoserDataAccessException.

Closes gh-29511
Closes gh-29675
2022-12-13 11:07:13 +01:00
Arjen Poutsma c79ae0c842 Align multipart codecs on client and server
This commit ensures that the same multipart codecs are registered on
both client and server. Previously, only the client enabled only sending
 multipart, and the server only receiving.

Closes gh-29630
2022-12-13 10:13:46 +01:00
Sam Brannen 46fc28fd1a Clean up Javadoc and source code regarding " ." typos 2022-12-12 16:31:14 +01:00
Johnny Lim f8a1dac593 Use consistent visibility for ResponseEntityExceptionHandler.getMessageSource()
See gh-29574
Closes gh-29676
2022-12-12 16:15:52 +01:00
Brian Clozel 9712bb62fd Upgrade optional dependencies 2022-12-12 14:58:21 +01:00
Brian Clozel e85f6342a2 Start building against Reactor 2022.0.1
See gh-29679
2022-12-12 14:58:21 +01:00
Brian Clozel 7199782d45 Upgrade to Micrometer 1.10.2
Closes gh-29678
2022-12-12 14:58:13 +01:00
Sébastien Deleuze 5ed4a14e2b Make @ModelAttribute and @InitBinder reflective
Closes gh-29572
2022-12-11 12:02:55 +01:00
Sébastien Deleuze b27735d4a6 Refine BindingReflectionHintsRegistrar Kotlin support
Closes gh-29593
2022-12-11 12:02:55 +01:00
Sébastien Deleuze dc5a773b2b Support properties on records in BindingReflectionHintsRegistrar
Closes gh-29571
2022-12-10 16:12:23 +01:00
Sam Brannen 2b6f3caff4 Remove dead code in MockClientHttpRequest 2022-12-09 15:57:35 -05:00
Sam Brannen 83eb8ac0ea Introduce additional constructors in MockClientHttp[Request|Response]
This commit introduces additional constructors in MockClientHttpRequest
and MockClientHttpResponse that were previously only present in the
internal "test fixtures" in spring-web.

This commit also aligns the mocks in spring-test with the test fixtures
in spring-web to simplify continued maintenance of the mocks and test
fixtures.

Closes gh-29670
2022-12-09 15:46:08 -05:00
Sam Brannen 9b38e43c17 Use URI#create instead of URI constructor where feasible in spring-websocket 2022-12-09 13:27:34 -05:00
Sam Brannen 284cb12f8f Use URI#create instead of URI constructor where feasible in spring-webmvc 2022-12-09 13:27:13 -05:00
Sam Brannen 67644a28b6 Use URI#create instead of URI constructor where feasible in spring-webflux 2022-12-09 13:26:30 -05:00
Sam Brannen 1b57f2bda5 Use URI#create instead of URI constructor where feasible in spring-web 2022-12-09 13:26:01 -05:00
Sam Brannen a1a140f7d5 Use URI#create instead of URI constructor where feasible in spring-test 2022-12-09 12:27:06 -05:00
Sam Brannen 4caf3c8ce0 Polish tests in spring-test 2022-12-09 12:26:38 -05:00
Sam Brannen 485c80fcf3 Apply 'instanceof pattern matching' in spring-test and Servlet mocks 2022-12-09 11:56:14 -05:00
rstoyanchev aae46263cc Restore top-level read permission for backport bot 2022-12-09 12:02:56 +00:00
rstoyanchev 39d4d2041a Polishing contribution
Closes gh-29634
2022-12-09 10:24:42 +00:00
koo.taejin 5ac97b16a8 Optimize object creation PartialMatchHelper
See gh-29634
2022-12-09 10:19:54 +00:00
rstoyanchev f457c17c78 Remove top-level permissions from backport-bot.yml 2022-12-09 09:10:03 +00:00
Sam Brannen b0b2bbc857 Polish CGLIB fork
- primarily automated "clean up" using Eclipse IDE
2022-12-09 00:56:38 -05:00
Sam Brannen 69f47e7700 Polishing
- primarily automated "clean up" using Eclipse IDE
2022-12-09 00:56:00 -05:00
Sam Brannen 52af5c2b38 Support arrays in AST string representations of SpEL expressions
Prior to this commit, SpEL's ConstructorReference did not provide
support for arrays when generating a string representation of the
internal AST. For example, 'new String[3]' was represented as 'new
String()' instead of 'new String[3]'.

This commit introduces support for standard array construction and array
construction with initializers in ConstructorReference's toStringAST()
implementation.

Closes gh-29665
2022-12-08 18:17:45 -05:00
Sam Brannen 404661d5cb Polishing 2022-12-08 16:03:05 -05:00
Sam Brannen f07a4587bb Apply 'instanceof pattern matching' in spring-expression 2022-12-07 17:15:25 -05:00
Sam Brannen 114d6a9256 Apply 'instanceof pattern matching' in spring-jdbc 2022-12-07 17:15:25 -05:00
Sam Brannen 73a18046c3 Fix SpEL support for quotes within String literals
Prior to this commit, there were two bugs in the support for quotes
within String literals in SpEL expressions.

- Two double quotes ("") or two single quotes ('') were always replaced
  with one double quote or one single quote, respectively, regardless
  of which quote character was used to enclose the original String
  literal. This resulted in the loss of one of the double quotes when
  the String literal was enclosed in single quotes, and vice versa. For
  example, 'x "" y' became 'x " y'.

- A single quote which was properly escaped in a String literal
  enclosed within single quotes was not escaped in the AST string
  representation of the expression. For example, 'x '' y' became 'x ' y'.

This commit fixes both of these related issues in StringLiteral and
overhauls the structure of ParsingTests.

Closes gh-29604, gh-28356
2022-12-07 15:43:47 -05:00
Sam Brannen c899af0e03 Polishing 2022-12-07 15:43:47 -05:00
Sam Brannen 098c924e32 Introduce @Suite classes for individual modules 2022-12-07 12:12:41 -05:00
Sam Brannen 4289a64e24 Improve Javadoc for SqlLobValue 2022-12-07 12:12:41 -05:00
Sam Brannen 7615762039 Update copyright headers 2022-12-07 12:11:45 -05:00
Brian Clozel d601f3196a Fix BindingReflectionHintsRegistrar anonymous classes support
This commit ensures that giving an anonymous class for reflection hints
registration does not result in a NullPointerException, since the
canonical name of anonymous classes is null.

Fixes gh-29657
2022-12-07 16:37:01 +01:00
Arjen Poutsma 92a6e7ddcd Fix InputStream violation in DataBufferInputStream
This commit fixes an issue in DataBufferInputStream::mark, which before
did not follow the contract defined by InputStream.

Closes gh-29642
2022-12-07 15:23:19 +01:00
ShenFeng312 c79474d269 Polish var-args declarations
Closes gh-29640
2022-12-07 04:30:40 +01:00
Sam Brannen f4bc9ffb98 Reintroduce component index support for Jakarta annotations
Spring Framework 6.0 GA introduced a regression in the component index
support for Jakarta annotations such as @Named and @ManagedBean.

Prior to this commit, @Named and @ManagedBean components were
registered in the component index at build time; however, component
scanning failed to find those component at run time.

This commit updates ClassPathScanningCandidateComponentProvider so that
`jakarta.*` annotation types are once again supported for component
scanning via the component index at run time.

Closes gh-29641
2022-12-06 16:23:31 -05:00
Sam Brannen e124e802a3 Polishing 2022-12-06 16:23:31 -05:00
Sam Brannen 348cc01284 Rename MultipartWebClientIntegrationTests classes to avoid duplicate names 2022-12-06 12:50:36 -05:00
Sam Brannen 9f7a510f90 Polishing 2022-12-06 12:29:03 -05:00
divcon f64397d822 Apply "instanceof pattern matching" in spring-webflux
Closes gh-29635
2022-12-06 10:03:26 -05:00
rstoyanchev 918edaba2e Add convenience methods for binding error messages
Closes gh-29574
2022-12-06 13:37:08 +00:00
rstoyanchev 5214bd3093 Fix issue with getHeaders in NoHandlerFoundException
Closes gh-29626
2022-12-06 13:37:08 +00:00
Moritz Halbritter 752ef6f456 Fall back to JdkClientHttpConnector as ClientHttpConnector 2022-12-06 14:33:03 +01:00
rstoyanchev bea1980e22 Correct (again) permissions key in backport bot config 2022-12-06 09:41:05 +00:00
rstoyanchev f3360e4ee2 Correct permissions key in backport bot config 2022-12-06 09:38:13 +00:00
rstoyanchev 8330bd424b Add write permission in backport bot config 2022-12-06 09:34:07 +00:00
rstoyanchev 507c9697fb Correct event name in backport bot config 2022-12-06 09:27:03 +00:00
rstoyanchev 52c073724d Enable backport bot for pull requests 2022-12-06 09:13:29 +00:00
Sam Brannen de07ad47f1 Polishing, suppression of deprecation warnings, copyright headers, etc. 2022-12-05 14:16:22 -05:00
rstoyanchev 93ea2e1df9 Push canWrite down into MultipartHttpMessageWriter
The implementation in the base class only matches the
MultipartHttpMessageWriter subclass. The other two override it anyway.

Closes gh-29631
2022-12-05 15:37:48 +00:00
CoderYellow a7bf14b364 Fix canWrite of PartHttpMessageWriter
See gh-29631
2022-12-05 15:37:48 +00:00
rstoyanchev 7899fa3b0a Polishing contribution
Closes gh-29622
2022-12-05 15:37:48 +00:00
Marten Deinum c1da2a4e73 Improve Netty code sample
See gh-29622
2022-12-05 15:37:48 +00:00
rstoyanchev db53b618c1 Simplify form data handling in HttpRequestValues
Closes gh-29615
2022-12-05 15:37:48 +00:00
rstoyanchev 913163884a Improve invalid Content-Type handling in WebFlux
Closes gh-29565
2022-12-05 15:37:48 +00:00
Brian Clozel 4a555aaef1 Reduce heap memory usage in ConcurrentLruCache
Prior to this commit, the `ConcurrentLruCache` implementation would use
arrays of `AtomicReference` as operation buffers, and the buffer count
would be calculated with the nearest power of two for the CPU count.

This can result in significant heap memory usage as each
`AtomicReference` buffer entry adds to the memory pressure. As seen in
FasterXML/jackson-databind#3665, this can add a significant overhead for
no real added benefit for the current use case.

This commit changes the current implementation to use
`AtomicReferenceArray` as buffers and reduce the number of buffers.
JMH benchmarks results are within the error margin so we can assume that
this does not change the performance characteristics for the typical use
case in Spring Framework.

Fixes gh-29520
2022-12-05 13:02:11 +01:00
Brian Clozel e9ada8d15c Upgrade JMH build plugins
This commit upgrades the Gradle JMH plugin to 0.6.8 and the companion
JMH version to 1.36.
2022-12-05 13:02:11 +01:00
Sam Brannen 63d841664c Polish ref docs build 2022-12-04 22:35:04 -05:00
Sam Brannen 9779d25d98 Revise PDF ref docs to include TOC, authors, and legal sections 2022-12-04 22:13:20 -05:00
Sam Brannen 89d0c6b962 Do not refer to HTML ref docs from HTML ref docs 2022-12-04 22:11:41 -05:00
Sam Brannen 20da0835b3 Polish overview for consistency 2022-12-04 12:33:44 -05:00
Sam Brannen 4f232a9003 Update copyright headers 2022-12-03 18:24:44 -05:00
Sam Brannen c24a51323d Reinstate test for JmxUtils.locateMBeanServer() 2022-12-03 18:23:41 -05:00
Sam Brannen 69d022712b Polish Javadoc for ErrorResponse etc. 2022-12-03 16:19:33 -05:00
Sam Brannen aa1f3d1681 Apply "instanceof pattern matching" 2022-12-03 16:06:58 -05:00
Spark61 b366cd352e Fix ErrorResponse#type documentation
Closes gh-29632
2022-12-03 22:06:16 +01:00
Juergen Hoeller ac667a1e2b Upgrade to Apache HttpClient 5.2
Includes JRuby 9.4, Groovy 4.0.6, Hibernate ORM 5.6.14, HSQLDB 2.7.1, SLF4J 2.0.5, Caffeine 3.1.2, Gson 2.10, POI 5.2.3, Protobuf 3.21.10, WebJars Locator 0.52, HtmlUnit 2.67, Mockito 4.9, Checkstyle 10.5

Closes gh-29627
2022-12-02 20:57:35 +01:00
rstoyanchev a09f93768a Fix URI override for HttpExchange
Closes gh-29624
2022-12-02 17:02:05 +00:00
Brian Clozel 0ccd2f8b87 Deprecate GraphQL media type in favor of new one
This commit deprecates the `"application/graphql+json"` media type in
favor of the new `"application/graphql-response+json"`, since the former
has been removed in graphql/graphql-over-http#215.

Closes gh-29617
2022-12-02 16:25:59 +01:00
Baljit Singh b1fdb148d0 ResponseStatusException delegates to protected constructor
This ensures that by default the reason is used to set the "detail"
field. It's a follow-up fix to a27f2e994b
which resolved the issue partially.

Closes gh-29608
2022-12-02 14:20:16 +00:00
Juergen Hoeller 955ca4d146 Revised support for Jetty 12 (tested against 12.0.0.alpha2)
Avoids HttpFields optimization completely, relying on Servlet header access instead.
ServletServerHttpResponse provides applyHeaders/adaptHeaders split for better reuse.

See gh-29575
2022-12-01 19:45:40 +01:00
Juergen Hoeller d5732fed45 Polishing 2022-12-01 17:46:31 +01:00
Juergen Hoeller 4c7b04e32a Use resolved factory method return type for supplier code generation
Closes gh-29598
2022-12-01 17:46:23 +01:00
Johnny Lim 74ec10bf3a Polish
Closes gh-29619
2022-12-01 17:34:33 +01:00
Juergen Hoeller dd6cb1b728 Polishing 2022-12-01 15:48:24 +01:00
Juergen Hoeller 1e4c10cef1 Add equals/hashCode methods to ProblemDetail
Closes gh-29606
2022-12-01 15:47:40 +01:00
Juergen Hoeller e47978e168 Avoid unnecessary parameter name inspection for constructor-arg resolution
Closes gh-29612
2022-12-01 15:47:12 +01:00
Sam Brannen 52e967a525 Apply update_copyright_headers.sh to staged files as well 2022-11-30 15:18:11 +01:00
Sam Brannen 1de9d123a5 Update Javadoc for MBeanTestUtils 2022-11-30 15:15:51 +01:00
Sam Brannen ad60164911 Update copyright headers for source code changed since August 2022
The changes in this commit were performed using the newly introduced
update_copyright_headers.sh script.
2022-11-29 19:38:42 +01:00
Sam Brannen c49b0825f9 Introduce update_copyright_headers.sh shell script
In order to automate maintenance of copyright headers in our source
code (especially when merging PRs from external contributors), this
commit introduces an update_copyright_headers.sh script (tested on
mac OS) that will update the copyright headers of all Java, Kotlin,
and Groovy source files that have been added or modified this year
(or at least as far back as the git log history supports it).

For example, running this script currently outputs the following.

Updating copyright headers in Java, Kotlin, and Groovy source code for year 2022
warning: log for 'main' only goes back to Tue, 16 Aug 2022 16:24:55 +0200
2022-11-29 19:34:11 +01:00
Sam Brannen fdec9f0adc Apply "instanceof pattern matching" 2022-11-29 18:09:42 +01:00
Sam Brannen a88dbbec98 Apply project formatting rules for ternary operator
Discovered via RegEx: ^\s+\?
2022-11-29 17:51:13 +01:00
Sam Brannen 61d8774da0 Add missing callout 2022-11-29 13:54:46 +01:00
Sam Brannen c2b16f1ffb Fix code example callouts in reference manual 2022-11-29 13:54:10 +01:00
Sam Brannen 6af94dffe5 Add "missing" callout for parity 2022-11-29 13:52:26 +01:00
Sam Brannen 2147175898 Polishing 2022-11-29 13:51:34 +01:00
Sam Brannen 052542254a Polishing 2022-11-28 17:51:18 +01:00
Sam Brannen 1bb723b9e0 Rename to AbstractReactiveWebSocketIntegrationTests to avoid duplicate class names 2022-11-28 17:43:39 +01:00
Sam Brannen db02c96c55 Avoid deprecation warnings in tests 2022-11-28 17:40:46 +01:00
Sam Brannen 164141a63e Fix broken tests in SockJsServiceTests
See gh-29594
2022-11-28 16:52:15 +01:00
Sam Brannen 0a731413de Polishing 2022-11-28 16:45:38 +01:00
Sam Brannen 6e5dd64025 Polish contribution
See gh-29594
2022-11-28 16:42:22 +01:00
Aashay Chapatwala 650875468c Add title to SockJS iFrames for accessibility compliance
Closes gh-29594
2022-11-28 16:33:41 +01:00
Yanming Zhou 6d2a25b877 Fix typo in observability documentation
Closes gh-29590
2022-11-28 15:13:03 +01:00
Sam Brannen 2847621928 Fix errors in Testing chapter
- group code example callouts to ensure callouts are displayed for the
  correct examples

- add missing callouts

- fix syntax, annotation attribute names, etc.
2022-11-28 14:47:56 +01:00
Sam Brannen 3afc6a5079 Polish Testing chapter 2022-11-28 14:45:46 +01:00
rstoyanchev 6c8fb6c204 Add MessageSource getters
See gh-29574
2022-11-28 10:59:57 +00:00
Sam Brannen 8e9a553517 Update Javadoc regarding JUnit versions 2022-11-26 16:48:03 +01:00
Sam Brannen 537d539b1d Use JUnit Jupiter annotations as examples in TestContextManager JavaDoc 2022-11-26 16:17:01 +01:00
Sam Brannen 476b9cfc99 Polish TestContextManager internals 2022-11-26 16:17:01 +01:00
Sam Brannen 352087a637 Forbid loading of test ApplicationContext in AOT mode if AOT processing failed
Prior to this commit, if AOT processing of a test's ApplicationContext
failed, the TestContext Framework (TCF) still attempted to load the
context via standard (JVM) mechanisms when running in AOT mode.

For example, if a test class uses Spring Boot's @MockBean, AOT
processing of that test's context will fail with a WARN log message,
and there will no mapping from that test class to an AOT-generated
ApplicationContextInitializer (ACI). Consequently, when the test suite
is run in AOT mode that particular test class will currently fail with
a confusing stack trace due to the fact that Spring Boot's
SpringApplication attempts to locate a "main" ACI instead of the
missing "test" ACI (missing because it was not generated during AOT
processing).

In general, the TCF should not attempt to load an ApplicationContext in
"JVM mode" while running in "AOT mode".

This commit therefore reworks the logic in
DefaultCacheAwareContextLoaderDelegate to fail fast (with a meaningful
error message) if an AOT-generated ACI cannot be found while running in
AOT mode. This avoids the aforementioned confusion when @MockBean tests
fail in AOT mode (on the JVM or within a native image), and it also
helps to diagnose build problems if AOT processing has not yet been
performed for the project's test suite.

Closes gh-29579
2022-11-26 15:32:16 +01:00
Sam Brannen 33d33802a8 Improve logging in TestContextManager 2022-11-26 15:30:50 +01:00
Sam Brannen 9ec937c843 Remove erroneous Javadoc link 2022-11-26 14:40:49 +01:00
Juergen Hoeller 8e5eb84da1 Consistently register CGLIB hints for lazy resolution proxy classes
Core JDK/CGLIB proxy registration code extracted to ClassHintUtils.

Closes gh-29584
2022-11-26 13:38:34 +01:00
Sam Brannen 902f8dd1d8 Upgrade Gradle wrapper
See gh-29583
2022-11-25 23:12:08 +01:00
Juergen Hoeller 22f8ea61fb Upgrade to Gradle 7.6
Closes gh-29583
2022-11-25 22:33:09 +01:00
Brian Clozel 8ce1f14554 Document Observability support in reference docs
Closes gh-29524
2022-11-25 17:33:11 +01:00
Brian Clozel 9a3cb628af Split integration chapter in smaller documents
This commit splits the integration chapter of the reference
documentation in smaller documents for easier maintenance.
2022-11-25 17:33:07 +01:00
Brian Clozel b0a4c5d72c Fix unrendered titles in websocket section
This commit fixes the rendering of titles in the websocket section of
the reference documentation.

Fixes gh-29569
2022-11-25 17:33:03 +01:00
Brian Clozel 8b110c9971 Reinstate checkstyle for reference docs code snippets
This commit also ensures that checks are performed before the
application is rendered to get early feedback on errors.
2022-11-25 17:32:59 +01:00
Juergen Hoeller 21d3a7529c Consistent documentation references to Jakarta WebSocket (2.1)
Closes gh-29581
2022-11-25 17:07:30 +01:00
Juergen Hoeller 9c7b471634 Deprecate JettyWebSocketClient in favor of StandardWebSocketClient
JettyWebSocketClient only supported on Jetty 11, to be phased out.

Closes gh-29576
2022-11-25 17:06:15 +01:00
Juergen Hoeller 45d45c2989 Early support for Jetty 12 (developed against 12.0.0.alpha2)
Reflective getHeaders calls to be revisited; see GitHub issue #8938 in Jetty project.
HttpOutput optimization commented out still in order to avoid alpha build dependency.

See gh-29575
2022-11-25 17:04:48 +01:00
Spring Builds 4ff18741fd Next development version (v6.0.3-SNAPSHOT) 2022-11-24 13:50:54 +00:00
808 changed files with 11885 additions and 10460 deletions
+3
View File
@@ -3,6 +3,8 @@ name: Backport Bot
on:
issues:
types: [labeled]
pull_request:
types: [labeled]
push:
branches:
- '*.x'
@@ -13,6 +15,7 @@ jobs:
permissions:
contents: read
issues: write
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
+2 -2
View File
@@ -10,7 +10,7 @@ plugins {
id 'com.github.ben-manes.versions' version '0.42.0'
id 'com.github.johnrengelman.shadow' version '7.1.2' apply false
id 'de.undercouch.download' version '5.1.0'
id 'me.champeau.jmh' version '0.6.6' apply false
id 'me.champeau.jmh' version '0.6.8' apply false
}
ext {
@@ -78,7 +78,7 @@ configure([rootProject] + javaProjects) { project ->
}
checkstyle {
toolVersion = "10.4"
toolVersion = "10.5.0"
configDirectory.set(rootProject.file("src/checkstyle"))
}
+12 -11
View File
@@ -13,15 +13,12 @@ configurations {
dependencies {
api(project(":spring-context"))
api(project(":spring-web"))
api("jakarta.servlet:jakarta.servlet-api")
implementation(project(":spring-core-test"))
implementation("org.assertj:assertj-core")
}
checkstyle {
sourceSets = []
}
jar {
enabled = false
}
@@ -116,18 +113,23 @@ asciidoctor {
sources {
include '*.adoc'
}
outputDir "$buildDir/docs/ref-docs/html5"
outputOptions {
backends "spring-html"
}
logDocuments = true
resources {
from(sourceDir) {
include 'images/*.png'
}
}
outputDir "$buildDir/docs/ref-docs/html5"
outputOptions {
backends "spring-html"
}
forkOptions {
jvmArgs += ["--add-opens", "java.base/sun.nio.ch=ALL-UNNAMED", "--add-opens", "java.base/java.io=ALL-UNNAMED"]
}
logDocuments = true
}
asciidoctor.mustRunAfter "check"
/**
* Generate the Spring Framework Reference documentation from "src/docs/asciidoc"
* in "build/docs/ref-docs/pdf".
@@ -247,7 +249,6 @@ task distZip(type: Zip, dependsOn: [docsZip, schemaZip]) {
distZip.mustRunAfter moduleProjects.check
publishing {
publications {
mavenJava(MavenPublication) {
@@ -256,4 +257,4 @@ publishing {
artifact distZip
}
}
}
}
@@ -3480,6 +3480,7 @@ configure a `DataSource` in your Spring configuration file and then dependency-i
that shared `DataSource` bean into your DAO classes. The `JdbcTemplate` is created in
the setter for the `DataSource`. This leads to DAOs that resemble the following:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -3504,6 +3505,7 @@ the setter for the `DataSource`. This leads to DAOs that resemble the following:
// JDBC-backed implementations of the methods on the CorporateEventDao follow...
}
----
--
The following example shows the corresponding XML configuration:
@@ -3540,6 +3542,7 @@ support for dependency injection. In this case, you can annotate the class with
(which makes it a candidate for component-scanning) and annotate the `DataSource` setter
method with `@Autowired`. The following example shows how to do so:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -3574,6 +3577,7 @@ method with `@Autowired`. The following example shows how to do so:
<1> Annotate the class with `@Repository`.
<2> Constructor injection of the `DataSource`.
<3> Create a new `JdbcTemplate` with the `DataSource`.
--
The following example shows the corresponding XML configuration:
@@ -5484,8 +5488,7 @@ example shows such a method:
.Java
----
public List<Actor> searchForActors(int age, String namePattern) {
List<Actor> actors = actorSearchMappingQuery.execute(age, namePattern);
return actors;
return actorSearchMappingQuery.execute(age, namePattern);
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
@@ -7078,6 +7081,7 @@ in your Spring configuration file and then dependency-inject
that shared `ConnectionFactory` bean into your DAO classes. The `DatabaseClient` is created in
the setter for the `ConnectionFactory`. This leads to DAOs that resemble the following:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -7102,12 +7106,14 @@ the setter for the `ConnectionFactory`. This leads to DAOs that resemble the fol
// R2DBC-backed implementations of the methods on the CorporateEventDao follow...
}
----
--
An alternative to explicit configuration is to use component-scanning and annotation
support for dependency injection. In this case, you can annotate the class with `@Component`
(which makes it a candidate for component-scanning) and annotate the `ConnectionFactory` setter
method with `@Autowired`. The following example shows how to do so:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -7142,6 +7148,7 @@ method with `@Autowired`. The following example shows how to do so:
<1> Annotate the class with `@Component`.
<2> Constructor injection of the `ConnectionFactory`.
<3> Create a new `DatabaseClient` with the `ConnectionFactory`.
--
Regardless of which of the above template initialization styles you choose to use (or
not), it is seldom necessary to create a new instance of a `DatabaseClient` class each
@@ -8866,9 +8873,10 @@ preamble of the XML configuration file. The following example shows how to do so
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:oxm="http://www.springframework.org/schema/oxm" <1>
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/oxm https://www.springframework.org/schema/oxm/spring-oxm.xsd"> <2>
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/oxm
https://www.springframework.org/schema/oxm/spring-oxm.xsd"> <2>
----
<1> Reference the `oxm` schema.
<2> Specify the `oxm` schema location.
@@ -38,12 +38,15 @@ are available to you:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" <1>
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd <2>
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd <2>
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- bean definitions here -->
@@ -79,8 +82,10 @@ the correct schema so that the elements in the `jdbc` namespace are available to
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc" <1>
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jdbc https://www.springframework.org/schema/jdbc/spring-jdbc.xsd"> <2>
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jdbc
https://www.springframework.org/schema/jdbc/spring-jdbc.xsd"> <2>
<!-- bean definitions here -->
+6 -6
View File
@@ -3,8 +3,8 @@
include::attributes.adoc[]
[horizontal]
<<overview.adoc#overview, Overview>> :: history, design philosophy, feedback,
getting started.
<<overview.adoc#overview, Overview>> :: History, Design Philosophy, Feedback,
Getting Started.
<<core.adoc#spring-core, Core>> :: IoC Container, Events, Resources, i18n,
Validation, Data Binding, Type Conversion, SpEL, AOP, AOT.
<<testing.adoc#testing, Testing>> :: Mock Objects, TestContext Framework,
@@ -16,13 +16,13 @@ STOMP Messaging.
<<web-reactive.adoc#spring-webflux, Web Reactive>> :: Spring WebFlux, WebClient,
WebSocket, RSocket.
<<integration.adoc#spring-integration, Integration>> :: REST Clients, JMS, JCA, JMX,
Email, Tasks, Scheduling, Caching.
Email, Tasks, Scheduling, Caching, Observability.
<<languages.adoc#languages, Languages>> :: Kotlin, Groovy, Dynamic Languages.
<<appendix.adoc#appendix, Appendix>> :: Spring properties.
https://github.com/spring-projects/spring-framework/wiki[*Wiki*] :: What's New,
Upgrade Notes, Supported Versions, and other cross-version information.
https://github.com/spring-projects/spring-framework/wiki[Wiki] :: What's New,
Upgrade Notes, Supported Versions, additional cross-version information.
NOTE: This documentation is available in {docs-spring-framework}/reference/html/index.html[HTML] and {docs-spring-framework}/reference/pdf/spring-framework.pdf[PDF] formats.
NOTE: This documentation is also available in {docs-spring-framework}/reference/pdf/spring-framework.pdf[PDF] format.
Rod Johnson, Juergen Hoeller, Keith Donald, Colin Sampaleanu, Rob Harrop, Thomas Risberg,
Alef Arendsen, Darren Davison, Dmitriy Kopylenko, Mark Pollack, Thierry Templier, Erwin
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,302 @@
[[mail]]
= Email
This section describes how to send email with the Spring Framework.
.Library dependencies
****
The following JAR needs to be on the classpath of your application in order to use
the Spring Framework's email library:
* The https://eclipse-ee4j.github.io/mail/[JavaMail / Jakarta Mail 1.6] library
This library is freely available on the web -- for example, in Maven Central as
`com.sun.mail:jakarta.mail`. Please make sure to use the latest 1.6.x version
rather than Jakarta Mail 2.0 (which comes with a different package namespace).
****
The Spring Framework provides a helpful utility library for sending email that shields
you from the specifics of the underlying mailing system and is responsible for
low-level resource handling on behalf of the client.
The `org.springframework.mail` package is the root level package for the Spring
Framework's email support. The central interface for sending emails is the `MailSender`
interface. A simple value object that encapsulates the properties of a simple mail such
as `from` and `to` (plus many others) is the `SimpleMailMessage` class. This package
also contains a hierarchy of checked exceptions that provide a higher level of
abstraction over the lower level mail system exceptions, with the root exception being
`MailException`. See the {api-spring-framework}/mail/MailException.html[javadoc]
for more information on the rich mail exception hierarchy.
The `org.springframework.mail.javamail.JavaMailSender` interface adds specialized
JavaMail features, such as MIME message support to the `MailSender` interface
(from which it inherits). `JavaMailSender` also provides a callback interface called
`org.springframework.mail.javamail.MimeMessagePreparator` for preparing a `MimeMessage`.
[[mail-usage]]
== Usage
Assume that we have a business interface called `OrderManager`, as the following example shows:
[source,java,indent=0,subs="verbatim,quotes"]
----
public interface OrderManager {
void placeOrder(Order order);
}
----
Further assume that we have a requirement stating that an email message with an
order number needs to be generated and sent to a customer who placed the relevant order.
[[mail-usage-simple]]
=== Basic `MailSender` and `SimpleMailMessage` Usage
The following example shows how to use `MailSender` and `SimpleMailMessage` to send an
email when someone places an order:
[source,java,indent=0,subs="verbatim,quotes"]
----
import org.springframework.mail.MailException;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;
public class SimpleOrderManager implements OrderManager {
private MailSender mailSender;
private SimpleMailMessage templateMessage;
public void setMailSender(MailSender mailSender) {
this.mailSender = mailSender;
}
public void setTemplateMessage(SimpleMailMessage templateMessage) {
this.templateMessage = templateMessage;
}
public void placeOrder(Order order) {
// Do the business calculations...
// Call the collaborators to persist the order...
// 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(
"Dear " + order.getCustomer().getFirstName()
+ order.getCustomer().getLastName()
+ ", thank you for placing order. Your order number is "
+ order.getOrderNumber());
try {
this.mailSender.send(msg);
}
catch (MailException ex) {
// simply log it and go on...
System.err.println(ex.getMessage());
}
}
}
----
The following example shows the bean definitions for the preceding code:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
<property name="host" value="mail.mycompany.example"/>
</bean>
<!-- this is a template message that we can pre-load with default state -->
<bean id="templateMessage" class="org.springframework.mail.SimpleMailMessage">
<property name="from" value="customerservice@mycompany.example"/>
<property name="subject" value="Your order"/>
</bean>
<bean id="orderManager" class="com.mycompany.businessapp.support.SimpleOrderManager">
<property name="mailSender" ref="mailSender"/>
<property name="templateMessage" ref="templateMessage"/>
</bean>
----
[[mail-usage-mime]]
=== Using `JavaMailSender` and `MimeMessagePreparator`
This section describes another implementation of `OrderManager` that uses the `MimeMessagePreparator`
callback interface. In the following example, the `mailSender` property is of type
`JavaMailSender` so that we are able to use the JavaMail `MimeMessage` class:
[source,java,indent=0,subs="verbatim,quotes"]
----
import jakarta.mail.Message;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeMessage;
import jakarta.mail.internet.MimeMessage;
import org.springframework.mail.MailException;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessagePreparator;
public class SimpleOrderManager implements OrderManager {
private JavaMailSender mailSender;
public void setMailSender(JavaMailSender mailSender) {
this.mailSender = mailSender;
}
public void placeOrder(final Order order) {
// Do the business calculations...
// Call the collaborators to persist the order...
MimeMessagePreparator preparator = new MimeMessagePreparator() {
public void prepare(MimeMessage mimeMessage) throws Exception {
mimeMessage.setRecipient(Message.RecipientType.TO,
new InternetAddress(order.getCustomer().getEmailAddress()));
mimeMessage.setFrom(new InternetAddress("mail@mycompany.example"));
mimeMessage.setText("Dear " + order.getCustomer().getFirstName() + " " +
order.getCustomer().getLastName() + ", thanks for your order. " +
"Your order number is " + order.getOrderNumber() + ".");
}
};
try {
this.mailSender.send(preparator);
}
catch (MailException ex) {
// simply log it and go on...
System.err.println(ex.getMessage());
}
}
}
----
NOTE: The mail code is a crosscutting concern and could well be a candidate for
refactoring into a <<core.adoc#aop, custom Spring AOP aspect>>, which could then
be run at appropriate joinpoints on the `OrderManager` target.
The Spring Framework's mail support ships with the standard JavaMail implementation.
See the relevant javadoc for more information.
[[mail-javamail-mime]]
== Using the JavaMail `MimeMessageHelper`
A class that comes in pretty handy when dealing with JavaMail messages is
`org.springframework.mail.javamail.MimeMessageHelper`, which shields you from
having to use the verbose JavaMail API. Using the `MimeMessageHelper`, it is
pretty easy to create a `MimeMessage`, as the following example shows:
[source,java,indent=0,subs="verbatim,quotes"]
----
// of course you would use DI in any real-world cases
JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setHost("mail.host.com");
MimeMessage message = sender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message);
helper.setTo("test@host.com");
helper.setText("Thank you for ordering!");
sender.send(message);
----
[[mail-javamail-mime-attachments]]
=== Sending Attachments and Inline Resources
Multipart email messages allow for both attachments and inline resources. Examples of
inline resources include an image or a stylesheet that you want to use in your message but
that you do not want displayed as an attachment.
[[mail-javamail-mime-attachments-attachment]]
==== Attachments
The following example shows you how to use the `MimeMessageHelper` to send an email
with a single JPEG image attachment:
[source,java,indent=0,subs="verbatim,quotes"]
----
JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setHost("mail.host.com");
MimeMessage message = sender.createMimeMessage();
// use the true flag to indicate you need a multipart message
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setTo("test@host.com");
helper.setText("Check out this image!");
// let's attach the infamous windows Sample file (this time copied to c:/)
FileSystemResource file = new FileSystemResource(new File("c:/Sample.jpg"));
helper.addAttachment("CoolImage.jpg", file);
sender.send(message);
----
[[mail-javamail-mime-attachments-inline]]
==== Inline Resources
The following example shows you how to use the `MimeMessageHelper` to send an email
with an inline image:
[source,java,indent=0,subs="verbatim,quotes"]
----
JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setHost("mail.host.com");
MimeMessage message = sender.createMimeMessage();
// use the true flag to indicate you need a multipart message
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setTo("test@host.com");
// use the true flag to indicate the text included is HTML
helper.setText("<html><body><img src='cid:identifier1234'></body></html>", true);
// let's include the infamous windows Sample file (this time copied to c:/)
FileSystemResource res = new FileSystemResource(new File("c:/Sample.jpg"));
helper.addInline("identifier1234", res);
sender.send(message);
----
WARNING: Inline resources are added to the `MimeMessage` by using the specified `Content-ID`
(`identifier1234` in the above example). The order in which you add the text
and the resource are very important. Be sure to first add the text and then
the resources. If you are doing it the other way around, it does not work.
[[mail-templates]]
=== Creating Email Content by Using a Templating Library
The code in the examples shown in the previous sections explicitly created the content of the email message,
by using methods calls such as `message.setText(..)`. This is fine for simple cases, and it
is okay in the context of the aforementioned examples, where the intent was to show you
the very basics of the API.
In your typical enterprise application, though, developers often do not create the content
of email messages by using the previously shown approach for a number of reasons:
* Creating HTML-based email content in Java code is tedious and error prone.
* There is no clear separation between display logic and business logic.
* Changing the display structure of the email content requires writing Java code,
recompiling, redeploying, and so on.
Typically, the approach taken to address these issues is to use a template library (such
as FreeMarker) to define the display structure of email content. This leaves your code
tasked only with creating the data that is to be rendered in the email template and
sending the email. It is definitely a best practice when the content of your email messages
becomes even moderately complex, and, with the Spring Framework's support classes for
FreeMarker, it becomes quite easy to do.
@@ -28,8 +28,10 @@ correct schema so that the elements in the `jee` namespace are available to you:
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jee https://www.springframework.org/schema/jee/spring-jee.xsd">
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jee
https://www.springframework.org/schema/jee/spring-jee.xsd">
<!-- bean definitions here -->
@@ -287,8 +289,10 @@ are available to you:
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jms="http://www.springframework.org/schema/jms"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jms https://www.springframework.org/schema/jms/spring-jms.xsd">
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jms
https://www.springframework.org/schema/jms/spring-jms.xsd">
<!-- bean definitions here -->
@@ -324,8 +328,10 @@ the correct schema so that the elements in the `cache` namespace are available t
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/cache https://www.springframework.org/schema/cache/spring-cache.xsd">
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/cache
https://www.springframework.org/schema/cache/spring-cache.xsd">
<!-- bean definitions here -->
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,175 @@
[[integration.observability]]
= Observability Support
Micrometer defines an https://micrometer.io/docs/observation[Observation concept that enables both Metrics and Traces] in applications.
Metrics support offers a way to create timers, gauges or counters for collecting statistics about the runtime behavior of your application.
Metrics can help you to track error rates, usage patterns, performance and more.
Traces provide a holistic view of an entire system, crossing application boundaries; you can zoom in on particular user requests and follow their entire completion across applications.
Spring Framework instruments various parts of its own codebase to publish observations if an `ObservationRegistry` is configured.
You can learn more about {docs-spring-boot}/html/actuator.html#actuator.metrics[configuring the observability infrastructure in Spring Boot].
[[integration.observability.concepts]]
== Micrometer Observation concepts
If you are not familiar with Micrometer Observation, here's a quick summary of the new concepts you should know about.
* `Observation` is the actual recording of something happening in your application. This is processed by `ObservationHandler` implementations to produce metrics or traces.
* Each observation has a corresponding `ObservationContext` implementation; this type holds all the relevant information for extracting metadata for it.
In the case of an HTTP server observation, the context implementation could hold the HTTP request, the HTTP response, any Exception thrown during processing...
* Each `Observation` holds `KeyValues` metadata. In the case of a server HTTP observation, this could be the HTTP request method, the HTTP response status...
This metadata is contributed by `ObservationConvention` implementations which should declare the type of `ObservationContext` they support.
* `KeyValues` are said to be "low cardinality" if there is a low, bounded number of possible values for the `KeyValue` tuple (HTTP method is a good example).
Low cardinality values are contributed to metrics only.
High cardinality values are on the other hand unbounded (for example, HTTP request URIs) and are only contributed to Traces.
* An `ObservationDocumentation` documents all observations in a particular domain, listing the expected key names and their meaning.
[[integration.observability.config]]
== Configuring Observations
Global configuration options are available at the `ObservationRegistry#observationConfig()` level.
Each instrumented component will provide two extension points:
* setting the `ObservationRegistry`; if not set, observations will not be recorded and will be no-ops
* providing a custom `ObservationConvention` to change the default observation name and extracted `KeyValues`
[[integration.observability.config.conventions]]
=== Using custom Observation conventions
Let's take the example of the Spring MVC "http.server.requests" metrics instrumentation with the `ServerHttpObservationFilter`.
This observation is using a `ServerRequestObservationConvention` with a `ServerRequestObservationContext`; custom conventions can be configured on the Servlet filter.
If you would like to customize the metadata produced with the observation, you can extend the `DefaultServerRequestObservationConvention` for your requirements:
include::code:ExtendedServerRequestObservationConvention[]
If you want full control, you can then implement the entire convention contract for the observation you're interested in:
include::code:CustomServerRequestObservationConvention[]
You can also achieve similar goals using a custom `ObservationFilter` - adding or removing key values for an observation.
Filters do not replace the default convention and are used as a post-processing component.
include::code:ServerRequestObservationFilter[]
You can configure `ObservationFilter` instances on the `ObservationRegistry`.
[[integration.observability.http-server]]
== HTTP Server instrumentation
HTTP server exchanges observations are created with the name `"http.server.requests"` for Servlet and Reactive applications.
[[integration.observability.http-server.servlet]]
=== Servlet applications
Applications need to configure the `org.springframework.web.filter.ServerHttpObservationFilter` Servlet filter in their application.
It is using the `org.springframework.http.server.observation.DefaultServerRequestObservationConvention` by default, backed by the `ServerRequestObservationContext`.
By default, the following `KeyValues` are created:
.Low cardinality Keys
[cols="a,a"]
|===
|Name | Description
|`exception` _(required)_|Name of the exception thrown during the exchange, or `KeyValue#NONE_VALUE`} if no exception happened.
|`method` _(required)_|Name of HTTP request method or `"none"` if the request was not received properly.
|`outcome` _(required)_|Outcome of the HTTP server exchange.
|`status` _(required)_|HTTP response raw status code, or `"UNKNOWN"` if no response was created.
|`uri` _(required)_|URI pattern for the matching handler if available, falling back to `REDIRECTION` for 3xx responses, `NOT_FOUND` for 404 responses, `root` for requests with no path info, and `UNKNOWN` for all other requests.
|===
.High cardinality Keys
[cols="a,a"]
|===
|Name | Description
|`http.url` _(required)_|HTTP request URI.
|===
[[integration.observability.http-server.reactive]]
=== Reactive applications
Applications need to configure the `org.springframework.web.filter.reactive.ServerHttpObservationFilter` reactive `WebFilter` in their application.
It is using the `org.springframework.http.server.reactive.observation.DefaultServerRequestObservationConvention` by default, backed by the `ServerRequestObservationContext`.
By default, the following `KeyValues` are created:
.Low cardinality Keys
[cols="a,a"]
|===
|Name | Description
|`exception` _(required)_|Name of the exception thrown during the exchange, or `"none"` if no exception happened.
|`method` _(required)_|Name of HTTP request method or `"none"` if the request was not received properly.
|`outcome` _(required)_|Outcome of the HTTP server exchange.
|`status` _(required)_|HTTP response raw status code, or `"UNKNOWN"` if no response was created.
|`uri` _(required)_|URI pattern for the matching handler if available, falling back to `REDIRECTION` for 3xx responses, `NOT_FOUND` for 404 responses, `root` for requests with no path info, and `UNKNOWN` for all other requests.
|===
.High cardinality Keys
[cols="a,a"]
|===
|Name | Description
|`http.url` _(required)_|HTTP request URI.
|===
[[integration.observability.http-client]]
== HTTP Client instrumentation
HTTP client exchanges observations are created with the name `"http.client.requests"` for blocking and reactive clients.
Unlike their server counterparts, the instrumentation is implemented directly in the client so the only required step is to configure an `ObservationRegistry` on the client.
[[integration.observability.http-client.resttemplate]]
=== RestTemplate
Instrumentation is using the `org.springframework.http.client.observation.ClientRequestObservationConvention` by default, backed by the `ClientRequestObservationContext`.
.Low cardinality Keys
[cols="a,a"]
|===
|Name | Description
|`exception` _(required)_|Name of the exception thrown during the exchange, or `"none"` if no exception happened.
|`method` _(required)_|Name of HTTP request method or `"none"` if the request could not be created.
|`outcome` _(required)_|Outcome of the HTTP client exchange.
|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received.
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided.
|===
.High cardinality Keys
[cols="a,a"]
|===
|Name | Description
|`client.name` _(required)_|Client name derived from the request URI host.
|`http.url` _(required)_|HTTP request URI.
|===
[[integration.observability.http-client.webclient]]
=== WebClient
Instrumentation is using the `org.springframework.web.reactive.function.client.ClientRequestObservationConvention` by default, backed by the `ClientRequestObservationContext`.
.Low cardinality Keys
[cols="a,a"]
|===
|Name | Description
|`exception` _(required)_|Name of the exception thrown during the exchange, or `"none"` if no exception happened.
|`method` _(required)_|Name of HTTP request method or `"none"` if the request could not be created.
|`outcome` _(required)_|Outcome of the HTTP client exchange.
|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received.
|`uri` _(required)_|URI template used for HTTP request, or `"none"` if none was provided.
|===
.High cardinality Keys
[cols="a,a"]
|===
|Name | Description
|`client.name` _(required)_|Client name derived from the request URI host.
|`http.url` _(required)_|HTTP request URI.
|===
@@ -0,0 +1,517 @@
[[rest-client-access]]
= REST Clients
The Spring Framework provides the following choices for making calls to REST endpoints:
* <<rest-webclient>> - non-blocking, reactive client w fluent API.
* <<rest-resttemplate>> - synchronous client with template method API.
* <<rest-http-interface>> - annotated interface with generated, dynamic proxy implementation.
[[rest-webclient]]
== `WebClient`
`WebClient` is a non-blocking, reactive client to perform HTTP requests. It was
introduced in 5.0 and offers an alternative to the `RestTemplate`, with support for
synchronous, asynchronous, and streaming scenarios.
`WebClient` supports the following:
* Non-blocking I/O.
* Reactive Streams back pressure.
* High concurrency with fewer hardware resources.
* Functional-style, fluent API that takes advantage of Java 8 lambdas.
* Synchronous and asynchronous interactions.
* Streaming up to or streaming down from a server.
See <<web-reactive.adoc#webflux-client, WebClient>> for more details.
[[rest-resttemplate]]
== `RestTemplate`
The `RestTemplate` provides a higher level API over HTTP client libraries. It makes it
easy to invoke REST endpoints in a single line. It exposes the following groups of
overloaded methods:
NOTE: `RestTemplate` is in maintenance mode, with only requests for minor
changes and bugs to be accepted. Please, consider using the
<<web-reactive.adoc#webflux-client, WebClient>> instead.
[[rest-overview-of-resttemplate-methods-tbl]]
.RestTemplate methods
[cols="1,3"]
|===
| Method group | Description
| `getForObject`
| Retrieves a representation via GET.
| `getForEntity`
| Retrieves a `ResponseEntity` (that is, status, headers, and body) by using GET.
| `headForHeaders`
| Retrieves all headers for a resource by using HEAD.
| `postForLocation`
| Creates a new resource by using POST and returns the `Location` header from the response.
| `postForObject`
| Creates a new resource by using POST and returns the representation from the response.
| `postForEntity`
| Creates a new resource by using POST and returns the representation from the response.
| `put`
| Creates or updates a resource by using PUT.
| `patchForObject`
| Updates a resource by using PATCH and returns the representation from the response.
Note that the JDK `HttpURLConnection` does not support `PATCH`, but Apache
HttpComponents and others do.
| `delete`
| Deletes the resources at the specified URI by using DELETE.
| `optionsForAllow`
| Retrieves allowed HTTP methods for a resource by using ALLOW.
| `exchange`
| More generalized (and less opinionated) version of the preceding methods that provides extra
flexibility when needed. It accepts a `RequestEntity` (including HTTP method, URL, headers,
and body as input) and returns a `ResponseEntity`.
These methods allow the use of `ParameterizedTypeReference` instead of `Class` to specify
a response type with generics.
| `execute`
| The most generalized way to perform a request, with full control over request
preparation and response extraction through callback interfaces.
|===
[[rest-resttemplate-create]]
=== Initialization
The default constructor uses `java.net.HttpURLConnection` to perform requests. You can
switch to a different HTTP library with an implementation of `ClientHttpRequestFactory`.
There is built-in support for the following:
* Apache HttpComponents
* Netty
* OkHttp
For example, to switch to Apache HttpComponents, you can use the following:
[source,java,indent=0,subs="verbatim,quotes"]
----
RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
----
Each `ClientHttpRequestFactory` exposes configuration options specific to the underlying
HTTP client library -- for example, for credentials, connection pooling, and other details.
TIP: Note that the `java.net` implementation for HTTP requests can raise an exception when
accessing the status of a response that represents an error (such as 401). If this is an
issue, switch to another HTTP client library.
[[rest-resttemplate-uri]]
==== URIs
Many of the `RestTemplate` methods accept a URI template and URI template variables,
either as a `String` variable argument, or as `Map<String,String>`.
The following example uses a `String` variable argument:
[source,java,indent=0,subs="verbatim,quotes"]
----
String result = restTemplate.getForObject(
"https://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42", "21");
----
The following example uses a `Map<String, String>`:
[source,java,indent=0,subs="verbatim,quotes"]
----
Map<String, String> vars = Collections.singletonMap("hotel", "42");
String result = restTemplate.getForObject(
"https://example.com/hotels/{hotel}/rooms/{hotel}", String.class, vars);
----
Keep in mind URI templates are automatically encoded, as the following example shows:
[source,java,indent=0,subs="verbatim,quotes"]
----
restTemplate.getForObject("https://example.com/hotel list", String.class);
// Results in request to "https://example.com/hotel%20list"
----
You can use the `uriTemplateHandler` property of `RestTemplate` to customize how URIs
are encoded. Alternatively, you can prepare a `java.net.URI` and pass it into one of
the `RestTemplate` methods that accepts a `URI`.
For more details on working with and encoding URIs, see <<web.adoc#mvc-uri-building, URI Links>>.
[[rest-template-headers]]
==== Headers
You can use the `exchange()` methods to specify request headers, as the following example shows:
[source,java,indent=0,subs="verbatim,quotes"]
----
String uriTemplate = "https://example.com/hotels/{hotel}";
URI uri = UriComponentsBuilder.fromUriString(uriTemplate).build(42);
RequestEntity<Void> requestEntity = RequestEntity.get(uri)
.header("MyRequestHeader", "MyValue")
.build();
ResponseEntity<String> response = template.exchange(requestEntity, String.class);
String responseHeader = response.getHeaders().getFirst("MyResponseHeader");
String body = response.getBody();
----
You can obtain response headers through many `RestTemplate` method variants that return
`ResponseEntity`.
[[rest-template-body]]
=== Body
Objects passed into and returned from `RestTemplate` methods are converted to and from raw
content with the help of an `HttpMessageConverter`.
On a POST, an input object is serialized to the request body, as the following example shows:
----
URI location = template.postForLocation("https://example.com/people", person);
----
You need not explicitly set the Content-Type header of the request. In most cases,
you can find a compatible message converter based on the source `Object` type, and the chosen
message converter sets the content type accordingly. If necessary, you can use the
`exchange` methods to explicitly provide the `Content-Type` request header, and that, in
turn, influences what message converter is selected.
On a GET, the body of the response is deserialized to an output `Object`, as the following example shows:
----
Person person = restTemplate.getForObject("https://example.com/people/{id}", Person.class, 42);
----
The `Accept` header of the request does not need to be explicitly set. In most cases,
a compatible message converter can be found based on the expected response type, which
then helps to populate the `Accept` header. If necessary, you can use the `exchange`
methods to provide the `Accept` header explicitly.
By default, `RestTemplate` registers all built-in
<<rest-message-conversion, message converters>>, depending on classpath checks that help
to determine what optional conversion libraries are present. You can also set the message
converters to use explicitly.
[[rest-message-conversion]]
==== Message Conversion
[.small]#<<web-reactive.adoc#webflux-codecs, WebFlux>>#
The `spring-web` module contains the `HttpMessageConverter` contract for reading and
writing the body of HTTP requests and responses through `InputStream` and `OutputStream`.
`HttpMessageConverter` instances are used on the client side (for example, in the `RestTemplate`) and
on the server side (for example, in Spring MVC REST controllers).
Concrete implementations for the main media (MIME) types are provided in the framework
and are, by default, registered with the `RestTemplate` on the client side and with
`RequestMappingHandlerAdapter` on the server side (see
<<web.adoc#mvc-config-message-converters, Configuring Message Converters>>).
The implementations of `HttpMessageConverter` are described in the following sections.
For all converters, a default media type is used, but you can override it by setting the
`supportedMediaTypes` bean property. The following table describes each implementation:
[[rest-message-converters-tbl]]
.HttpMessageConverter Implementations
[cols="1,3"]
|===
| MessageConverter | Description
| `StringHttpMessageConverter`
| An `HttpMessageConverter` implementation that can read and write `String` instances from the HTTP
request and response. By default, this converter supports all text media types
(`text/{asterisk}`) and writes with a `Content-Type` of `text/plain`.
| `FormHttpMessageConverter`
| An `HttpMessageConverter` implementation that can read and write form data from the HTTP
request and response. By default, this converter reads and writes the
`application/x-www-form-urlencoded` media type. Form data is read from and written into a
`MultiValueMap<String, String>`. The converter can also write (but not read) multipart
data read from a `MultiValueMap<String, Object>`. By default, `multipart/form-data` is
supported. As of Spring Framework 5.2, additional multipart subtypes can be supported for
writing form data. Consult the javadoc for `FormHttpMessageConverter` for further details.
| `ByteArrayHttpMessageConverter`
| An `HttpMessageConverter` implementation that can read and write byte arrays from the
HTTP request and response. By default, this converter supports all media types (`{asterisk}/{asterisk}`)
and writes with a `Content-Type` of `application/octet-stream`. You can override this
by setting the `supportedMediaTypes` property and overriding `getContentType(byte[])`.
| `MarshallingHttpMessageConverter`
| An `HttpMessageConverter` implementation that can read and write XML by using Spring's
`Marshaller` and `Unmarshaller` abstractions from the `org.springframework.oxm` package.
This converter requires a `Marshaller` and `Unmarshaller` before it can be used. You can inject these
through constructor or bean properties. By default, this converter supports
`text/xml` and `application/xml`.
| `MappingJackson2HttpMessageConverter`
| An `HttpMessageConverter` implementation that can read and write JSON by using Jackson's
`ObjectMapper`. You can customize JSON mapping as needed through the use of Jackson's
provided annotations. When you need further control (for cases where custom JSON
serializers/deserializers need to be provided for specific types), you can inject a custom `ObjectMapper`
through the `ObjectMapper` property. By default, this
converter supports `application/json`.
| `MappingJackson2XmlHttpMessageConverter`
| An `HttpMessageConverter` implementation that can read and write XML by using
https://github.com/FasterXML/jackson-dataformat-xml[Jackson XML] extension's
`XmlMapper`. You can customize XML mapping as needed through the use of JAXB
or Jackson's provided annotations. When you need further control (for cases where custom XML
serializers/deserializers need to be provided for specific types), you can inject a custom `XmlMapper`
through the `ObjectMapper` property. By default, this
converter supports `application/xml`.
| `SourceHttpMessageConverter`
| An `HttpMessageConverter` implementation that can read and write
`javax.xml.transform.Source` from the HTTP request and response. Only `DOMSource`,
`SAXSource`, and `StreamSource` are supported. By default, this converter supports
`text/xml` and `application/xml`.
| `BufferedImageHttpMessageConverter`
| An `HttpMessageConverter` implementation that can read and write
`java.awt.image.BufferedImage` from the HTTP request and response. This converter reads
and writes the media type supported by the Java I/O API.
|===
[[rest-template-jsonview]]
=== Jackson JSON Views
You can specify a https://www.baeldung.com/jackson-json-view-annotation[Jackson JSON View]
to serialize only a subset of the object properties, as the following example shows:
[source,java,indent=0,subs="verbatim,quotes"]
----
MappingJacksonValue value = new MappingJacksonValue(new User("eric", "7!jd#h23"));
value.setSerializationView(User.WithoutPasswordView.class);
RequestEntity<MappingJacksonValue> requestEntity =
RequestEntity.post(new URI("https://example.com/user")).body(value);
ResponseEntity<String> response = template.exchange(requestEntity, String.class);
----
[[rest-template-multipart]]
=== Multipart
To send multipart data, you need to provide a `MultiValueMap<String, Object>` whose values
may be an `Object` for part content, a `Resource` for a file part, or an `HttpEntity` for
part content with headers. For example:
[source,java,indent=0,subs="verbatim,quotes"]
----
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
parts.add("fieldPart", "fieldValue");
parts.add("filePart", new FileSystemResource("...logo.png"));
parts.add("jsonPart", new Person("Jason"));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_XML);
parts.add("xmlPart", new HttpEntity<>(myBean, headers));
----
In most cases, you do not have to specify the `Content-Type` for each part. The content
type is determined automatically based on the `HttpMessageConverter` chosen to serialize
it or, in the case of a `Resource` based on the file extension. If necessary, you can
explicitly provide the `MediaType` with an `HttpEntity` wrapper.
Once the `MultiValueMap` is ready, you can pass it to the `RestTemplate`, as show below:
[source,java,indent=0,subs="verbatim,quotes"]
----
MultiValueMap<String, Object> parts = ...;
template.postForObject("https://example.com/upload", parts, Void.class);
----
If the `MultiValueMap` contains at least one non-`String` value, the `Content-Type` is set
to `multipart/form-data` by the `FormHttpMessageConverter`. If the `MultiValueMap` has
`String` values the `Content-Type` is defaulted to `application/x-www-form-urlencoded`.
If necessary the `Content-Type` may also be set explicitly.
[[rest-http-interface]]
== HTTP Interface
The Spring Framework lets you define an HTTP service as a Java interface with annotated
methods for HTTP exchanges. You can then generate a proxy that implements this interface
and performs the exchanges. This helps to simplify HTTP remote access which often
involves a facade that wraps the details of using the underlying HTTP client.
One, declare an interface with `@HttpExchange` methods:
[source,java,indent=0,subs="verbatim,quotes"]
----
interface RepositoryService {
@GetExchange("/repos/{owner}/{repo}")
Repository getRepository(@PathVariable String owner, @PathVariable String repo);
// more HTTP exchange methods...
}
----
Two, create a proxy that will perform the declared HTTP exchanges:
[source,java,indent=0,subs="verbatim,quotes"]
----
WebClient client = WebClient.builder().baseUrl("https://api.github.com/").build();
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).build();
RepositoryService service = factory.createClient(RepositoryService.class);
----
`@HttpExchange` is supported at the type level where it applies to all methods:
[source,java,indent=0,subs="verbatim,quotes"]
----
@HttpExchange(url = "/repos/{owner}/{repo}", accept = "application/vnd.github.v3+json")
interface RepositoryService {
@GetExchange
Repository getRepository(@PathVariable String owner, @PathVariable String repo);
@PatchExchange(contentType = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
void updateRepository(@PathVariable String owner, @PathVariable String repo,
@RequestParam String name, @RequestParam String description, @RequestParam String homepage);
}
----
[[rest-http-interface-method-parameters]]
=== Method Parameters
Annotated, HTTP exchange methods support flexible method signatures with the following
method parameters:
[cols="1,2", options="header"]
|===
| Method argument | Description
| `URI`
| Dynamically set the URL for the request, overriding the annotation's `url` attribute.
| `HttpMethod`
| Dynamically set the HTTP method for the request, overriding the annotation's `method` attribute
| `@RequestHeader`
| Add a request header or mutliple headers. The argument may be a `Map<String, ?>` or
`MultiValueMap<String, ?>` with multiple headers, a `Collection<?>` of values, or an
individual value. Type conversion is supported for non-String values.
| `@PathVariable`
| Add a variable for expand a placeholder in the request URL. The argument may be a
`Map<String, ?>` with multiple variables, or an individual value. Type conversion
is supported for non-String values.
| `@RequestBody`
| Provide the body of the request either as an Object to be serialized, or a
Reactive Streams `Publisher` such as `Mono`, `Flux`, or any other async type supported
through the configured `ReactiveAdapterRegistry`.
| `@RequestParam`
| Add a request parameter or mutliple parameters. The argument may be a `Map<String, ?>`
or `MultiValueMap<String, ?>` with multiple parameters, a `Collection<?>` of values, or
an individual value. Type conversion is supported for non-String values.
When `"content-type"` is set to `"application/x-www-form-urlencoded"`, request
parameters are encoded in the request body. Otherwise, they are added as URL query
parameters.
| `@RequestPart`
| Add a request part, which may be a String (form field), `Resource` (file part),
Object (entity to be encoded, e.g. as JSON), `HttpEntity` (part content and headers),
a Spring `Part`, or Reactive Streams `Publisher` of any of the above.
| `@CookieValue`
| Add a cookie or mutliple cookies. The argument may be a `Map<String, ?>` or
`MultiValueMap<String, ?>` with multiple cookies, a `Collection<?>` of values, or an
individual value. Type conversion is supported for non-String values.
|===
[[rest-http-interface-return-values]]
=== Return Values
Annotated, HTTP exchange methods support the following return values:
[cols="1,2", options="header"]
|===
| Method return value | Description
| `void`, `Mono<Void>`
| Perform the given request, and release the response content, if any.
| `HttpHeaders`, `Mono<HttpHeaders>`
| Perform the given request, release the response content, if any, and return the
response headers.
| `<T>`, `Mono<T>`
| Perform the given request and decode the response content to the declared return type.
| `<T>`, `Flux<T>`
| Perform the given request and decode the response content to a stream of the declared
element type.
| `ResponseEntity<Void>`, `Mono<ResponseEntity<Void>>`
| Perform the given request, and release the response content, if any, and return a
`ResponseEntity` with the status and headers.
| `ResponseEntity<T>`, `Mono<ResponseEntity<T>>`
| Perform the given request, decode the response content to the declared return type, and
return a `ResponseEntity` with the status, headers, and the decoded body.
| `Mono<ResponseEntity<Flux<T>>`
| Perform the given request, decode the response content to a stream of the declared
element type, and return a `ResponseEntity` with the status, headers, and the decoded
response body stream.
|===
TIP: You can also use any other async or reactive types registered in the
`ReactiveAdapterRegistry`.
[[rest-http-interface-exceptions]]
=== Exception Handling
By default, `WebClient` raises `WebClientResponseException` for 4xx and 5xx HTTP status
codes. To customize this, you can register a response status handler that applies to all
responses performed through the client:
[source,java,indent=0,subs="verbatim,quotes"]
----
WebClient webClient = WebClient.builder()
.defaultStatusHandler(HttpStatusCode::isError, resp -> ...)
.build();
WebClientAdapter clientAdapter = WebClientAdapter.forClient(webClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory
.builder(clientAdapter).build();
----
For more details and options, such as suppressing error status codes, see the Javadoc of
`defaultStatusHandler` in `WebClient.Builder`.
@@ -0,0 +1,967 @@
[[scheduling]]
= Task Execution and Scheduling
The Spring Framework provides abstractions for the asynchronous execution and scheduling of
tasks with the `TaskExecutor` and `TaskScheduler` interfaces, respectively. Spring also
features implementations of those interfaces that support thread pools or delegation to
CommonJ within an application server environment. Ultimately, the use of these
implementations behind the common interfaces abstracts away the differences between Java
SE 5, Java SE 6, and Jakarta EE environments.
Spring also features integration classes to support scheduling with the `Timer`
(part of the JDK since 1.3) and the Quartz Scheduler ( https://www.quartz-scheduler.org/[]).
You can set up both of those schedulers by using a `FactoryBean` with optional references to
`Timer` or `Trigger` instances, respectively. Furthermore, a convenience class for both
the Quartz Scheduler and the `Timer` is available that lets you invoke a method of
an existing target object (analogous to the normal `MethodInvokingFactoryBean`
operation).
[[scheduling-task-executor]]
== The Spring `TaskExecutor` Abstraction
Executors are the JDK name for the concept of thread pools. The "`executor`" naming is
due to the fact that there is no guarantee that the underlying implementation is
actually a pool. An executor may be single-threaded or even synchronous. Spring's
abstraction hides implementation details between the Java SE and Jakarta EE environments.
Spring's `TaskExecutor` interface is identical to the `java.util.concurrent.Executor`
interface. In fact, originally, its primary reason for existence was to abstract away
the need for Java 5 when using thread pools. The interface has a single method
(`execute(Runnable task)`) that accepts a task for execution based on the semantics
and configuration of the thread pool.
The `TaskExecutor` was originally created to give other Spring components an abstraction
for thread pooling where needed. Components such as the `ApplicationEventMulticaster`,
JMS's `AbstractMessageListenerContainer`, and Quartz integration all use the
`TaskExecutor` abstraction to pool threads. However, if your beans need thread pooling
behavior, you can also use this abstraction for your own needs.
[[scheduling-task-executor-types]]
=== `TaskExecutor` Types
Spring includes a number of pre-built implementations of `TaskExecutor`.
In all likelihood, you should never need to implement your own.
The variants that Spring provides are as follows:
* `SyncTaskExecutor`:
This implementation does not run invocations asynchronously. Instead, each
invocation takes place in the calling thread. It is primarily used in situations
where multi-threading is not necessary, such as in simple test cases.
* `SimpleAsyncTaskExecutor`:
This implementation does not reuse any threads. Rather, it starts up a new thread
for each invocation. However, it does support a concurrency limit that blocks
any invocations that are over the limit until a slot has been freed up. If you
are looking for true pooling, see `ThreadPoolTaskExecutor`, later in this list.
* `ConcurrentTaskExecutor`:
This implementation is an adapter for a `java.util.concurrent.Executor` instance.
There is an alternative (`ThreadPoolTaskExecutor`) that exposes the `Executor`
configuration parameters as bean properties. There is rarely a need to use
`ConcurrentTaskExecutor` directly. However, if the `ThreadPoolTaskExecutor` is not
flexible enough for your needs, `ConcurrentTaskExecutor` is an alternative.
* `ThreadPoolTaskExecutor`:
This implementation is most commonly used. It exposes bean properties for
configuring a `java.util.concurrent.ThreadPoolExecutor` and wraps it in a `TaskExecutor`.
If you need to adapt to a different kind of `java.util.concurrent.Executor`, we
recommend that you use a `ConcurrentTaskExecutor` instead.
* `DefaultManagedTaskExecutor`:
This implementation uses a JNDI-obtained `ManagedExecutorService` in a JSR-236
compatible runtime environment (such as a Jakarta EE application server),
replacing a CommonJ WorkManager for that purpose.
[[scheduling-task-executor-usage]]
=== Using a `TaskExecutor`
Spring's `TaskExecutor` implementations are used as simple JavaBeans. In the following example,
we define a bean that uses the `ThreadPoolTaskExecutor` to asynchronously print
out a set of messages:
[source,java,indent=0,subs="verbatim,quotes"]
----
import org.springframework.core.task.TaskExecutor;
public class TaskExecutorExample {
private class MessagePrinterTask implements Runnable {
private String message;
public MessagePrinterTask(String message) {
this.message = message;
}
public void run() {
System.out.println(message);
}
}
private TaskExecutor taskExecutor;
public TaskExecutorExample(TaskExecutor taskExecutor) {
this.taskExecutor = taskExecutor;
}
public void printMessages() {
for(int i = 0; i < 25; i++) {
taskExecutor.execute(new MessagePrinterTask("Message" + i));
}
}
}
----
As you can see, rather than retrieving a thread from the pool and executing it yourself,
you add your `Runnable` to the queue. Then the `TaskExecutor` uses its internal rules to
decide when the task gets run.
To configure the rules that the `TaskExecutor` uses, we expose simple bean properties:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="5"/>
<property name="maxPoolSize" value="10"/>
<property name="queueCapacity" value="25"/>
</bean>
<bean id="taskExecutorExample" class="TaskExecutorExample">
<constructor-arg ref="taskExecutor"/>
</bean>
----
[[scheduling-task-scheduler]]
== The Spring `TaskScheduler` Abstraction
In addition to the `TaskExecutor` abstraction, Spring 3.0 introduced a `TaskScheduler`
with a variety of methods for scheduling tasks to run at some point in the future.
The following listing shows the `TaskScheduler` interface definition:
[source,java,indent=0,subs="verbatim,quotes"]
----
public interface TaskScheduler {
ScheduledFuture schedule(Runnable task, Trigger trigger);
ScheduledFuture schedule(Runnable task, Instant startTime);
ScheduledFuture scheduleAtFixedRate(Runnable task, Instant startTime, Duration period);
ScheduledFuture scheduleAtFixedRate(Runnable task, Duration period);
ScheduledFuture scheduleWithFixedDelay(Runnable task, Instant startTime, Duration delay);
ScheduledFuture scheduleWithFixedDelay(Runnable task, Duration delay);
----
The simplest method is the one named `schedule` that takes only a `Runnable` and an `Instant`.
That causes the task to run once after the specified time. All of the other methods
are capable of scheduling tasks to run repeatedly. The fixed-rate and fixed-delay
methods are for simple, periodic execution, but the method that accepts a `Trigger` is
much more flexible.
[[scheduling-trigger-interface]]
=== `Trigger` Interface
The `Trigger` interface is essentially inspired by JSR-236 which, as of Spring 3.0,
was not yet officially implemented. The basic idea of the `Trigger` is that execution
times may be determined based on past execution outcomes or even arbitrary conditions.
If these determinations do take into account the outcome of the preceding execution,
that information is available within a `TriggerContext`. The `Trigger` interface itself
is quite simple, as the following listing shows:
[source,java,indent=0,subs="verbatim,quotes"]
----
public interface Trigger {
Date nextExecutionTime(TriggerContext triggerContext);
}
----
The `TriggerContext` is the most important part. It encapsulates all of
the relevant data and is open for extension in the future, if necessary. The
`TriggerContext` is an interface (a `SimpleTriggerContext` implementation is used by
default). The following listing shows the available methods for `Trigger` implementations.
[source,java,indent=0,subs="verbatim,quotes"]
----
public interface TriggerContext {
Date lastScheduledExecutionTime();
Date lastActualExecutionTime();
Date lastCompletionTime();
}
----
[[scheduling-trigger-implementations]]
=== `Trigger` Implementations
Spring provides two implementations of the `Trigger` interface. The most interesting one
is the `CronTrigger`. It enables the scheduling of tasks based on
<<scheduling-cron-expression,cron expressions>>.
For example, the following task is scheduled to run 15 minutes past each hour but only
during the 9-to-5 "`business hours`" on weekdays:
[source,java,indent=0]
[subs="verbatim"]
----
scheduler.schedule(task, new CronTrigger("0 15 9-17 * * MON-FRI"));
----
The other implementation is a `PeriodicTrigger` that accepts a fixed
period, an optional initial delay value, and a boolean to indicate whether the period
should be interpreted as a fixed-rate or a fixed-delay. Since the `TaskScheduler`
interface already defines methods for scheduling tasks at a fixed rate or with a
fixed delay, those methods should be used directly whenever possible. The value of the
`PeriodicTrigger` implementation is that you can use it within components that rely on
the `Trigger` abstraction. For example, it may be convenient to allow periodic triggers,
cron-based triggers, and even custom trigger implementations to be used interchangeably.
Such a component could take advantage of dependency injection so that you can configure such `Triggers`
externally and, therefore, easily modify or extend them.
[[scheduling-task-scheduler-implementations]]
=== `TaskScheduler` implementations
As with Spring's `TaskExecutor` abstraction, the primary benefit of the `TaskScheduler`
arrangement is that an application's scheduling needs are decoupled from the deployment
environment. This abstraction level is particularly relevant when deploying to an
application server environment where threads should not be created directly by the
application itself. For such scenarios, Spring provides a `TimerManagerTaskScheduler`
that delegates to a CommonJ `TimerManager` on WebLogic or WebSphere as well as a more recent
`DefaultManagedTaskScheduler` that delegates to a JSR-236 `ManagedScheduledExecutorService`
in a Jakarta EE environment. Both are typically configured with a JNDI lookup.
Whenever external thread management is not a requirement, a simpler alternative is
a local `ScheduledExecutorService` setup within the application, which can be adapted
through Spring's `ConcurrentTaskScheduler`. As a convenience, Spring also provides a
`ThreadPoolTaskScheduler`, which internally delegates to a `ScheduledExecutorService`
to provide common bean-style configuration along the lines of `ThreadPoolTaskExecutor`.
These variants work perfectly fine for locally embedded thread pool setups in lenient
application server environments, as well -- in particular on Tomcat and Jetty.
[[scheduling-annotation-support]]
== Annotation Support for Scheduling and Asynchronous Execution
Spring provides annotation support for both task scheduling and asynchronous method
execution.
[[scheduling-enable-annotation-support]]
=== Enable Scheduling Annotations
To enable support for `@Scheduled` and `@Async` annotations, you can add `@EnableScheduling` and
`@EnableAsync` to one of your `@Configuration` classes, as the following example shows:
[source,java,indent=0,subs="verbatim,quotes"]
----
@Configuration
@EnableAsync
@EnableScheduling
public class AppConfig {
}
----
You can pick and choose the relevant annotations for your application. For example,
if you need only support for `@Scheduled`, you can omit `@EnableAsync`. For more
fine-grained control, you can additionally implement the `SchedulingConfigurer`
interface, the `AsyncConfigurer` interface, or both. See the
{api-spring-framework}/scheduling/annotation/SchedulingConfigurer.html[`SchedulingConfigurer`]
and {api-spring-framework}/scheduling/annotation/AsyncConfigurer.html[`AsyncConfigurer`]
javadoc for full details.
If you prefer XML configuration, you can use the `<task:annotation-driven>` element,
as the following example shows:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<task:annotation-driven executor="myExecutor" scheduler="myScheduler"/>
<task:executor id="myExecutor" pool-size="5"/>
<task:scheduler id="myScheduler" pool-size="10"/>
----
Note that, with the preceding XML, an executor reference is provided for handling those
tasks that correspond to methods with the `@Async` annotation, and the scheduler
reference is provided for managing those methods annotated with `@Scheduled`.
NOTE: The default advice mode for processing `@Async` annotations is `proxy` which allows
for interception of calls through the proxy only. Local calls within the same class
cannot get intercepted that way. For a more advanced mode of interception, consider
switching to `aspectj` mode in combination with compile-time or load-time weaving.
[[scheduling-annotation-support-scheduled]]
=== The `@Scheduled` annotation
You can add the `@Scheduled` annotation to a method, along with trigger metadata. For
example, the following method is invoked every five seconds (5000 milliseconds) with a
fixed delay, meaning that the period is measured from the completion time of each
preceding invocation.
[source,java,indent=0,subs="verbatim,quotes"]
----
@Scheduled(fixedDelay = 5000)
public void doSomething() {
// something that should run periodically
}
----
[NOTE]
====
By default, milliseconds will be used as the time unit for fixed delay, fixed rate, and
initial delay values. If you would like to use a different time unit such as seconds or
minutes, you can configure this via the `timeUnit` attribute in `@Scheduled`.
For example, the previous example can also be written as follows.
[source,java,indent=0,subs="verbatim,quotes"]
----
@Scheduled(fixedDelay = 5, timeUnit = TimeUnit.SECONDS)
public void doSomething() {
// something that should run periodically
}
----
====
If you need a fixed-rate execution, you can use the `fixedRate` attribute within the
annotation. The following method is invoked every five seconds (measured between the
successive start times of each invocation).
[source,java,indent=0,subs="verbatim,quotes"]
----
@Scheduled(fixedRate = 5, timeUnit = TimeUnit.SECONDS)
public void doSomething() {
// something that should run periodically
}
----
For fixed-delay and fixed-rate tasks, you can specify an initial delay by indicating the
amount of time to wait before the first execution of the method, as the following
`fixedRate` example shows.
[source,java,indent=0,subs="verbatim,quotes"]
----
@Scheduled(initialDelay = 1000, fixedRate = 5000)
public void doSomething() {
// something that should run periodically
}
----
If simple periodic scheduling is not expressive enough, you can provide a
<<scheduling-cron-expression,cron expression>>.
The following example runs only on weekdays:
[source,java,indent=0]
[subs="verbatim"]
----
@Scheduled(cron="*/5 * * * * MON-FRI")
public void doSomething() {
// something that should run on weekdays only
}
----
TIP: You can also use the `zone` attribute to specify the time zone in which the cron
expression is resolved.
Notice that the methods to be scheduled must have void returns and must not accept any
arguments. If the method needs to interact with other objects from the application
context, those would typically have been provided through dependency injection.
[NOTE]
====
As of Spring Framework 4.3, `@Scheduled` methods are supported on beans of any scope.
Make sure that you are not initializing multiple instances of the same `@Scheduled`
annotation class at runtime, unless you do want to schedule callbacks to each such
instance. Related to this, make sure that you do not use `@Configurable` on bean
classes that are annotated with `@Scheduled` and registered as regular Spring beans
with the container. Otherwise, you would get double initialization (once through the
container and once through the `@Configurable` aspect), with the consequence of each
`@Scheduled` method being invoked twice.
====
[[scheduling-annotation-support-async]]
=== The `@Async` annotation
You can provide the `@Async` annotation on a method so that invocation of that method
occurs asynchronously. In other words, the caller returns immediately upon
invocation, while the actual execution of the method occurs in a task that has been
submitted to a Spring `TaskExecutor`. In the simplest case, you can apply the annotation
to a method that returns `void`, as the following example shows:
[source,java,indent=0,subs="verbatim,quotes"]
----
@Async
void doSomething() {
// this will be run asynchronously
}
----
Unlike the methods annotated with the `@Scheduled` annotation, these methods can expect
arguments, because they are invoked in the "`normal`" way by callers at runtime rather
than from a scheduled task being managed by the container. For example, the following code is
a legitimate application of the `@Async` annotation:
[source,java,indent=0,subs="verbatim,quotes"]
----
@Async
void doSomething(String s) {
// this will be run asynchronously
}
----
Even methods that return a value can be invoked asynchronously. However, such methods
are required to have a `Future`-typed return value. This still provides the benefit of
asynchronous execution so that the caller can perform other tasks prior to calling
`get()` on that `Future`. The following example shows how to use `@Async` on a method
that returns a value:
[source,java,indent=0,subs="verbatim,quotes"]
----
@Async
Future<String> returnSomething(int i) {
// this will be run asynchronously
}
----
TIP: `@Async` methods may not only declare a regular `java.util.concurrent.Future` return type
but also Spring's `org.springframework.util.concurrent.ListenableFuture` or, as of Spring
4.2, JDK 8's `java.util.concurrent.CompletableFuture`, for richer interaction with the
asynchronous task and for immediate composition with further processing steps.
You can not use `@Async` in conjunction with lifecycle callbacks such as
`@PostConstruct`. To asynchronously initialize Spring beans, you currently have to use
a separate initializing Spring bean that then invokes the `@Async` annotated method on the
target, as the following example shows:
[source,java,indent=0,subs="verbatim,quotes"]
----
public class SampleBeanImpl implements SampleBean {
@Async
void doSomething() {
// ...
}
}
public class SampleBeanInitializer {
private final SampleBean bean;
public SampleBeanInitializer(SampleBean bean) {
this.bean = bean;
}
@PostConstruct
public void initialize() {
bean.doSomething();
}
}
----
NOTE: There is no direct XML equivalent for `@Async`, since such methods should be designed
for asynchronous execution in the first place, not externally re-declared to be asynchronous.
However, you can manually set up Spring's `AsyncExecutionInterceptor` with Spring AOP,
in combination with a custom pointcut.
[[scheduling-annotation-support-qualification]]
=== Executor Qualification with `@Async`
By default, when specifying `@Async` on a method, the executor that is used is the
one <<scheduling-enable-annotation-support, configured when enabling async support>>,
i.e. the "`annotation-driven`" element if you are using XML or your `AsyncConfigurer`
implementation, if any. However, you can use the `value` attribute of the `@Async`
annotation when you need to indicate that an executor other than the default should be
used when executing a given method. The following example shows how to do so:
[source,java,indent=0,subs="verbatim,quotes"]
----
@Async("otherExecutor")
void doSomething(String s) {
// this will be run asynchronously by "otherExecutor"
}
----
In this case, `"otherExecutor"` can be the name of any `Executor` bean in the Spring
container, or it may be the name of a qualifier associated with any `Executor` (for example, as
specified with the `<qualifier>` element or Spring's `@Qualifier` annotation).
[[scheduling-annotation-support-exception]]
=== Exception Management with `@Async`
When an `@Async` method has a `Future`-typed return value, it is easy to manage
an exception that was thrown during the method execution, as this exception is
thrown when calling `get` on the `Future` result. With a `void` return type,
however, the exception is uncaught and cannot be transmitted. You can provide an
`AsyncUncaughtExceptionHandler` to handle such exceptions. The following example shows
how to do so:
[source,java,indent=0,subs="verbatim,quotes"]
----
public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
// handle exception
}
}
----
By default, the exception is merely logged. You can define a custom `AsyncUncaughtExceptionHandler`
by using `AsyncConfigurer` or the `<task:annotation-driven/>` XML element.
[[scheduling-task-namespace]]
== The `task` Namespace
As of version 3.0, Spring includes an XML namespace for configuring `TaskExecutor` and
`TaskScheduler` instances. It also provides a convenient way to configure tasks to be
scheduled with a trigger.
[[scheduling-task-namespace-scheduler]]
=== The 'scheduler' Element
The following element creates a `ThreadPoolTaskScheduler` instance with the
specified thread pool size:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<task:scheduler id="scheduler" pool-size="10"/>
----
The value provided for the `id` attribute is used as the prefix for thread names
within the pool. The `scheduler` element is relatively straightforward. If you do not
provide a `pool-size` attribute, the default thread pool has only a single thread.
There are no other configuration options for the scheduler.
[[scheduling-task-namespace-executor]]
=== The `executor` Element
The following creates a `ThreadPoolTaskExecutor` instance:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<task:executor id="executor" pool-size="10"/>
----
As with the scheduler shown in the <<scheduling-task-namespace-scheduler, previous section>>,
the value provided for the `id` attribute is used as the prefix for thread names within
the pool. As far as the pool size is concerned, the `executor` element supports more
configuration options than the `scheduler` element. For one thing, the thread pool for
a `ThreadPoolTaskExecutor` is itself more configurable. Rather than only a single size,
an executor's thread pool can have different values for the core and the max size.
If you provide a single value, the executor has a fixed-size thread pool (the core and
max sizes are the same). However, the `executor` element's `pool-size` attribute also
accepts a range in the form of `min-max`. The following example sets a minimum value of
`5` and a maximum value of `25`:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<task:executor
id="executorWithPoolSizeRange"
pool-size="5-25"
queue-capacity="100"/>
----
In the preceding configuration, a `queue-capacity` value has also been provided.
The configuration of the thread pool should also be considered in light of the
executor's queue capacity. For the full description of the relationship between pool
size and queue capacity, see the documentation for
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ThreadPoolExecutor.html[`ThreadPoolExecutor`].
The main idea is that, when a task is submitted, the executor first tries to use a
free thread if the number of active threads is currently less than the core size.
If the core size has been reached, the task is added to the queue, as long as its
capacity has not yet been reached. Only then, if the queue's capacity has been
reached, does the executor create a new thread beyond the core size. If the max size
has also been reached, then the executor rejects the task.
By default, the queue is unbounded, but this is rarely the desired configuration,
because it can lead to `OutOfMemoryErrors` if enough tasks are added to that queue while
all pool threads are busy. Furthermore, if the queue is unbounded, the max size has
no effect at all. Since the executor always tries the queue before creating a new
thread beyond the core size, a queue must have a finite capacity for the thread pool to
grow beyond the core size (this is why a fixed-size pool is the only sensible case
when using an unbounded queue).
Consider the case, as mentioned above, when a task is rejected. By default, when a
task is rejected, a thread pool executor throws a `TaskRejectedException`. However,
the rejection policy is actually configurable. The exception is thrown when using
the default rejection policy, which is the `AbortPolicy` implementation.
For applications where some tasks can be skipped under heavy load, you can instead
configure either `DiscardPolicy` or `DiscardOldestPolicy`. Another option that works
well for applications that need to throttle the submitted tasks under heavy load is
the `CallerRunsPolicy`. Instead of throwing an exception or discarding tasks,
that policy forces the thread that is calling the submit method to run the task itself.
The idea is that such a caller is busy while running that task and not able to submit
other tasks immediately. Therefore, it provides a simple way to throttle the incoming
load while maintaining the limits of the thread pool and queue. Typically, this allows
the executor to "`catch up`" on the tasks it is handling and thereby frees up some
capacity on the queue, in the pool, or both. You can choose any of these options from an
enumeration of values available for the `rejection-policy` attribute on the `executor`
element.
The following example shows an `executor` element with a number of attributes to specify
various behaviors:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<task:executor
id="executorWithCallerRunsPolicy"
pool-size="5-25"
queue-capacity="100"
rejection-policy="CALLER_RUNS"/>
----
Finally, the `keep-alive` setting determines the time limit (in seconds) for which threads
may remain idle before being stopped. If there are more than the core number of threads
currently in the pool, after waiting this amount of time without processing a task, excess
threads get stopped. A time value of zero causes excess threads to stop
immediately after executing a task without remaining follow-up work in the task queue.
The following example sets the `keep-alive` value to two minutes:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<task:executor
id="executorWithKeepAlive"
pool-size="5-25"
keep-alive="120"/>
----
[[scheduling-task-namespace-scheduled-tasks]]
=== The 'scheduled-tasks' Element
The most powerful feature of Spring's task namespace is the support for configuring
tasks to be scheduled within a Spring Application Context. This follows an approach
similar to other "`method-invokers`" in Spring, such as that provided by the JMS namespace
for configuring message-driven POJOs. Basically, a `ref` attribute can point to any
Spring-managed object, and the `method` attribute provides the name of a method to be
invoked on that object. The following listing shows a simple example:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<task:scheduled-tasks scheduler="myScheduler">
<task:scheduled ref="beanA" method="methodA" fixed-delay="5000"/>
</task:scheduled-tasks>
<task:scheduler id="myScheduler" pool-size="10"/>
----
The scheduler is referenced by the outer element, and each individual
task includes the configuration of its trigger metadata. In the preceding example, that
metadata defines a periodic trigger with a fixed delay indicating the number of
milliseconds to wait after each task execution has completed. Another option is
`fixed-rate`, indicating how often the method should be run regardless of how long
any previous execution takes. Additionally, for both `fixed-delay` and `fixed-rate` tasks, you can specify an
'initial-delay' parameter, indicating the number of milliseconds to wait
before the first execution of the method. For more control, you can instead provide a `cron` attribute
to provide a <<scheduling-cron-expression,cron expression>>.
The following example shows these other options:
[source,xml,indent=0]
[subs="verbatim"]
----
<task:scheduled-tasks scheduler="myScheduler">
<task:scheduled ref="beanA" method="methodA" fixed-delay="5000" initial-delay="1000"/>
<task:scheduled ref="beanB" method="methodB" fixed-rate="5000"/>
<task:scheduled ref="beanC" method="methodC" cron="*/5 * * * * MON-FRI"/>
</task:scheduled-tasks>
<task:scheduler id="myScheduler" pool-size="10"/>
----
[[scheduling-cron-expression]]
== Cron Expressions
All Spring cron expressions have to conform to the same format, whether you are using them in
<<scheduling-annotation-support-scheduled,`@Scheduled` annotations>>,
<<scheduling-task-namespace-scheduled-tasks,`task:scheduled-tasks` elements>>,
or someplace else.
A well-formed cron expression, such as `* * * * * *`, consists of six space-separated time and date
fields, each with its own range of valid values:
....
┌───────────── second (0-59)
│ ┌───────────── minute (0 - 59)
│ │ ┌───────────── hour (0 - 23)
│ │ │ ┌───────────── day of the month (1 - 31)
│ │ │ │ ┌───────────── month (1 - 12) (or JAN-DEC)
│ │ │ │ │ ┌───────────── day of the week (0 - 7)
│ │ │ │ │ │ (0 or 7 is Sunday, or MON-SUN)
│ │ │ │ │ │
* * * * * *
....
There are some rules that apply:
* A field may be an asterisk (`*`), which always stands for "`first-last`".
For the day-of-the-month or day-of-the-week fields, a question mark (`?`) may be used instead of an
asterisk.
* Commas (`,`) are used to separate items of a list.
* Two numbers separated with a hyphen (`-`) express a range of numbers.
The specified range is inclusive.
* Following a range (or `*`) with `/` specifies the interval of the number's value through the range.
* English names can also be used for the month and day-of-week fields.
Use the first three letters of the particular day or month (case does not matter).
* The day-of-month and day-of-week fields can contain an `L` character, which has a different meaning.
** In the day-of-month field, `L` stands for _the last day of the month_.
If followed by a negative offset (that is, `L-n`), it means _``n``th-to-last day of the month_.
** In the day-of-week field, `L` stands for _the last day of the week_.
If prefixed by a number or three-letter name (`dL` or `DDDL`), it means _the last day of week (`d`
or `DDD`) in the month_.
* The day-of-month field can be `nW`, which stands for _the nearest weekday to day of the month ``n``_.
If `n` falls on Saturday, this yields the Friday before it.
If `n` falls on Sunday, this yields the Monday after, which also happens if `n` is `1` and falls on
a Saturday (that is: `1W` stands for _the first weekday of the month_).
* If the day-of-month field is `LW`, it means _the last weekday of the month_.
* The day-of-week field can be `d#n` (or `DDD#n`), which stands for _the ``n``th day of week `d`
(or ``DDD``) in the month_.
Here are some examples:
|===
| Cron Expression | Meaning
|`0 0 * * * *` | top of every hour of every day
|`*/10 * * * * *` | every ten seconds
| `0 0 8-10 * * *` | 8, 9 and 10 o'clock of every day
| `0 0 6,19 * * *` | 6:00 AM and 7:00 PM every day
| `0 0/30 8-10 * * *` | 8:00, 8:30, 9:00, 9:30, 10:00 and 10:30 every day
| `0 0 9-17 * * MON-FRI`| on the hour nine-to-five weekdays
| `0 0 0 25 DEC ?` | every Christmas Day at midnight
| `0 0 0 L * *` | last day of the month at midnight
| `0 0 0 L-3 * *` | third-to-last day of the month at midnight
| `0 0 0 * * 5L` | last Friday of the month at midnight
| `0 0 0 * * THUL` | last Thursday of the month at midnight
| `0 0 0 1W * *` | first weekday of the month at midnight
| `0 0 0 LW * *` | last weekday of the month at midnight
| `0 0 0 ? * 5#2` | the second Friday in the month at midnight
| `0 0 0 ? * MON#1` | the first Monday in the month at midnight
|===
=== Macros
Expressions such as `0 0 * * * *` are hard for humans to parse and are, therefore, hard to fix in case of bugs.
To improve readability, Spring supports the following macros, which represent commonly used sequences.
You can use these macros instead of the six-digit value, thus: `@Scheduled(cron = "@hourly")`.
|===
|Macro | Meaning
| `@yearly` (or `@annually`) | once a year (`0 0 0 1 1 *`)
| `@monthly` | once a month (`0 0 0 1 * *`)
| `@weekly` | once a week (`0 0 0 * * 0`)
| `@daily` (or `@midnight`) | once a day (`0 0 0 * * *`), or
| `@hourly` | once an hour, (`0 0 * * * *`)
|===
[[scheduling-quartz]]
== Using the Quartz Scheduler
Quartz uses `Trigger`, `Job`, and `JobDetail` objects to realize scheduling of all kinds
of jobs. For the basic concepts behind Quartz, see
https://www.quartz-scheduler.org/[]. For convenience purposes, Spring offers a couple of
classes that simplify using Quartz within Spring-based applications.
[[scheduling-quartz-jobdetail]]
=== Using the `JobDetailFactoryBean`
Quartz `JobDetail` objects contain all the information needed to run a job. Spring provides a
`JobDetailFactoryBean`, which provides bean-style properties for XML configuration purposes.
Consider the following example:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<bean name="exampleJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass" value="example.ExampleJob"/>
<property name="jobDataAsMap">
<map>
<entry key="timeout" value="5"/>
</map>
</property>
</bean>
----
The job detail configuration has all the information it needs to run the job (`ExampleJob`).
The timeout is specified in the job data map. The job data map is available through the
`JobExecutionContext` (passed to you at execution time), but the `JobDetail` also gets
its properties from the job data mapped to properties of the job instance. So, in the following example,
the `ExampleJob` contains a bean property named `timeout`, and the `JobDetail`
has it applied automatically:
[source,java,indent=0]
[subs="verbatim"]
----
package example;
public class ExampleJob extends QuartzJobBean {
private int timeout;
/**
* Setter called after the ExampleJob is instantiated
* with the value from the JobDetailFactoryBean (5)
*/
public void setTimeout(int timeout) {
this.timeout = timeout;
}
protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException {
// do the actual work
}
}
----
All additional properties from the job data map are available to you as well.
NOTE: By using the `name` and `group` properties, you can modify the name and the group
of the job, respectively. By default, the name of the job matches the bean name
of the `JobDetailFactoryBean` (`exampleJob` in the preceding example above).
[[scheduling-quartz-method-invoking-job]]
=== Using the `MethodInvokingJobDetailFactoryBean`
Often you merely need to invoke a method on a specific object. By using the
`MethodInvokingJobDetailFactoryBean`, you can do exactly this, as the following example shows:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="exampleBusinessObject"/>
<property name="targetMethod" value="doIt"/>
</bean>
----
The preceding example results in the `doIt` method being called on the
`exampleBusinessObject` method, as the following example shows:
[source,java,indent=0,subs="verbatim,quotes"]
----
public class ExampleBusinessObject {
// properties and collaborators
public void doIt() {
// do the actual work
}
}
----
[source,xml,indent=0,subs="verbatim,quotes"]
----
<bean id="exampleBusinessObject" class="examples.ExampleBusinessObject"/>
----
By using the `MethodInvokingJobDetailFactoryBean`, you need not create one-line jobs
that merely invoke a method. You need only create the actual business object and
wire up the detail object.
By default, Quartz Jobs are stateless, resulting in the possibility of jobs interfering
with each other. If you specify two triggers for the same `JobDetail`, it is
possible that, before the first job has finished, the second one starts. If
`JobDetail` classes implement the `Stateful` interface, this does not happen. The second
job does not start before the first one has finished. To make jobs resulting from the
`MethodInvokingJobDetailFactoryBean` be non-concurrent, set the `concurrent` flag to
`false`, as the following example shows:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="exampleBusinessObject"/>
<property name="targetMethod" value="doIt"/>
<property name="concurrent" value="false"/>
</bean>
----
NOTE: By default, jobs will run in a concurrent fashion.
[[scheduling-quartz-cron]]
=== Wiring up Jobs by Using Triggers and `SchedulerFactoryBean`
We have created job details and jobs. We have also reviewed the convenience bean that lets
you invoke a method on a specific object. Of course, we still need to schedule the
jobs themselves. This is done by using triggers and a `SchedulerFactoryBean`. Several
triggers are available within Quartz, and Spring offers two Quartz `FactoryBean`
implementations with convenient defaults: `CronTriggerFactoryBean` and
`SimpleTriggerFactoryBean`.
Triggers need to be scheduled. Spring offers a `SchedulerFactoryBean` that exposes
triggers to be set as properties. `SchedulerFactoryBean` schedules the actual jobs with
those triggers.
The following listing uses both a `SimpleTriggerFactoryBean` and a `CronTriggerFactoryBean`:
[source,xml,indent=0]
[subs="verbatim"]
----
<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
<!-- see the example of method invoking job above -->
<property name="jobDetail" ref="jobDetail"/>
<!-- 10 seconds -->
<property name="startDelay" value="10000"/>
<!-- repeat every 50 seconds -->
<property name="repeatInterval" value="50000"/>
</bean>
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="exampleJob"/>
<!-- run every morning at 6 AM -->
<property name="cronExpression" value="0 0 6 * * ?"/>
</bean>
----
The preceding example sets up two triggers, one running every 50 seconds with a starting delay of 10
seconds and one running every morning at 6 AM. To finalize everything, we need to set up the
`SchedulerFactoryBean`, as the following example shows:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="cronTrigger"/>
<ref bean="simpleTrigger"/>
</list>
</property>
</bean>
----
More properties are available for the `SchedulerFactoryBean`, such as the calendars used by the
job details, properties to customize Quartz with, and a Spring-provided JDBC DataSource. See
the {api-spring-framework}/scheduling/quartz/SchedulerFactoryBean.html[`SchedulerFactoryBean`]
javadoc for more information.
NOTE: `SchedulerFactoryBean` also recognizes a `quartz.properties` file in the classpath,
based on Quartz property keys, as with regular Quartz configuration. Please note that many
`SchedulerFactoryBean` settings interact with common Quartz settings in the properties file;
it is therefore not recommended to specify values at both levels. For example, do not set
an "org.quartz.jobStore.class" property if you mean to rely on a Spring-provided DataSource,
or specify an `org.springframework.scheduling.quartz.LocalDataSourceJobStore` variant which
is a full-fledged replacement for the standard `org.quartz.impl.jdbcjobstore.JobStoreTX`.
@@ -1,6 +1,17 @@
:noheader:
= Spring Framework Documentation
:toc:
include::attributes.adoc[]
= Spring Framework Documentation
Rod Johnson; Juergen Hoeller; Keith Donald; Colin Sampaleanu; Rob Harrop; Thomas Risberg; Alef Arendsen; Darren Davison; Dmitriy Kopylenko; Mark Pollack; Thierry Templier; Erwin Vervaet; Portia Tung; Ben Hale; Adrian Colyer; John Lewis; Costin Leau; Mark Fisher; Sam Brannen; Ramnivas Laddad; Arjen Poutsma; Chris Beams; Tareq Abedrabbo; Andy Clement; Dave Syer; Oliver Gierke; Rossen Stoyanchev; Phillip Webb; Rob Winch; Brian Clozel; Stephane Nicoll; Sebastien Deleuze; Jay Bryant; Mark Paluch
NOTE: This documentation is also available in {docs-spring-framework}/reference/html/index.html[HTML] format.
[[legal]]
== Legal
Copyright © 2002 - 2022 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.
include::overview.adoc[leveloffset=+1]
include::core.adoc[leveloffset=+1]
@@ -11,16 +22,3 @@ include::web-reactive.adoc[leveloffset=+1]
include::integration.adoc[leveloffset=+1]
include::languages.adoc[leveloffset=+1]
include::appendix.adoc[leveloffset=+1]
Rod Johnson, Juergen Hoeller, Keith Donald, Colin Sampaleanu, Rob Harrop, Thomas Risberg,
Alef Arendsen, Darren Davison, Dmitriy Kopylenko, Mark Pollack, Thierry Templier, Erwin
Vervaet, Portia Tung, Ben Hale, Adrian Colyer, John Lewis, Costin Leau, Mark Fisher, Sam
Brannen, Ramnivas Laddad, Arjen Poutsma, Chris Beams, Tareq Abedrabbo, Andy Clement, Dave
Syer, Oliver Gierke, Rossen Stoyanchev, Phillip Webb, Rob Winch, Brian Clozel, Stephane
Nicoll, Sebastien Deleuze, Jay Bryant, Mark Paluch
Copyright © 2002 - 2022 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.
@@ -1429,6 +1429,7 @@ Now we can use WebDriver as we normally would but without the need to deploy our
application to a Servlet container. For example, we can request the view to create a
message with the following:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -1440,9 +1441,11 @@ message with the following:
----
val page = CreateMessagePage.to(driver)
----
--
We can then fill out the form and submit it to create a message, as follows:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -1456,6 +1459,7 @@ We can then fill out the form and submit it to create a message, as follows:
val viewMessagePage =
page.createMessage(ViewMessagePage::class, expectedSummary, expectedText)
----
--
This improves on the design of our <<spring-mvc-test-server-htmlunit-mah-usage, HtmlUnit test>>
by leveraging the Page Object Pattern. As we mentioned in
@@ -1463,18 +1467,17 @@ by leveraging the Page Object Pattern. As we mentioned in
with HtmlUnit, but it is much easier with WebDriver. Consider the following
`CreateMessagePage` implementation:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
public class CreateMessagePage
extends AbstractPage { // <1>
public class CreateMessagePage extends AbstractPage { // <1>
// <2>
private WebElement summary;
private WebElement text;
// <3>
@FindBy(css = "input[type=submit]")
@FindBy(css = "input[type=submit]") // <3>
private WebElement submit;
public CreateMessagePage(WebDriver driver) {
@@ -1509,7 +1512,7 @@ by the `id` or `name` of the element within the HTML page.
<3> We can use the
https://github.com/SeleniumHQ/selenium/wiki/PageFactory#making-the-example-work-using-annotations[`@FindBy` annotation]
to override the default lookup behavior. Our example shows how to use the `@FindBy`
annotation to look up our submit button with a `css` selector (*input[type=submit]*).
annotation to look up our submit button with a `css` selector (`input[type=submit]`).
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
@@ -1520,8 +1523,7 @@ annotation to look up our submit button with a `css` selector (*input[type=submi
private lateinit var summary: WebElement
private lateinit var text: WebElement
// <3>
@FindBy(css = "input[type=submit]")
@FindBy(css = "input[type=submit]") // <3>
private lateinit var submit: WebElement
fun <T> createMessage(resultPage: Class<T>, summary: String, details: String): T {
@@ -1554,11 +1556,12 @@ by the `id` or `name` of the element within the HTML page.
https://github.com/SeleniumHQ/selenium/wiki/PageFactory#making-the-example-work-using-annotations[`@FindBy` annotation]
to override the default lookup behavior. Our example shows how to use the `@FindBy`
annotation to look up our submit button with a `css` selector (*input[type=submit]*).
--
Finally, we can verify that a new message was created successfully. The following
assertions use the https://assertj.github.io/doc/[AssertJ] assertion library:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -1571,10 +1574,12 @@ assertions use the https://assertj.github.io/doc/[AssertJ] assertion library:
assertThat(viewMessagePage.message).isEqualTo(expectedMessage)
assertThat(viewMessagePage.success).isEqualTo("Successfully created a new message")
----
--
We can see that our `ViewMessagePage` lets us interact with our custom domain model. For
example, it exposes a method that returns a `Message` object:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -1592,12 +1597,14 @@ example, it exposes a method that returns a `Message` object:
----
fun getMessage() = Message(getId(), getCreated(), getSummary(), getText())
----
--
We can then use the rich domain objects in our assertions.
Lastly, we must not forget to close the `WebDriver` instance when the test is complete,
as follows:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -1619,6 +1626,7 @@ as follows:
}
}
----
--
For additional information on using WebDriver, see the Selenium
https://github.com/SeleniumHQ/selenium/wiki/Getting-Started[WebDriver documentation].
@@ -631,7 +631,7 @@ path that represents a resource URL (i.e., a path prefixed with `classpath:`, `f
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "/app-config.xml" and
// "/test-config.xml" in the root of the classpath
@ContextConfiguration(locations={"/app-config.xml", "/test-config.xml"}) // <1>
@ContextConfiguration(locations = {"/app-config.xml", "/test-config.xml"}) // <1>
class MyTest {
// class body...
}
@@ -644,7 +644,7 @@ path that represents a resource URL (i.e., a path prefixed with `classpath:`, `f
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from "/app-config.xml" and
// "/test-config.xml" in the root of the classpath
@ContextConfiguration("/app-config.xml", "/test-config.xml") // <1>
@ContextConfiguration(locations = ["/app-config.xml", "/test-config.xml"]) // <1>
class MyTest {
// class body...
}
@@ -667,7 +667,7 @@ demonstrated in the following example:
// class body...
}
----
<1> Specifying XML files without using the `location` attribute.
<1> Specifying XML files without using the `locations` attribute.
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
@@ -678,7 +678,7 @@ demonstrated in the following example:
// class body...
}
----
<1> Specifying XML files without using the `location` attribute.
<1> Specifying XML files without using the `locations` attribute.
If you omit both the `locations` and the `value` attributes from the
@@ -743,6 +743,7 @@ The following example shows how to specify Groovy configuration files:
// class body...
}
----
<1> Specifying the location of Groovy configuration files.
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
@@ -898,8 +899,7 @@ class:
.Java
----
@SpringJUnitConfig <1>
// ApplicationContext will be loaded from the
// static nested Config class
// ApplicationContext will be loaded from the static nested Config class
class OrderServiceTest {
@Configuration
@@ -222,6 +222,7 @@ resource base path). The resource base path is used behind the scenes to create
The following example shows how to use the `@WebAppConfiguration` annotation:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -231,6 +232,7 @@ The following example shows how to use the `@WebAppConfiguration` annotation:
// class body...
}
----
<1> The `@WebAppConfiguration` annotation.
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
@@ -242,6 +244,7 @@ The following example shows how to use the `@WebAppConfiguration` annotation:
}
----
<1> The `@WebAppConfiguration` annotation.
--
To override the default, you can specify a different base resource path by using the
@@ -249,6 +252,7 @@ implicit `value` attribute. Both `classpath:` and `file:` resource prefixes are
supported. If no resource prefix is supplied, the path is assumed to be a file system
resource. The following example shows how to specify a classpath resource:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -270,6 +274,7 @@ resource. The following example shows how to specify a classpath resource:
}
----
<1> Specifying a classpath resource.
--
Note that `@WebAppConfiguration` must be used in conjunction with
@@ -1104,7 +1109,7 @@ annotation. The following example shows how to declare an SQL group:
@SqlGroup({ // <1>
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),
@Sql("/test-user-data.sql")
)}
})
void userTest() {
// run code that uses the test schema and test data
}
@@ -8,12 +8,12 @@
.Java
----
UriComponents uriComponents = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}") // <1>
.queryParam("q", "{q}") // <2>
.fromUriString("https://example.com/hotels/{hotel}") // <1>
.queryParam("q", "{q}") // <2>
.encode() // <3>
.build(); // <4>
URI uri = uriComponents.expand("Westin", "123").toUri(); // <5>
URI uri = uriComponents.expand("Westin", "123").toUri(); // <5>
----
<1> Static factory method with a URI template.
<2> Add or replace URI components.
@@ -25,12 +25,12 @@
.Kotlin
----
val uriComponents = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}") // <1>
.queryParam("q", "{q}") // <2>
.fromUriString("https://example.com/hotels/{hotel}") // <1>
.queryParam("q", "{q}") // <2>
.encode() // <3>
.build() // <4>
val uri = uriComponents.expand("Westin", "123").toUri() // <5>
val uri = uriComponents.expand("Westin", "123").toUri() // <5>
----
<1> Static factory method with a URI template.
<2> Add or replace URI components.
@@ -83,6 +83,7 @@ The {api-spring-framework}/web/bind/annotation/CrossOrigin.html[`@CrossOrigin`]
annotation enables cross-origin requests on annotated controller methods, as the
following example shows:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -121,6 +122,7 @@ following example shows:
}
}
----
--
By default, `@CrossOrigin` allows:
@@ -139,6 +141,7 @@ the `allowOriginPatterns` property may be used to match to a dynamic set of orig
`@CrossOrigin` is supported at the class level, too, and inherited by all methods.
The following example specifies a certain domain and sets `maxAge` to an hour:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -177,10 +180,12 @@ The following example specifies a certain domain and sets `maxAge` to an hour:
}
}
----
--
You can use `@CrossOrigin` at both the class and the method level,
as the following example shows:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -226,6 +231,7 @@ as the following example shows:
----
<1> Using `@CrossOrigin` at the class level.
<2> Using `@CrossOrigin` at the method level.
--
@@ -317,6 +317,7 @@ ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView::cla
We can write a handler function as a lambda, as the following example shows:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -328,6 +329,7 @@ HandlerFunction<ServerResponse> helloWorld =
----
val helloWorld = HandlerFunction<ServerResponse> { ServerResponse.ok().bodyValue("Hello World") }
----
--
That is convenient, but in an application we need multiple functions, and multiple inline
lambda's can get messy.
@@ -335,6 +337,7 @@ Therefore, it is useful to group related handler functions together into a handl
has a similar role as `@Controller` in an annotation-based application.
For example, the following class exposes a reactive `Person` repository:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -409,6 +412,7 @@ Note that `PersonRepository.savePerson(Person)` is a suspending function with no
<3> `getPerson` is a handler function that returns a single person, identified by the `id` path
variable. We retrieve that `Person` from the repository and create a JSON response, if it is
found. If it is not found, we return a 404 Not Found response.
--
[[webflux-fn-handler-validation]]
@@ -156,6 +156,7 @@ application deployed as a WAR), you can declare a Spring-managed bean of type
Netty global resources are shut down when the Spring `ApplicationContext` is closed,
as the following example shows:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -170,11 +171,13 @@ as the following example shows:
@Bean
fun reactorResourceFactory() = ReactorResourceFactory()
----
--
You can also choose not to participate in the global Reactor Netty resources. However,
in this mode, the burden is on you to ensure that all Reactor Netty client and server
instances use shared resources, as the following example shows:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -225,6 +228,7 @@ instances use shared resources, as the following example shows:
<1> Create resources independent of global ones.
<2> Use the `ReactorClientHttpConnector` constructor with resource factory.
<3> Plug the connector into the `WebClient.Builder`.
--
[[webflux-client-builder-reactor-timeout]]
@@ -375,6 +379,7 @@ The following example shows how to customize the JDK `HttpClient`:
The following example shows how to customize Jetty `HttpClient` settings:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -395,6 +400,7 @@ The following example shows how to customize Jetty `HttpClient` settings:
.clientConnector(JettyClientHttpConnector(httpClient))
.build();
----
--
By default, `HttpClient` creates its own resources (`Executor`, `ByteBufferPool`, `Scheduler`),
which remain active until the process exits or `stop()` is called.
@@ -404,6 +410,7 @@ ensure that the resources are shut down when the Spring `ApplicationContext` is
declaring a Spring-managed bean of type `JettyResourceFactory`, as the following example
shows:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -446,6 +453,7 @@ shows:
----
<1> Use the `JettyClientHttpConnector` constructor with resource factory.
<2> Plug the connector into the `WebClient.Builder`.
--
@@ -387,14 +387,14 @@ The code snippets below show using the `HttpHandler` adapters with each server A
----
HttpHandler handler = ...
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
HttpServer.create().host(host).port(port).handle(adapter).bind().block();
HttpServer.create().host(host).port(port).handle(adapter).bindNow();
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
val handler: HttpHandler = ...
val adapter = ReactorHttpHandlerAdapter(handler)
HttpServer.create().host(host).port(port).handle(adapter).bind().block()
HttpServer.create().host(host).port(port).handle(adapter).bindNow()
----
*Undertow*
@@ -1474,6 +1474,7 @@ You can map requests by using glob patterns and wildcards:
Captured URI variables can be accessed with `@PathVariable`, as the following example shows:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -1490,9 +1491,11 @@ Captured URI variables can be accessed with `@PathVariable`, as the following ex
// ...
}
----
--
You can declare URI variables at the class and method levels, as the following example shows:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -1524,6 +1527,7 @@ You can declare URI variables at the class and method levels, as the following e
----
<1> Class-level URI mapping.
<2> Method-level URI mapping.
--
URI variables are automatically converted to the appropriate type or a `TypeMismatchException`
@@ -1543,6 +1547,7 @@ The syntax `{varName:regex}` declares a URI variable with a regular expression t
syntax: `{varName:regex}`. For example, given a URL of `/spring-web-3.0.5.jar`, the following method
extracts the name, version, and file extension:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -1559,6 +1564,7 @@ extracts the name, version, and file extension:
// ...
}
----
--
URI path patterns can also have embedded `${...}` placeholders that are resolved on startup
through `PropertySourcesPlaceholderConfigurer` against local, system, environment, and
@@ -2660,6 +2666,7 @@ content. The best way to handle a file upload form (for example, from a browser)
is through data binding to a <<webflux-ann-modelattrib-method-args, command object>>,
as the following example shows:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -2700,6 +2707,7 @@ as the following example shows:
}
----
--
You can also submit multipart requests from non-browser clients in a RESTful service
scenario. The following example uses a file along with JSON:
@@ -2726,6 +2734,7 @@ Content-Transfer-Encoding: 8bit
You can access individual parts with `@RequestPart`, as the following example shows:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -2749,11 +2758,13 @@ You can access individual parts with `@RequestPart`, as the following example sh
----
<1> Using `@RequestPart` to get the metadata.
<2> Using `@RequestPart` to get the file.
--
To deserialize the raw part content (for example, to JSON -- similar to `@RequestBody`),
you can declare a concrete target `Object`, instead of `Part`, as the following example shows:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -2773,6 +2784,7 @@ you can declare a concrete target `Object`, instead of `Part`, as the following
}
----
<1> Using `@RequestPart` to get the metadata.
--
You can use `@RequestPart` in combination with `jakarta.validation.Valid` or Spring's
`@Validated` annotation, which causes Standard Bean Validation to be applied. Validation
@@ -2781,6 +2793,7 @@ The exception contains a `BindingResult` with the error details and can also be
in the controller method by declaring the argument with an async wrapper and then using
error related operators:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -2798,10 +2811,12 @@ error related operators:
// ...
}
----
--
To access all multipart data as a `MultiValueMap`, you can use `@RequestBody`,
as the following example shows:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -2821,6 +2836,7 @@ as the following example shows:
}
----
<1> Using `@RequestBody`.
--
===== `PartEvent`
@@ -3347,6 +3363,7 @@ do, except for `@ModelAttribute` (command object) arguments. Typically, they are
with a `WebDataBinder` argument, for registrations, and a `void` return value.
The following example uses the `@InitBinder` annotation:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -3381,11 +3398,14 @@ The following example uses the `@InitBinder` annotation:
// ...
}
----
<1> Using the `@InitBinder` annotation.
--
Alternatively, when using a `Formatter`-based setup through a shared
`FormattingConversionService`, you could re-use the same approach and register
controller-specific `Formatter` instances, as the following example shows:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -3417,6 +3437,8 @@ controller-specific `Formatter` instances, as the following example shows:
}
----
<1> Adding a custom formatter (a `DateFormatter`, in this case).
--
[[webflux-ann-initbinder-model-design]]
==== Model Design
@@ -3823,6 +3845,7 @@ Controllers can add explicit support for HTTP caching. We recommend doing so, si
against conditional request headers. A controller can add an `ETag` and `Cache-Control`
settings to a `ResponseEntity`, as the following example shows:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -3856,6 +3879,7 @@ settings to a `ResponseEntity`, as the following example shows:
.body(book)
}
----
--
The preceding example sends a 304 (NOT_MODIFIED) response with an empty body if the comparison
to the conditional request headers indicates the content has not changed. Otherwise, the
@@ -3864,6 +3888,7 @@ to the conditional request headers indicates the content has not changed. Otherw
You can also make the check against conditional request headers in the controller,
as the following example shows:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -3903,6 +3928,7 @@ as the following example shows:
<1> Application-specific calculation.
<2> Response has been set to 304 (NOT_MODIFIED). No further processing.
<3> Continue with request processing.
--
There are three variants for checking conditional requests against `eTag` values, `lastModified`
values, or both. For conditional `GET` and `HEAD` requests, you can set the response to
@@ -40,7 +40,7 @@ as the following example shows:
PersonRepository repository = ...
PersonHandler handler = new PersonHandler(repository);
RouterFunction<ServerResponse> route = route()
RouterFunction<ServerResponse> route = route() // <1>
.GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
.GET("/person", accept(APPLICATION_JSON), handler::listPeople)
.POST("/person", handler::createPerson)
@@ -64,6 +64,7 @@ as the following example shows:
}
}
----
<1> Create router using `route()`.
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
@@ -289,6 +290,7 @@ allows you to send Strings, or other objects as JSON. For example:
We can write a handler function as a lambda, as the following example shows:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -301,6 +303,7 @@ HandlerFunction<ServerResponse> helloWorld =
val helloWorld: (ServerRequest) -> ServerResponse =
{ ServerResponse.ok().body("Hello World") }
----
--
That is convenient, but in an application we need multiple functions, and multiple inline
lambda's can get messy.
@@ -308,6 +311,7 @@ Therefore, it is useful to group related handler functions together into a handl
has a similar role as `@Controller` in an annotation-based application.
For example, the following class exposes a reactive `Person` repository:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -383,6 +387,7 @@ JSON.
<3> `getPerson` is a handler function that returns a single person, identified by the `id` path
variable. We retrieve that `Person` from the repository and create a JSON response, if it is
found. If it is not found, we return a 404 Not Found response.
--
[[webmvc-fn-handler-validation]]
@@ -5144,6 +5144,7 @@ Controllers can add explicit support for HTTP caching. We recommended doing so,
against conditional request headers. A controller can add an `ETag` header and `Cache-Control`
settings to a `ResponseEntity`, as the following example shows:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -5176,6 +5177,7 @@ settings to a `ResponseEntity`, as the following example shows:
.body(book)
}
----
--
The preceding example sends a 304 (NOT_MODIFIED) response with an empty body if the comparison
to the conditional request headers indicates that the content has not changed. Otherwise, the
@@ -5184,6 +5186,7 @@ to the conditional request headers indicates that the content has not changed. O
You can also make the check against conditional request headers in the controller,
as the following example shows:
--
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@@ -5223,6 +5226,7 @@ as the following example shows:
<1> Application-specific calculation.
<2> The response has been set to 304 (NOT_MODIFIED) -- no further processing.
<3> Continue with the request processing.
--
There are three variants for checking conditional requests against `eTag` values, `lastModified`
@@ -1,4 +1,4 @@
[[{chapter}.websocket-intro]]
[id={chapter}.websocket-intro]
= Introduction to WebSocket
The WebSocket protocol, https://tools.ietf.org/html/rfc6455[RFC 6455], provides a standardized
@@ -54,7 +54,7 @@ instructions of the cloud provider related to WebSocket support.
[[{chapter}.websocket-intro-architecture]]
[id={chapter}.websocket-intro-architecture]
== HTTP Versus WebSocket
Even though WebSocket is designed to be HTTP-compatible and starts with an HTTP request,
@@ -80,7 +80,7 @@ In the absence of that, they need to come up with their own conventions.
[[{chapter}.websocket-intro-when-to-use]]
[id={chapter}.websocket-intro-when-to-use]
== When to Use WebSockets
WebSockets can make a web page be dynamic and interactive. However, in many cases,
@@ -184,7 +184,7 @@ HTTP requests. It is also easy to integrate into other HTTP processing scenarios
by invoking `WebSocketHttpRequestHandler`. This is convenient and easy to
understand. However, special considerations apply with regards to JSR-356 runtimes.
The Java WebSocket API (JSR-356) provides two deployment mechanisms. The first
The Jakarta WebSocket API (JSR-356) provides two deployment mechanisms. The first
involves a Servlet container classpath scan (a Servlet 3 feature) at startup.
The other is a registration API to use at Servlet container initialization.
Neither of these mechanism makes it possible to use a single "`front controller`"
@@ -193,17 +193,9 @@ requests -- such as Spring MVC's `DispatcherServlet`.
This is a significant limitation of JSR-356 that Spring's WebSocket support addresses with
server-specific `RequestUpgradeStrategy` implementations even when running in a JSR-356 runtime.
Such strategies currently exist for Tomcat, Jetty, GlassFish, WebLogic, WebSphere, and
Undertow (and WildFly).
NOTE: A request to overcome the preceding limitation in the Java WebSocket API has been
created and can be followed at
https://github.com/eclipse-ee4j/websocket-api/issues/211[eclipse-ee4j/websocket-api#211].
Tomcat, Undertow, and WebSphere provide their own API alternatives that
make it possible to do this, and it is also possible with Jetty. We are hopeful
that more servers will do the same.
Such strategies currently exist for Tomcat, Jetty, GlassFish, WebLogic, WebSphere, and Undertow
(and WildFly). As of Jakarta WebSocket 2.1, a standard request upgrade strategy is available
which Spring chooses on Jakarta EE 10 based web containers such as Tomcat 10.1 and Jetty 12.
A secondary consideration is that Servlet containers with JSR-356 support are expected
to perform a `ServletContainerInitializer` (SCI) scan that can slow down application
@@ -970,7 +962,7 @@ endpoints, over WebSocket with <<websocket-fallback>>, as the following example
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/portfolio").withSockJS(); // <1>
registry.addEndpoint("/portfolio").withSockJS(); // <1>
}
@Override
@@ -39,9 +39,8 @@ class SampleReflectionRuntimeHintsTests {
void shouldRegisterReflectionHints() {
RuntimeHints runtimeHints = new RuntimeHints();
// Call a RuntimeHintsRegistrar that contributes hints like:
runtimeHints.reflection().registerType(SpringVersion.class, typeHint -> {
typeHint.withMethod("getVersion", List.of(), ExecutableMode.INVOKE);
});
runtimeHints.reflection().registerType(SpringVersion.class, typeHint ->
typeHint.withMethod("getVersion", List.of(), ExecutableMode.INVOKE));
// Invoke the relevant piece of code we want to test within a recording lambda
RuntimeHintsInvocations invocations = RuntimeHintsRecorder.record(() -> {
@@ -0,0 +1,71 @@
/*
* Copyright 2002-2022 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.docs.integration.observability.config.conventions;
import io.micrometer.common.KeyValue;
import io.micrometer.common.KeyValues;
import org.springframework.http.server.observation.ServerHttpObservationDocumentation;
import org.springframework.http.server.observation.ServerRequestObservationContext;
import org.springframework.http.server.observation.ServerRequestObservationConvention;
public class CustomServerRequestObservationConvention implements ServerRequestObservationConvention {
@Override
public String getName() {
// will be used as the metric name
return "http.server.requests";
}
@Override
public String getContextualName(ServerRequestObservationContext context) {
// will be used for the trace name
return "http " + context.getCarrier().getMethod().toLowerCase();
}
@Override
public KeyValues getLowCardinalityKeyValues(ServerRequestObservationContext context) {
return KeyValues.of(method(context), status(context), exception(context));
}
@Override
public KeyValues getHighCardinalityKeyValues(ServerRequestObservationContext context) {
return KeyValues.of(httpUrl(context));
}
private KeyValue method(ServerRequestObservationContext context) {
// You should reuse as much as possible the corresponding ObservationDocumentation for key names
return KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.METHOD, context.getCarrier().getMethod());
}
// @fold:on // status(), exception(), httpUrl()...
private KeyValue status(ServerRequestObservationContext context) {
return KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.STATUS, String.valueOf(context.getResponse().getStatus()));
}
private KeyValue exception(ServerRequestObservationContext context) {
String exception = (context.getError() != null) ? context.getError().getClass().getSimpleName() : KeyValue.NONE_VALUE;
return KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.EXCEPTION, exception);
}
private KeyValue httpUrl(ServerRequestObservationContext context) {
return KeyValue.of(ServerHttpObservationDocumentation.HighCardinalityKeyNames.HTTP_URL, context.getCarrier().getRequestURI());
}
// @fold:off
}
@@ -0,0 +1,37 @@
/*
* Copyright 2002-2022 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.docs.integration.observability.config.conventions;
import io.micrometer.common.KeyValue;
import io.micrometer.common.KeyValues;
import org.springframework.http.server.observation.DefaultServerRequestObservationConvention;
import org.springframework.http.server.observation.ServerRequestObservationContext;
public class ExtendedServerRequestObservationConvention extends DefaultServerRequestObservationConvention {
@Override
public KeyValues getLowCardinalityKeyValues(ServerRequestObservationContext context) {
// here, we just want to have an additional KeyValue to the observation, keeping the default values
return super.getLowCardinalityKeyValues(context).and(custom(context));
}
private KeyValue custom(ServerRequestObservationContext context) {
return KeyValue.of("custom.method", context.getCarrier().getMethod());
}
}
@@ -0,0 +1,38 @@
/*
* Copyright 2002-2022 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.docs.integration.observability.config.conventions;
import io.micrometer.common.KeyValue;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationFilter;
import org.springframework.http.server.observation.ServerRequestObservationContext;
public class ServerRequestObservationFilter implements ObservationFilter {
@Override
public Observation.Context map(Observation.Context context) {
if (context instanceof ServerRequestObservationContext serverContext) {
context.setName("custom.observation.name");
context.addLowCardinalityKeyValue(KeyValue.of("project", "spring"));
String customAttribute = (String) serverContext.getCarrier().getAttribute("customAttribute");
context.addLowCardinalityKeyValue(KeyValue.of("custom.attribute", customAttribute));
}
return context;
}
}
+19 -19
View File
@@ -8,28 +8,28 @@ javaPlatform {
dependencies {
api(platform("com.fasterxml.jackson:jackson-bom:2.14.1"))
api(platform("io.micrometer:micrometer-bom:1.10.0"))
api(platform("io.micrometer:micrometer-bom:1.10.2"))
api(platform("io.netty:netty-bom:4.1.85.Final"))
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
api(platform("io.projectreactor:reactor-bom:2022.0.0"))
api(platform("io.projectreactor:reactor-bom:2022.0.1"))
api(platform("io.rsocket:rsocket-bom:1.1.3"))
api(platform("org.apache.groovy:groovy-bom:4.0.5"))
api(platform("org.apache.groovy:groovy-bom:4.0.6"))
api(platform("org.apache.logging.log4j:log4j-bom:2.19.0"))
api(platform("org.eclipse.jetty:jetty-bom:11.0.12"))
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.1"))
api(platform("org.mockito:mockito-bom:4.8.1"))
api(platform("org.mockito:mockito-bom:4.9.0"))
constraints {
api("com.fasterxml.woodstox:woodstox-core:6.4.0")
api("com.fasterxml:aalto-xml:1.3.1")
api("com.github.ben-manes.caffeine:caffeine:3.1.1")
api("com.fasterxml.woodstox:woodstox-core:6.4.0")
api("com.github.ben-manes.caffeine:caffeine:3.1.2")
api("com.github.librepdf:openpdf:1.3.30")
api("com.google.code.findbugs:findbugs:3.0.1")
api("com.google.code.findbugs:jsr305:3.0.2")
api("com.google.code.gson:gson:2.9.1")
api("com.google.protobuf:protobuf-java-util:3.21.5")
api("com.google.code.gson:gson:2.10")
api("com.google.protobuf:protobuf-java-util:3.21.11")
api("com.googlecode.protobuf-java-format:protobuf-java-format:1.4")
api("com.h2database:h2:2.1.214")
api("com.jayway.jsonpath:json-path:2.7.0")
@@ -45,7 +45,7 @@ dependencies {
api("com.thoughtworks.xstream:xstream:1.4.19")
api("commons-io:commons-io:2.11.0")
api("de.bechte.junit:junit-hierarchicalcontextrunner:4.12.1")
api("info.picocli:picocli:4.6.3")
api("info.picocli:picocli:4.7.0")
api("io.micrometer:context-propagation:1.0.0")
api("io.mockk:mockk:1.12.1")
api("io.projectreactor.netty:reactor-netty5-http:2.0.0-M3")
@@ -87,7 +87,7 @@ dependencies {
api("jaxen:jaxen:1.2.0")
api("junit:junit:4.13.2")
api("net.sf.jopt-simple:jopt-simple:5.0.4")
api("net.sourceforge.htmlunit:htmlunit:2.66.0")
api("net.sourceforge.htmlunit:htmlunit:2.67.0")
api("org.apache-extras.beanshell:bsh:2.0b6")
api("org.apache.activemq:activemq-broker:5.16.2")
api("org.apache.activemq:activemq-kahadb-store:5.16.2")
@@ -96,9 +96,9 @@ dependencies {
api("org.apache.derby:derby:10.16.1.1")
api("org.apache.derby:derbyclient:10.16.1.1")
api("org.apache.derby:derbytools:10.16.1.1")
api("org.apache.httpcomponents.client5:httpclient5:5.1.3")
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.1.3")
api("org.apache.poi:poi-ooxml:5.2.2")
api("org.apache.httpcomponents.client5:httpclient5:5.2")
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.2")
api("org.apache.poi:poi-ooxml:5.2.3")
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.1")
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.1")
api("org.apache.tomcat:tomcat-util:10.1.1")
@@ -124,11 +124,11 @@ dependencies {
api("org.glassfish:jakarta.el:4.0.2")
api("org.graalvm.sdk:graal-sdk:22.3.0")
api("org.hamcrest:hamcrest:2.2")
api("org.hibernate:hibernate-core-jakarta:5.6.12.Final")
api("org.hibernate:hibernate-core-jakarta:5.6.14.Final")
api("org.hibernate:hibernate-validator:7.0.5.Final")
api("org.hsqldb:hsqldb:2.7.0")
api("org.hsqldb:hsqldb:2.7.1")
api("org.javamoney:moneta:1.4.2")
api("org.jruby:jruby:9.3.8.0")
api("org.jruby:jruby:9.4.0.0")
api("org.junit.support:testng-engine:1.0.4")
api("org.mozilla:rhino:1.7.11")
api("org.ogce:xpp3:1.1.6")
@@ -137,12 +137,12 @@ dependencies {
api("org.seleniumhq.selenium:htmlunit-driver:2.66.0")
api("org.seleniumhq.selenium:selenium-java:3.141.59")
api("org.skyscreamer:jsonassert:1.5.0")
api("org.slf4j:slf4j-api:2.0.3")
api("org.slf4j:slf4j-api:2.0.5")
api("org.testng:testng:7.6.1")
api("org.webjars:underscorejs:1.8.3")
api("org.webjars:webjars-locator-core:0.48")
api("org.webjars:webjars-locator-core:0.52")
api("org.xmlunit:xmlunit-assertj:2.9.0")
api("org.xmlunit:xmlunit-matchers:2.9.0")
api("org.yaml:snakeyaml:1.30")
api("org.yaml:snakeyaml:1.33")
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
version=6.0.2-SNAPSHOT
version=6.0.3
org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m
+2 -2
View File
@@ -8,8 +8,8 @@ apply plugin: 'me.champeau.jmh'
apply from: "$rootDir/gradle/publications.gradle"
dependencies {
jmh 'org.openjdk.jmh:jmh-core:1.32'
jmh 'org.openjdk.jmh:jmh-generator-annprocess:1.32'
jmh 'org.openjdk.jmh:jmh-core:1.36'
jmh 'org.openjdk.jmh:jmh-generator-annprocess:1.36'
jmh 'net.sf.jopt-simple:jopt-simple'
}
Binary file not shown.
+2 -1
View File
@@ -1,5 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+14 -4
View File
@@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -80,10 +80,10 @@ do
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
@@ -143,12 +143,16 @@ fi
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -205,6 +209,12 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
Vendored
+9 -6
View File
@@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -25,7 +25,8 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -34,7 +34,7 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for scoped proxy use in conjunction with aop: namespace.
* Deemed an integration test because .web mocks and application contexts are required.
* Deemed an integration test because web mocks and application contexts are required.
*
* @author Rob Harrop
* @author Juergen Hoeller
@@ -61,7 +61,7 @@ class EnableCachingIntegrationTests {
ctx.register(Config.class, AspectJCacheConfig.class);
// 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.
// in integration-tests that it's not found.
assertThatException().isThrownBy(ctx::refresh)
.withMessageContaining("AspectJCachingConfiguration");
}
@@ -97,7 +97,7 @@ class EnableTransactionManagementIntegrationTests {
ctx.register(Config.class, AspectJTxConfig.class);
// 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.
// in integration-tests that it's not found.
assertThatException()
.isThrownBy(ctx::refresh)
.withMessageContaining("AspectJJtaTransactionManagementConfiguration");
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,6 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
@@ -163,24 +162,22 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
/**
* Class modelling an AspectJ annotation, exposing its type enumeration and
* Class modeling an AspectJ annotation, exposing its type enumeration and
* pointcut String.
* @param <A> the annotation type
*/
protected static class AspectJAnnotation<A extends Annotation> {
private static final String[] EXPRESSION_ATTRIBUTES = new String[] {"pointcut", "value"};
private static final String[] EXPRESSION_ATTRIBUTES = {"pointcut", "value"};
private static Map<Class<?>, AspectJAnnotationType> annotationTypeMap = new HashMap<>(8);
static {
annotationTypeMap.put(Pointcut.class, AspectJAnnotationType.AtPointcut);
annotationTypeMap.put(Around.class, AspectJAnnotationType.AtAround);
annotationTypeMap.put(Before.class, AspectJAnnotationType.AtBefore);
annotationTypeMap.put(After.class, AspectJAnnotationType.AtAfter);
annotationTypeMap.put(AfterReturning.class, AspectJAnnotationType.AtAfterReturning);
annotationTypeMap.put(AfterThrowing.class, AspectJAnnotationType.AtAfterThrowing);
}
private static final Map<Class<?>, AspectJAnnotationType> annotationTypeMap = Map.of(
Pointcut.class, AspectJAnnotationType.AtPointcut, //
Around.class, AspectJAnnotationType.AtAround, //
Before.class, AspectJAnnotationType.AtBefore, //
After.class, AspectJAnnotationType.AtAfter, //
AfterReturning.class, AspectJAnnotationType.AtAfterReturning, //
AfterThrowing.class, AspectJAnnotationType.AtAfterThrowing //
);
private final A annotation;
@@ -196,7 +193,7 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
try {
this.pointcutExpression = resolveExpression(annotation);
Object argNames = AnnotationUtils.getValue(annotation, "argNames");
this.argumentNames = (argNames instanceof String ? (String) argNames : "");
this.argumentNames = (argNames instanceof String names ? names : "");
}
catch (Exception ex) {
throw new IllegalArgumentException(annotation + " is not a valid AspectJ annotation", ex);
@@ -214,13 +211,11 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
private String resolveExpression(A annotation) {
for (String attributeName : EXPRESSION_ATTRIBUTES) {
Object val = AnnotationUtils.getValue(annotation, attributeName);
if (val instanceof String str) {
if (!str.isEmpty()) {
return str;
}
if (val instanceof String str && !str.isEmpty()) {
return str;
}
}
throw new IllegalStateException("Failed to resolve expression: " + annotation);
throw new IllegalStateException("Failed to resolve expression in: " + annotation);
}
public AspectJAnnotationType getAnnotationType() {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
package org.springframework.beans;
import java.beans.BeanDescriptor;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
@@ -52,6 +53,10 @@ class SimpleBeanInfoFactory implements BeanInfoFactory, Ordered {
PropertyDescriptorUtils.determineBasicProperties(beanClass);
return new SimpleBeanInfo() {
@Override
public BeanDescriptor getBeanDescriptor() {
return new BeanDescriptor(beanClass);
}
@Override
public PropertyDescriptor[] getPropertyDescriptors() {
return pds.toArray(PropertyDescriptorUtils.EMPTY_PROPERTY_DESCRIPTOR_ARRAY);
@@ -25,7 +25,6 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -46,6 +45,7 @@ import org.springframework.aot.generate.GeneratedMethod;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.support.ClassHintUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValues;
@@ -1018,10 +1018,10 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
private void registerProxyIfNecessary(RuntimeHints runtimeHints, DependencyDescriptor dependencyDescriptor) {
if (this.candidateResolver != null) {
Class<?> proxyType =
Class<?> proxyClass =
this.candidateResolver.getLazyResolutionProxyClass(dependencyDescriptor, null);
if (proxyType != null && Proxy.isProxyClass(proxyType)) {
runtimeHints.proxies().registerJdkProxy(proxyType.getInterfaces());
if (proxyClass != null) {
ClassHintUtils.registerProxyIfNecessary(proxyClass, runtimeHints);
}
}
}
@@ -134,10 +134,12 @@ class BeanDefinitionMethodGenerator {
type.addJavadoc("Bean definitions for {@link $T}", topLevelClassName);
type.addModifiers(Modifier.PUBLIC);
});
List<String> names = target.simpleNames();
if (names.size() == 1) {
return generatedClass;
}
List<String> namesToProcess = names.subList(1, names.size());
ClassName currentTargetClassName = topLevelClassName;
GeneratedClass tmp = generatedClass;
@@ -148,8 +150,7 @@ class BeanDefinitionMethodGenerator {
return tmp;
}
private static GeneratedClass createInnerClass(GeneratedClass generatedClass,
String name, ClassName target) {
private static GeneratedClass createInnerClass(GeneratedClass generatedClass, String name, ClassName target) {
return generatedClass.getOrAdd(name, type -> {
type.addJavadoc("Bean definitions for {@link $T}", target);
type.addModifiers(Modifier.PUBLIC, Modifier.STATIC);
@@ -167,16 +168,16 @@ class BeanDefinitionMethodGenerator {
return codeFragments;
}
private GeneratedMethod generateBeanDefinitionMethod(
GenerationContext generationContext, ClassName className,
GeneratedMethods generatedMethods, BeanRegistrationCodeFragments codeFragments,
Modifier modifier) {
private GeneratedMethod generateBeanDefinitionMethod(GenerationContext generationContext,
ClassName className, GeneratedMethods generatedMethods,
BeanRegistrationCodeFragments codeFragments, Modifier modifier) {
BeanRegistrationCodeGenerator codeGenerator = new BeanRegistrationCodeGenerator(
className, generatedMethods, this.registeredBean,
this.constructorOrFactoryMethod, codeFragments);
this.aotContributions.forEach(aotContribution -> aotContribution
.applyTo(generationContext, codeGenerator));
this.aotContributions.forEach(aotContribution -> aotContribution.applyTo(generationContext, codeGenerator));
return generatedMethods.add("getBeanDefinition", method -> {
method.addJavadoc("Get the $L definition for '$L'",
(!this.registeredBean.isInnerBean()) ? "bean" : "inner-bean",
@@ -87,8 +87,7 @@ class BeanDefinitionMethodGeneratorFactory {
* {@link BeanRegistrationAotProcessor} provided contributions.
* @param registeredBean the registered bean
* @param currentPropertyName the property name that this bean belongs to
* @return a new {@link BeanDefinitionMethodGenerator} instance or
* {@code null}
* @return a new {@link BeanDefinitionMethodGenerator} instance or {@code null}
*/
@Nullable
BeanDefinitionMethodGenerator getBeanDefinitionMethodGenerator(
@@ -97,8 +96,7 @@ class BeanDefinitionMethodGeneratorFactory {
if (isExcluded(registeredBean)) {
return null;
}
List<BeanRegistrationAotContribution> contributions = getAotContributions(
registeredBean);
List<BeanRegistrationAotContribution> contributions = getAotContributions(registeredBean);
return new BeanDefinitionMethodGenerator(this, registeredBean,
currentPropertyName, contributions);
}
@@ -110,8 +108,7 @@ class BeanDefinitionMethodGeneratorFactory {
* {@link BeanDefinitionMethodGenerator} will include all
* {@link BeanRegistrationAotProcessor} provided contributions.
* @param registeredBean the registered bean
* @return a new {@link BeanDefinitionMethodGenerator} instance or
* {@code null}
* @return a new {@link BeanDefinitionMethodGenerator} instance or {@code null}
*/
@Nullable
BeanDefinitionMethodGenerator getBeanDefinitionMethodGenerator(RegisteredBean registeredBean) {
@@ -142,19 +139,16 @@ class BeanDefinitionMethodGeneratorFactory {
}
if (BeanRegistrationAotProcessor.class.isAssignableFrom(beanClass)) {
BeanRegistrationAotProcessor processor = this.aotProcessors.findByBeanName(registeredBean.getBeanName());
return (processor == null) || processor.isBeanExcludedFromAotProcessing();
return (processor == null || processor.isBeanExcludedFromAotProcessing());
}
return false;
}
private List<BeanRegistrationAotContribution> getAotContributions(
RegisteredBean registeredBean) {
private List<BeanRegistrationAotContribution> getAotContributions(RegisteredBean registeredBean) {
String beanName = registeredBean.getBeanName();
List<BeanRegistrationAotContribution> contributions = new ArrayList<>();
for (BeanRegistrationAotProcessor aotProcessor : this.aotProcessors) {
BeanRegistrationAotContribution contribution = aotProcessor
.processAheadOfTime(registeredBean);
BeanRegistrationAotContribution contribution = aotProcessor.processAheadOfTime(registeredBean);
if (contribution != null) {
logger.trace(LogMessage.format(
"Adding bean registration AOT contribution %S from %S to '%S'",
@@ -155,7 +155,7 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
public BeanInstanceSupplier<T> withGenerator(
ThrowingBiFunction<RegisteredBean, AutowiredArguments, T> generator) {
Assert.notNull(generator, "'generator' must not be null");
return new BeanInstanceSupplier<T>(this.lookup, generator, this.shortcuts);
return new BeanInstanceSupplier<>(this.lookup, generator, this.shortcuts);
}
/**
@@ -196,7 +196,7 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
* that uses the shortcuts
*/
public BeanInstanceSupplier<T> withShortcuts(String... beanNames) {
return new BeanInstanceSupplier<T>(this.lookup, this.generator, beanNames);
return new BeanInstanceSupplier<>(this.lookup, this.generator, beanNames);
}
@Override
@@ -64,8 +64,7 @@ public class BeanRegistrationCodeFragmentsDecorator implements BeanRegistrationC
}
@Override
public CodeBlock generateSetBeanDefinitionPropertiesCode(
GenerationContext generationContext,
public CodeBlock generateSetBeanDefinitionPropertiesCode(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode, RootBeanDefinition beanDefinition,
Predicate<String> attributeFilter) {
@@ -74,8 +73,7 @@ public class BeanRegistrationCodeFragmentsDecorator implements BeanRegistrationC
}
@Override
public CodeBlock generateSetBeanInstanceSupplierCode(
GenerationContext generationContext,
public CodeBlock generateSetBeanInstanceSupplierCode(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode, CodeBlock instanceSupplierCode,
List<MethodReference> postProcessors) {
@@ -85,8 +83,8 @@ public class BeanRegistrationCodeFragmentsDecorator implements BeanRegistrationC
@Override
public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode,
Executable constructorOrFactoryMethod, boolean allowDirectSupplierShortcut) {
BeanRegistrationCode beanRegistrationCode, Executable constructorOrFactoryMethod,
boolean allowDirectSupplierShortcut) {
return this.delegate.generateInstanceSupplierCode(generationContext,
beanRegistrationCode, constructorOrFactoryMethod, allowDirectSupplierShortcut);
@@ -22,6 +22,7 @@ import java.util.Map;
import org.springframework.beans.factory.aot.BeanRegistrationsAotContribution.Registration;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.lang.Nullable;
/**
* {@link BeanFactoryInitializationAotProcessor} that contributes code to
@@ -35,19 +36,22 @@ import org.springframework.beans.factory.support.RegisteredBean;
class BeanRegistrationsAotProcessor implements BeanFactoryInitializationAotProcessor {
@Override
@Nullable
public BeanRegistrationsAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
BeanDefinitionMethodGeneratorFactory beanDefinitionMethodGeneratorFactory =
new BeanDefinitionMethodGeneratorFactory(beanFactory);
Map<String, Registration> registrations = new LinkedHashMap<>();
for (String beanName : beanFactory.getBeanDefinitionNames()) {
RegisteredBean registeredBean = RegisteredBean.of(beanFactory, beanName);
BeanDefinitionMethodGenerator beanDefinitionMethodGenerator = beanDefinitionMethodGeneratorFactory
.getBeanDefinitionMethodGenerator(registeredBean);
BeanDefinitionMethodGenerator beanDefinitionMethodGenerator =
beanDefinitionMethodGeneratorFactory.getBeanDefinitionMethodGenerator(registeredBean);
if (beanDefinitionMethodGenerator != null) {
registrations.put(beanName, new Registration(beanDefinitionMethodGenerator,
beanFactory.getAliases(beanName)));
}
}
if (registrations.isEmpty()) {
return null;
}
@@ -46,16 +46,16 @@ import org.springframework.util.function.ThrowingSupplier;
* Internal code generator to create an {@link InstanceSupplier}, usually in
* the form of a {@link BeanInstanceSupplier} that retains the executable
* that is used to instantiate the bean.
* <p>
* Generated code is usually a method reference that generate the
* {@link BeanInstanceSupplier}, but some shortcut can be used as well such
* as:
*
* <p>Generated code is usually a method reference that generate the
* {@link BeanInstanceSupplier}, but some shortcut can be used as well such as:
* <pre class="code">
* {@code InstanceSupplier.of(TheGeneratedClass::getMyBeanInstance);}
* </pre>
*
* @author Phillip Webb
* @author Stephane Nicoll
* @author Juergen Hoeller
* @since 6.0
*/
class InstanceSupplierCodeGenerator {
@@ -90,9 +90,7 @@ class InstanceSupplierCodeGenerator {
}
CodeBlock generateCode(RegisteredBean registeredBean,
Executable constructorOrFactoryMethod) {
CodeBlock generateCode(RegisteredBean registeredBean, Executable constructorOrFactoryMethod) {
if (constructorOrFactoryMethod instanceof Constructor<?> constructor) {
return generateCodeForConstructor(registeredBean, constructor);
}
@@ -108,6 +106,7 @@ class InstanceSupplierCodeGenerator {
Class<?> beanClass = registeredBean.getBeanClass();
Class<?> declaringClass = constructor.getDeclaringClass();
boolean dependsOnBean = ClassUtils.isInnerClass(declaringClass);
Visibility accessVisibility = getAccessVisibility(registeredBean, constructor);
if (accessVisibility != Visibility.PRIVATE) {
return generateCodeForAccessibleConstructor(beanName, beanClass, constructor,
@@ -121,6 +120,7 @@ class InstanceSupplierCodeGenerator {
this.generationContext.getRuntimeHints().reflection().registerConstructor(
constructor, ExecutableMode.INTROSPECT);
if (!dependsOnBean && constructor.getParameterCount() == 0) {
if (!this.allowDirectSupplierShortcut) {
return CodeBlock.of("$T.using($T::new)", InstanceSupplier.class, declaringClass);
@@ -130,6 +130,7 @@ class InstanceSupplierCodeGenerator {
}
return CodeBlock.of("$T.of($T::new)", ThrowingSupplier.class, declaringClass);
}
GeneratedMethod generatedMethod = generateGetInstanceSupplierMethod(method ->
buildGetInstanceMethodForConstructor(method, beanName, beanClass, constructor,
declaringClass, dependsOnBean, PRIVATE_STATIC));
@@ -141,6 +142,7 @@ class InstanceSupplierCodeGenerator {
this.generationContext.getRuntimeHints().reflection()
.registerConstructor(constructor, ExecutableMode.INVOKE);
GeneratedMethod generatedMethod = generateGetInstanceSupplierMethod(method -> {
method.addJavadoc("Get the bean instance supplier for '$L'.", beanName);
method.addModifiers(PRIVATE_STATIC);
@@ -148,6 +150,7 @@ class InstanceSupplierCodeGenerator {
int parameterOffset = (!dependsOnBean) ? 0 : 1;
method.addStatement(generateResolverForConstructor(beanClass, constructor, parameterOffset));
});
return generateReturnStatement(generatedMethod);
}
@@ -158,14 +161,17 @@ class InstanceSupplierCodeGenerator {
method.addJavadoc("Get the bean instance supplier for '$L'.", beanName);
method.addModifiers(modifiers);
method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, beanClass));
int parameterOffset = (!dependsOnBean) ? 0 : 1;
CodeBlock.Builder code = CodeBlock.builder();
code.add(generateResolverForConstructor(beanClass, constructor, parameterOffset));
boolean hasArguments = constructor.getParameterCount() > 0;
CodeBlock arguments = hasArguments ?
new AutowiredArgumentsCodeGenerator(declaringClass, constructor)
.generateCode(constructor.getParameterTypes(), parameterOffset)
: NO_ARGS;
CodeBlock newInstance = generateNewInstanceCodeForConstructor(dependsOnBean, declaringClass, arguments);
code.add(generateWithGeneratorCode(hasArguments, newInstance));
method.addStatement(code.build());
@@ -184,92 +190,99 @@ class InstanceSupplierCodeGenerator {
if (!dependsOnBean) {
return CodeBlock.of("new $T($L)", declaringClass, args);
}
return CodeBlock.of("$L.getBeanFactory().getBean($T.class).new $L($L)",
REGISTERED_BEAN_PARAMETER_NAME, declaringClass.getEnclosingClass(),
declaringClass.getSimpleName(), args);
}
private CodeBlock generateCodeForFactoryMethod(RegisteredBean registeredBean,
Method factoryMethod) {
private CodeBlock generateCodeForFactoryMethod(RegisteredBean registeredBean, Method factoryMethod) {
String beanName = registeredBean.getBeanName();
Class<?> beanClass = registeredBean.getBeanClass();
Class<?> declaringClass = ClassUtils
.getUserClass(factoryMethod.getDeclaringClass());
Class<?> declaringClass = ClassUtils.getUserClass(factoryMethod.getDeclaringClass());
boolean dependsOnBean = !Modifier.isStatic(factoryMethod.getModifiers());
Visibility accessVisibility = getAccessVisibility(registeredBean, factoryMethod);
if (accessVisibility != Visibility.PRIVATE) {
return generateCodeForAccessibleFactoryMethod(
beanName, beanClass, factoryMethod, declaringClass, dependsOnBean);
beanName, factoryMethod, declaringClass, dependsOnBean);
}
return generateCodeForInaccessibleFactoryMethod(beanName, beanClass, factoryMethod, declaringClass);
return generateCodeForInaccessibleFactoryMethod(beanName, factoryMethod, declaringClass);
}
private CodeBlock generateCodeForAccessibleFactoryMethod(String beanName,
Class<?> beanClass, Method factoryMethod, Class<?> declaringClass, boolean dependsOnBean) {
Method factoryMethod, Class<?> declaringClass, boolean dependsOnBean) {
this.generationContext.getRuntimeHints().reflection().registerMethod(
factoryMethod, ExecutableMode.INTROSPECT);
if (!dependsOnBean && factoryMethod.getParameterCount() == 0) {
Class<?> suppliedType = ClassUtils.resolvePrimitiveIfNecessary(factoryMethod.getReturnType());
CodeBlock.Builder code = CodeBlock.builder();
code.add("$T.<$T>forFactoryMethod($T.class, $S)", BeanInstanceSupplier.class,
beanClass, declaringClass, factoryMethod.getName());
suppliedType, declaringClass, factoryMethod.getName());
code.add(".withGenerator($T::$L)", declaringClass, factoryMethod.getName());
return code.build();
}
GeneratedMethod getInstanceMethod = generateGetInstanceSupplierMethod(method ->
buildGetInstanceMethodForFactoryMethod(method, beanName, beanClass, factoryMethod,
buildGetInstanceMethodForFactoryMethod(method, beanName, factoryMethod,
declaringClass, dependsOnBean, PRIVATE_STATIC));
return generateReturnStatement(getInstanceMethod);
}
private CodeBlock generateCodeForInaccessibleFactoryMethod(String beanName, Class<?> beanClass,
Method factoryMethod, Class<?> declaringClass) {
private CodeBlock generateCodeForInaccessibleFactoryMethod(
String beanName, Method factoryMethod, Class<?> declaringClass) {
this.generationContext.getRuntimeHints().reflection().registerMethod(factoryMethod, ExecutableMode.INVOKE);
GeneratedMethod getInstanceMethod = generateGetInstanceSupplierMethod(method -> {
Class<?> suppliedType = ClassUtils.resolvePrimitiveIfNecessary(factoryMethod.getReturnType());
method.addJavadoc("Get the bean instance supplier for '$L'.", beanName);
method.addModifiers(PRIVATE_STATIC);
method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, beanClass));
method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, suppliedType));
method.addStatement(generateInstanceSupplierForFactoryMethod(
beanClass, factoryMethod, declaringClass, factoryMethod.getName()));
factoryMethod, suppliedType, declaringClass, factoryMethod.getName()));
});
return generateReturnStatement(getInstanceMethod);
}
private void buildGetInstanceMethodForFactoryMethod(MethodSpec.Builder method,
String beanName, Class<?> beanClass, Method factoryMethod, Class<?> declaringClass,
String beanName, Method factoryMethod, Class<?> declaringClass,
boolean dependsOnBean, javax.lang.model.element.Modifier... modifiers) {
String factoryMethodName = factoryMethod.getName();
Class<?> suppliedType = ClassUtils.resolvePrimitiveIfNecessary(factoryMethod.getReturnType());
method.addJavadoc("Get the bean instance supplier for '$L'.", beanName);
method.addModifiers(modifiers);
method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, beanClass));
method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, suppliedType));
CodeBlock.Builder code = CodeBlock.builder();
code.add(generateInstanceSupplierForFactoryMethod(
beanClass, factoryMethod, declaringClass, factoryMethodName));
factoryMethod, suppliedType, declaringClass, factoryMethodName));
boolean hasArguments = factoryMethod.getParameterCount() > 0;
CodeBlock arguments = hasArguments ?
new AutowiredArgumentsCodeGenerator(declaringClass, factoryMethod)
.generateCode(factoryMethod.getParameterTypes())
: NO_ARGS;
CodeBlock newInstance = generateNewInstanceCodeForMethod(
dependsOnBean, declaringClass, factoryMethodName, arguments);
code.add(generateWithGeneratorCode(hasArguments, newInstance));
method.addStatement(code.build());
}
private CodeBlock generateInstanceSupplierForFactoryMethod(Class<?> beanClass,
Method factoryMethod, Class<?> declaringClass, String factoryMethodName) {
private CodeBlock generateInstanceSupplierForFactoryMethod(Method factoryMethod,
Class<?> suppliedType, Class<?> declaringClass, String factoryMethodName) {
if (factoryMethod.getParameterCount() == 0) {
return CodeBlock.of("return $T.<$T>forFactoryMethod($T.class, $S)",
BeanInstanceSupplier.class, beanClass, declaringClass,
factoryMethodName);
BeanInstanceSupplier.class, suppliedType, declaringClass, factoryMethodName);
}
CodeBlock parameterTypes = generateParameterTypesCode(factoryMethod.getParameterTypes(), 0);
return CodeBlock.of("return $T.<$T>forFactoryMethod($T.class, $S, $L)",
BeanInstanceSupplier.class, beanClass, declaringClass, factoryMethodName, parameterTypes);
BeanInstanceSupplier.class, suppliedType, declaringClass, factoryMethodName, parameterTypes);
}
private CodeBlock generateNewInstanceCodeForMethod(boolean dependsOnBean,
@@ -288,9 +301,9 @@ class InstanceSupplierCodeGenerator {
}
private CodeBlock generateWithGeneratorCode(boolean hasArguments, CodeBlock newInstance) {
CodeBlock lambdaArguments = (hasArguments
? CodeBlock.of("($L, $L)", REGISTERED_BEAN_PARAMETER_NAME, ARGS_PARAMETER_NAME)
: CodeBlock.of("($L)", REGISTERED_BEAN_PARAMETER_NAME));
CodeBlock lambdaArguments = (hasArguments ?
CodeBlock.of("($L, $L)", REGISTERED_BEAN_PARAMETER_NAME, ARGS_PARAMETER_NAME) :
CodeBlock.of("($L)", REGISTERED_BEAN_PARAMETER_NAME));
Builder code = CodeBlock.builder();
code.add("\n");
code.indent().indent();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@@ -360,6 +360,25 @@ public class ConstructorArgumentValues {
return valueHolder;
}
/**
* Determine whether at least one argument value refers to a name.
* @since 6.0.3
* @see ValueHolder#getName()
*/
public boolean containsNamedArgument() {
for (ValueHolder valueHolder : this.indexedArgumentValues.values()) {
if (valueHolder.getName() != null) {
return true;
}
}
for (ValueHolder valueHolder : this.genericArgumentValues) {
if (valueHolder.getName() != null) {
return true;
}
}
return false;
}
/**
* Return the number of argument values held in this instance,
* counting both indexed and generic argument values.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@@ -143,7 +143,7 @@ public class TypedStringValue implements BeanMetadataElement {
}
/**
* Return whether this typed String value carries a target type .
* Return whether this typed String value carries a target type.
*/
public boolean hasTargetType() {
return (this.targetType instanceof Class);
@@ -184,8 +184,9 @@ public abstract class YamlProcessor {
protected Yaml createYaml() {
LoaderOptions loaderOptions = new LoaderOptions();
loaderOptions.setAllowDuplicateKeys(false);
return new Yaml(new FilteringConstructor(loaderOptions), new Representer(),
new DumperOptions(), loaderOptions);
DumperOptions dumperOptions = new DumperOptions();
return new Yaml(new FilteringConstructor(loaderOptions), new Representer(dumperOptions),
dumperOptions, loaderOptions);
}
private boolean process(MatchCallback callback, Yaml yaml, Resource resource) {
@@ -232,11 +232,14 @@ class ConstructorResolver {
Class<?>[] paramTypes = candidate.getParameterTypes();
if (resolvedValues != null) {
try {
String[] paramNames = ConstructorPropertiesChecker.evaluate(candidate, parameterCount);
if (paramNames == null) {
ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer();
if (pnd != null) {
paramNames = pnd.getParameterNames(candidate);
String[] paramNames = null;
if (resolvedValues.containsNamedArgument()) {
paramNames = ConstructorPropertiesChecker.evaluate(candidate, parameterCount);
if (paramNames == null) {
ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer();
if (pnd != null) {
paramNames = pnd.getParameterNames(candidate);
}
}
}
argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames,
@@ -536,9 +539,11 @@ class ConstructorResolver {
// Resolved constructor arguments: type conversion and/or autowiring necessary.
try {
String[] paramNames = null;
ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer();
if (pnd != null) {
paramNames = pnd.getParameterNames(candidate);
if (resolvedValues != null && resolvedValues.containsNamedArgument()) {
ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer();
if (pnd != null) {
paramNames = pnd.getParameterNames(candidate);
}
}
argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw,
paramTypes, paramNames, candidate, autowiring, candidates.size() == 1);
@@ -75,7 +75,7 @@ public interface InstanceSupplier<T> extends ThrowingSupplier<T> {
default <V> InstanceSupplier<V> andThen(
ThrowingBiFunction<RegisteredBean, ? super T, ? extends V> after) {
Assert.notNull(after, "'after' function must not be null");
return new InstanceSupplier<V>() {
return new InstanceSupplier<>() {
@Override
public V get(RegisteredBean registeredBean) throws Exception {
@@ -119,7 +119,7 @@ public interface InstanceSupplier<T> extends ThrowingSupplier<T> {
&& instanceSupplier.getFactoryMethod() == factoryMethod) {
return instanceSupplier;
}
return new InstanceSupplier<T>() {
return new InstanceSupplier<>() {
@Override
public T get(RegisteredBean registeredBean) throws Exception {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@@ -77,6 +77,21 @@ class BeanWrapperTests extends AbstractPropertyAccessorTests {
assertThat(accessor.getPropertyValue("aliasedName")).isEqualTo("tom");
}
@Test
void replaceWrappedInstance() {
GetterBean target = new GetterBean();
BeanWrapperImpl accessor = createAccessor(target);
accessor.setPropertyValue("name", "tom");
assertThat(target.getAliasedName()).isEqualTo("tom");
assertThat(accessor.getPropertyValue("aliasedName")).isEqualTo("tom");
target = new GetterBean();
accessor.setWrappedInstance(target);
accessor.setPropertyValue("name", "tom");
assertThat(target.getAliasedName()).isEqualTo("tom");
assertThat(accessor.getPropertyValue("aliasedName")).isEqualTo("tom");
}
@Test
void setValidAndInvalidPropertyValuesShouldContainExceptionDetails() {
TestBean target = new TestBean();
@@ -380,6 +395,7 @@ class BeanWrapperTests extends AbstractPropertyAccessorTests {
}
@SuppressWarnings("try")
public static class ActiveResource implements AutoCloseable {
public ActiveResource getResource() {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@@ -238,10 +238,9 @@ class AotServicesTests {
@Override
public Enumeration<URL> getResources(String name) throws IOException {
return (!"META-INF/spring/aot.factories".equals(name))
? super.getResources(name)
: super.getResources("org/springframework/beans/factory/aot/"
+ this.factoriesName);
return (!"META-INF/spring/aot.factories".equals(name) ?
super.getResources(name) :
super.getResources("org/springframework/beans/factory/aot/" + this.factoriesName));
}
}
@@ -88,8 +88,7 @@ class BeanDefinitionMethodGeneratorTests {
@Test
void generateBeanDefinitionMethodGeneratesMethod() {
RegisteredBean registeredBean = registerBean(
new RootBeanDefinition(TestBean.class));
RegisteredBean registeredBean = registerBean(new RootBeanDefinition(TestBean.class));
BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator(
this.methodGeneratorFactory, registeredBean, null,
Collections.emptyList());
@@ -172,10 +171,8 @@ class BeanDefinitionMethodGeneratorTests {
@Test
void generateBeanDefinitionMethodWhenHasInstancePostProcessorGeneratesMethod() {
RegisteredBean registeredBean = registerBean(
new RootBeanDefinition(TestBean.class));
BeanRegistrationAotContribution aotContribution = (generationContext,
beanRegistrationCode) -> {
RegisteredBean registeredBean = registerBean(new RootBeanDefinition(TestBean.class));
BeanRegistrationAotContribution aotContribution = (generationContext, beanRegistrationCode) -> {
GeneratedMethod generatedMethod = beanRegistrationCode.getMethods().add("postProcess", method ->
method.addModifiers(Modifier.STATIC)
.addParameter(RegisteredBean.class, "registeredBean")
@@ -183,16 +180,14 @@ class BeanDefinitionMethodGeneratorTests {
.returns(TestBean.class).addCode("return new $T($S);", TestBean.class, "postprocessed"));
beanRegistrationCode.addInstancePostProcessor(generatedMethod.toMethodReference());
};
List<BeanRegistrationAotContribution> aotContributions = Collections
.singletonList(aotContribution);
List<BeanRegistrationAotContribution> aotContributions = Collections.singletonList(aotContribution);
BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator(
this.methodGeneratorFactory, registeredBean, null, aotContributions);
MethodReference method = generator.generateBeanDefinitionMethod(
this.generationContext, this.beanRegistrationsCode);
compile(method, (actual, compiled) -> {
assertThat(actual.getBeanClass()).isEqualTo(TestBean.class);
InstanceSupplier<?> supplier = (InstanceSupplier<?>) actual
.getInstanceSupplier();
InstanceSupplier<?> supplier = (InstanceSupplier<?>) actual.getInstanceSupplier();
try {
TestBean instance = (TestBean) supplier.get(registeredBean);
assertThat(instance.getName()).isEqualTo("postprocessed");
@@ -204,9 +199,10 @@ class BeanDefinitionMethodGeneratorTests {
});
}
@Test // gh-28748
@Test // gh-28748
void generateBeanDefinitionMethodWhenHasInstancePostProcessorAndFactoryMethodGeneratesMethod() {
this.beanFactory.registerBeanDefinition("testBeanConfiguration", new RootBeanDefinition(TestBeanConfiguration.class));
this.beanFactory.registerBeanDefinition("testBeanConfiguration",
new RootBeanDefinition(TestBeanConfiguration.class));
RootBeanDefinition beanDefinition = new RootBeanDefinition(TestBean.class);
beanDefinition.setFactoryBeanName("testBeanConfiguration");
beanDefinition.setFactoryMethodName("testBean");
@@ -220,8 +216,7 @@ class BeanDefinitionMethodGeneratorTests {
.returns(TestBean.class).addCode("return new $T($S);", TestBean.class, "postprocessed"));
beanRegistrationCode.addInstancePostProcessor(generatedMethod.toMethodReference());
};
List<BeanRegistrationAotContribution> aotContributions = Collections
.singletonList(aotContribution);
List<BeanRegistrationAotContribution> aotContributions = Collections.singletonList(aotContribution);
BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator(
this.methodGeneratorFactory, registeredBean, null, aotContributions);
MethodReference method = generator.generateBeanDefinitionMethod(
@@ -244,10 +239,9 @@ class BeanDefinitionMethodGeneratorTests {
@Test
void generateBeanDefinitionMethodWhenHasCodeFragmentsCustomizerGeneratesMethod() {
RegisteredBean registeredBean = registerBean(
new RootBeanDefinition(TestBean.class));
BeanRegistrationAotContribution aotContribution = BeanRegistrationAotContribution
.withCustomCodeFragments(this::customizeBeanDefinitionCode);
RegisteredBean registeredBean = registerBean(new RootBeanDefinition(TestBean.class));
BeanRegistrationAotContribution aotContribution =
BeanRegistrationAotContribution.withCustomCodeFragments(this::customizeBeanDefinitionCode);
List<BeanRegistrationAotContribution> aotContributions = Collections.singletonList(aotContribution);
BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator(
this.methodGeneratorFactory, registeredBean, null, aotContributions);
@@ -260,21 +254,16 @@ class BeanDefinitionMethodGeneratorTests {
});
}
private BeanRegistrationCodeFragments customizeBeanDefinitionCode(
BeanRegistrationCodeFragments codeFragments) {
private BeanRegistrationCodeFragments customizeBeanDefinitionCode(BeanRegistrationCodeFragments codeFragments) {
return new BeanRegistrationCodeFragmentsDecorator(codeFragments) {
@Override
public CodeBlock generateNewBeanDefinitionCode(
GenerationContext generationContext,
ResolvableType beanType,
BeanRegistrationCode beanRegistrationCode) {
public CodeBlock generateNewBeanDefinitionCode(GenerationContext generationContext,
ResolvableType beanType, BeanRegistrationCode beanRegistrationCode) {
CodeBlock.Builder code = CodeBlock.builder();
code.addStatement("// I am custom");
code.add(super.generateNewBeanDefinitionCode(generationContext, beanType, beanRegistrationCode));
return code.build();
}
};
}
@@ -301,10 +290,9 @@ class BeanDefinitionMethodGeneratorTests {
beanDefinition.setAttribute("a", "A");
beanDefinition.setAttribute("b", "B");
RegisteredBean registeredBean = registerBean(beanDefinition);
BeanRegistrationAotContribution aotContribution = BeanRegistrationAotContribution
.withCustomCodeFragments(this::customizeAttributeFilter);
List<BeanRegistrationAotContribution> aotContributions = Collections
.singletonList(aotContribution);
BeanRegistrationAotContribution aotContribution =
BeanRegistrationAotContribution.withCustomCodeFragments(this::customizeAttributeFilter);
List<BeanRegistrationAotContribution> aotContributions = Collections.singletonList(aotContribution);
BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator(
this.methodGeneratorFactory, registeredBean, null,
aotContributions);
@@ -316,20 +304,15 @@ class BeanDefinitionMethodGeneratorTests {
});
}
private BeanRegistrationCodeFragments customizeAttributeFilter(
BeanRegistrationCodeFragments codeFragments) {
private BeanRegistrationCodeFragments customizeAttributeFilter(BeanRegistrationCodeFragments codeFragments) {
return new BeanRegistrationCodeFragmentsDecorator(codeFragments) {
@Override
public CodeBlock generateSetBeanDefinitionPropertiesCode(
GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode,
RootBeanDefinition beanDefinition,
public CodeBlock generateSetBeanDefinitionPropertiesCode(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode, RootBeanDefinition beanDefinition,
Predicate<String> attributeFilter) {
return super.generateSetBeanDefinitionPropertiesCode(generationContext,
beanRegistrationCode, beanDefinition, "a"::equals);
}
};
}
@@ -471,8 +454,7 @@ class BeanDefinitionMethodGeneratorTests {
@Test
@CompileWithForkedClassLoader
void generateBeanDefinitionMethodWhenPackagePrivateBean() {
RegisteredBean registeredBean = registerBean(
new RootBeanDefinition(PackagePrivateTestBean.class));
RegisteredBean registeredBean = registerBean(new RootBeanDefinition(PackagePrivateTestBean.class));
BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator(
this.methodGeneratorFactory, registeredBean, null,
Collections.emptyList());
@@ -502,6 +484,13 @@ class BeanDefinitionMethodGeneratorTests {
testBeanDefinitionMethodInCurrentFile(DocumentBuilderFactory.class, beanDefinition);
}
@Test
void generateBeanDefinitionMethodWhenBeanIsOfPrimitiveType() {
RootBeanDefinition beanDefinition = (RootBeanDefinition) BeanDefinitionBuilder
.rootBeanDefinition(Boolean.class).setFactoryMethod("parseBoolean").addConstructorArgValue("true").getBeanDefinition();
testBeanDefinitionMethodInCurrentFile(Boolean.class, beanDefinition);
}
private void testBeanDefinitionMethodInCurrentFile(Class<?> targetType, RootBeanDefinition beanDefinition) {
RegisteredBean registeredBean = registerBean(new RootBeanDefinition(beanDefinition));
BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator(
@@ -525,8 +514,7 @@ class BeanDefinitionMethodGeneratorTests {
return RegisteredBean.of(this.beanFactory, beanName);
}
private void compile(MethodReference method,
BiConsumer<RootBeanDefinition, Compiled> result) {
private void compile(MethodReference method, BiConsumer<RootBeanDefinition, Compiled> result) {
this.beanRegistrationsCode.getTypeBuilder().set(type -> {
CodeBlock methodInvocation = method.toInvokeCodeBlock(ArgumentCodeGenerator.none(),
this.beanRegistrationsCode.getClassName());
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,12 +19,13 @@ package org.springframework.context;
import org.springframework.beans.factory.Aware;
/**
* Interface to be implemented by any object that wishes to be notified
* of the MessageSource (typically the ApplicationContext) that it runs in.
* Interface to be implemented by any object that wishes to be notified of the
* {@link MessageSource} (typically the ApplicationContext) that it runs in.
*
* <p>Note that the MessageSource can usually also be passed on as bean
* reference (to arbitrary bean properties or constructor arguments), because
* it is defined as bean with name "messageSource" in the application context.
* <p>Note that the {@code MessageSource} can usually also be passed in as a bean
* reference (via arbitrary bean properties or constructor arguments), because
* it is defined as a bean with name {@code "messageSource"} in the application
* context.
*
* @author Juergen Hoeller
* @author Chris Beams
@@ -34,7 +35,7 @@ import org.springframework.beans.factory.Aware;
public interface MessageSourceAware extends Aware {
/**
* Set the MessageSource that this object runs in.
* Set the {@link MessageSource} that this object runs in.
* <p>Invoked after population of normal bean properties but before an init
* callback like InitializingBean's afterPropertiesSet or a custom init-method.
* Invoked before ApplicationContextAware's setApplicationContext.
@@ -79,6 +79,7 @@ import org.springframework.util.ClassUtils;
* @author Ramnivas Laddad
* @author Chris Beams
* @author Stephane Nicoll
* @author Sam Brannen
* @since 2.5
* @see org.springframework.core.type.classreading.MetadataReaderFactory
* @see org.springframework.core.type.AnnotationMetadata
@@ -340,13 +341,13 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
* @see #extractStereotype(TypeFilter)
*/
private boolean indexSupportsIncludeFilter(TypeFilter filter) {
if (filter instanceof AnnotationTypeFilter) {
Class<? extends Annotation> annotation = ((AnnotationTypeFilter) filter).getAnnotationType();
return (AnnotationUtils.isAnnotationDeclaredLocally(Indexed.class, annotation) ||
annotation.getName().startsWith("javax."));
if (filter instanceof AnnotationTypeFilter annotationTypeFilter) {
Class<? extends Annotation> annotationType = annotationTypeFilter.getAnnotationType();
return (AnnotationUtils.isAnnotationDeclaredLocally(Indexed.class, annotationType) ||
annotationType.getName().startsWith("jakarta."));
}
if (filter instanceof AssignableTypeFilter) {
Class<?> target = ((AssignableTypeFilter) filter).getTargetType();
if (filter instanceof AssignableTypeFilter assignableTypeFilter) {
Class<?> target = assignableTypeFilter.getTargetType();
return AnnotationUtils.isAnnotationDeclaredLocally(Indexed.class, target);
}
return false;
@@ -361,11 +362,11 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
*/
@Nullable
private String extractStereotype(TypeFilter filter) {
if (filter instanceof AnnotationTypeFilter) {
return ((AnnotationTypeFilter) filter).getAnnotationType().getName();
if (filter instanceof AnnotationTypeFilter annotationTypeFilter) {
return annotationTypeFilter.getAnnotationType().getName();
}
if (filter instanceof AssignableTypeFilter) {
return ((AssignableTypeFilter) filter).getTargetType().getName();
if (filter instanceof AssignableTypeFilter assignableTypeFilter) {
return assignableTypeFilter.getTargetType().getName();
}
return null;
}
@@ -536,10 +537,10 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
* Clear the local metadata cache, if any, removing all cached class metadata.
*/
public void clearCache() {
if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory cmrf) {
// Clear cache in externally provided MetadataReaderFactory; this is a no-op
// for a shared cache since it'll be cleared by the ApplicationContext.
((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
cmrf.clearCache();
}
}
@@ -147,9 +147,8 @@ class ConfigurationClassParser {
this.problemReporter = problemReporter;
this.environment = environment;
this.resourceLoader = resourceLoader;
this.propertySourceRegistry = (this.environment instanceof ConfigurableEnvironment ce
? new PropertySourceRegistry(new PropertySourceProcessor(ce, this.resourceLoader))
: null);
this.propertySourceRegistry = (this.environment instanceof ConfigurableEnvironment ce ?
new PropertySourceRegistry(new PropertySourceProcessor(ce, this.resourceLoader)) : null);
this.registry = registry;
this.componentScanParser = new ComponentScanAnnotationParser(
environment, resourceLoader, componentScanBeanNameGenerator, registry);
@@ -319,6 +319,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
}
@Override
@Nullable
public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
boolean hasPropertySourceDescriptors = !CollectionUtils.isEmpty(this.propertySourceDescriptors);
boolean hasImportRegistry = beanFactory.containsBean(IMPORT_REGISTRY_BEAN_NAME);
@@ -16,7 +16,6 @@
package org.springframework.context.aot;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.springframework.aot.generate.GeneratedFiles;
@@ -26,6 +25,7 @@ import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.TypeHint.Builder;
import org.springframework.aot.hint.TypeReference;
import org.springframework.aot.hint.support.ClassHintUtils;
import org.springframework.cglib.core.ReflectUtils;
import org.springframework.core.io.ByteArrayResource;
@@ -34,8 +34,10 @@ import org.springframework.core.io.ByteArrayResource;
* and register the necessary hints so that they can be instantiated.
*
* @author Stephane Nicoll
* @see ReflectUtils#setGeneratedClassHandler(BiConsumer)
* @see ReflectUtils#setLoadedClassHandler(Consumer)
* @since 6.0
* @see ReflectUtils#setGeneratedClassHandler
* @see ReflectUtils#setLoadedClassHandler
* @see ClassHintUtils#registerProxyIfNecessary
*/
class CglibClassHandler {
@@ -46,11 +48,13 @@ class CglibClassHandler {
private final GeneratedFiles generatedFiles;
CglibClassHandler(GenerationContext generationContext) {
this.runtimeHints = generationContext.getRuntimeHints();
this.generatedFiles = generationContext.getGeneratedFiles();
}
/**
* Handle the specified generated CGLIB class.
* @param cglibClassName the name of the generated class
@@ -18,15 +18,12 @@ package org.springframework.context.support;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Proxy;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.TypeHint.Builder;
import org.springframework.aot.hint.support.ClassHintUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanDefinitionStoreException;
@@ -49,7 +46,6 @@ import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.metrics.ApplicationStartup;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Generic ApplicationContext implementation that holds a single internal
@@ -72,7 +68,7 @@ import org.springframework.util.ClassUtils;
* definitions on it. {@link #refresh()} may only be called once.
*
* <p>This ApplicationContext implementation is suitable for Ahead of Time
* processing, using {@link #refreshForAotProcessing()} as an alternative to the
* processing, using {@link #refreshForAotProcessing} as an alternative to the
* regular {@link #refresh()}.
*
* <p>Usage example:
@@ -88,16 +84,13 @@ import org.springframework.util.ClassUtils;
* MyBean myBean = (MyBean) ctx.getBean("myBean");
* ...</pre>
*
* For the typical case of XML bean definitions, simply use
* For the typical case of XML bean definitions, you may also use
* {@link ClassPathXmlApplicationContext} or {@link FileSystemXmlApplicationContext},
* which are easier to set up - but less flexible, since you can just use standard
* resource locations for XML bean definitions, rather than mixing arbitrary bean
* definition formats. The equivalent in a web environment is
* {@link org.springframework.web.context.support.XmlWebApplicationContext}.
*
* <p>For custom application context implementations that are supposed to read
* special bean definition formats in a refreshable manner, consider deriving
* from the {@link AbstractRefreshableApplicationContext} base class.
* definition formats. For a custom application context implementation supposed to
* read a specific bean definition format in a refreshable manner, consider
* deriving from the {@link AbstractRefreshableApplicationContext} base class.
*
* @author Juergen Hoeller
* @author Chris Beams
@@ -111,15 +104,6 @@ import org.springframework.util.ClassUtils;
*/
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
private static final Consumer<Builder> asClassBasedProxy = hint ->
hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_DECLARED_METHODS,
MemberCategory.DECLARED_FIELDS);
private static final Consumer<Builder> asProxiedUserClass = hint ->
hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS);
private final DefaultListableBeanFactory beanFactory;
@Nullable
@@ -442,11 +426,11 @@ public class GenericApplicationContext extends AbstractApplicationContext implem
for (String beanName : this.beanFactory.getBeanDefinitionNames()) {
Class<?> beanType = this.beanFactory.getType(beanName);
if (beanType != null) {
registerProxyHintIfNecessary(beanType, runtimeHints);
ClassHintUtils.registerProxyIfNecessary(beanType, runtimeHints);
for (SmartInstantiationAwareBeanPostProcessor bpp : bpps) {
Class<?> newBeanType = bpp.determineBeanType(beanType, beanName);
if (newBeanType != beanType) {
registerProxyHintIfNecessary(newBeanType, runtimeHints);
ClassHintUtils.registerProxyIfNecessary(newBeanType, runtimeHints);
beanType = newBeanType;
}
}
@@ -454,22 +438,6 @@ public class GenericApplicationContext extends AbstractApplicationContext implem
}
}
private void registerProxyHintIfNecessary(Class<?> beanType, RuntimeHints runtimeHints) {
if (Proxy.isProxyClass(beanType)) {
// A JDK proxy class needs an explicit hint
runtimeHints.proxies().registerJdkProxy(beanType.getInterfaces());
}
else {
// Potentially a CGLIB-generated subclass with reflection hints
Class<?> userClass = ClassUtils.getUserClass(beanType);
if (userClass != beanType) {
runtimeHints.reflection()
.registerType(beanType, asClassBasedProxy)
.registerType(userClass, asProxiedUserClass);
}
}
}
//---------------------------------------------------------------------
// Convenient methods for registering individual beans
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2022 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

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