Compare commits

..

366 Commits

Author SHA1 Message Date
Brian Clozel ba590ac9e4 Release v6.2.7 2025-05-15 10:35:00 +02:00
rstoyanchev ee62701f56 Make use of PatternMatchUtils ignoreCase option
Closes gh-34801
2025-05-15 08:18:56 +01:00
Juergen Hoeller fa168ca78a Revise FactoryBean locking behavior for strict/lenient consistency
After the bootstrap phase (and with spring.locking.strict=true during the bootstrap phase), getSingletonFactoryBeanForTypeCheck always locks. In a background bootstrap thread, it never locks. Otherwise, it tries locking and explicitly resolves the bean class for subsequent type-based resolution (even for a component-scanned class) when it fails to acquire the lock. Furthermore, getObjectFromFactoryBean follows the same locking algorithm for post-processing.

Closes gh-34902
2025-05-15 01:45:09 +02:00
rstoyanchev 3c228a5c1d Add missing @since tags in PatternMatchUtils
See: gh-34801
2025-05-14 15:15:50 +01:00
Juergen Hoeller 9bf6b8cddf Upgrade to Reactor 2024.0.6
Closes gh-34898
2025-05-14 15:03:28 +02:00
Patrick Strawderman 37ecdd1437 Forward more methods to underlying InputStream in NonClosingInputStream
NonClosingInputStream extends FilterInputStream, which does not forward some
newer InputStream methods such as transferTo and readAllBytes. Specific InputStream
implementations may have more optimized methods (e.g., FileInputStream).

Closes gh-34893

Signed-off-by: Patrick Strawderman <pstrawderman@netflix.com>
2025-05-14 09:58:23 +02:00
Juergen Hoeller 73f1c5a189 Polishing 2025-05-13 16:08:57 +02:00
Juergen Hoeller 4d296fb4ca Upgrade to Micrometer 1.14.7
Includes Jackson 2.18.4, Jetty 12.0.21, Netty 4.1.121, Apache HttpClient 5.4.4, Checkstyle 10.23.1

Closes gh-34889
2025-05-13 16:08:31 +02:00
Juergen Hoeller 6a9444473f Clarify CompositePropertySource behavior for EnumerablePropertySource contract
Closes gh-34886
2025-05-13 16:08:03 +02:00
Sam Brannen 03ae97b2eb Introduce Spring property for default escape character for placeholders
Spring Framework 6.2 introduced support for an escape character for
property placeholders (by default '\'). However, as of Spring Framework
6.2.6, there was no way to either escape the escape character or disable
escape character support.

For example, given a `username` property configured with the value of
`Jane.Smith` and a `DOMAIN\${username}` configuration string, property
placeholder replacement used to result in `DOMAIN\Jane.Smith` prior to
6.2 but now results in `DOMAIN${username}`. Similarly, an attempt to
escape the escape character via `DOMAIN\\${username}` results in
`DOMAIN\${username}`.

In theory, one should be able to disable use of an escape character
altogether, and that is currently possible by invoking
setEscapeCharacter(null) on AbstractPropertyResolver and
PlaceholderConfigurerSupport (the superclass of
PropertySourcesPlaceholderConfigurer).

However, in reality, there are two hurdles.

- As of 6.2.6, an invocation of setEscapeCharacter(null) on a
  PropertySourcesPlaceholderConfigurer applied to its internal
  top-level PropertySourcesPropertyResolver but not to any nested
  PropertySourcesPropertyResolver, which means that the `null` escape
  character could not be effectively applied.

- Users may not have an easy way to explicitly set the escape character
  to `null` for a PropertyResolver or
  PropertySourcesPlaceholderConfigurer. For example, Spring Boot
  auto-configures a PropertySourcesPlaceholderConfigurer with the
  default escape character enabled.

This first issue above has recently been addressed by gh-34861.

This commit therefore addresses the second issue as follows.

- To allow developers to easily revert to the pre-6.2 behavior without
  changes to code or configuration strings, this commit introduces a
  `spring.placeholder.escapeCharacter.default` property for use with
  SpringProperties which globally sets the default escape character that
  is automatically configured in AbstractPropertyResolver and
  PlaceholderConfigurerSupport.

- Setting the property to an empty string sets the default escape
  character to `null`, effectively disabling the default support for
  escape characters.

    spring.placeholder.escapeCharacter.default =

- Setting the property to any other character sets the default escape
  character to that specific character.

    spring.placeholder.escapeCharacter.default = ~

- Setting the property to a string containing more than one character
  results in an exception.

- Developers are still able to configure an explicit escape character
  in AbstractPropertyResolver and PlaceholderConfigurerSupport if they
  choose to do so.

- Third-party components that wish to rely on the same feature can
  invoke AbstractPropertyResolver.getDefaultEscapeCharacter() to obtain
  the globally configured default escape character.

See gh-9628
See gh-34315
See gh-34861
Closes gh-34865
2025-05-13 13:37:30 +02:00
Sam Brannen e34cdc2a55 Improve Javadoc for property placeholder support 2025-05-12 17:25:49 +02:00
Sam Brannen 8b1b9ef9e5 Stop redefining placeholder syntax constants in PlaceholderConfigurerSupport 2025-05-12 17:21:37 +02:00
Sam Brannen efdaae02e0 Add test for late binding from Environment property sources
This new test serves as a "regression test" for behavior tested in
Spring Boot.

See gh-34861
2025-05-11 16:41:19 +02:00
Sam Brannen 49ffb833a3 Sort methods to align with PropertySource declaration order
See gh-34861
2025-05-11 15:48:00 +02:00
Sam Brannen 065e50a444 Override containsProperty() in FallbackEnvironmentPropertySource
This commit overrides containsProperty() in
FallbackEnvironmentPropertySource for consistency with the
implementation of ConfigurableEnvironmentPropertySource.

See gh-34861
2025-05-11 15:44:51 +02:00
Sam Brannen 7135527765 Always obtain fresh PropertySources in ConfigurableEnvironmentPropertySource
Although it's unlikely that the implementation of getPropertySources()
in a ConfigurableEnvironment would be overridden to return a different
MutablePropertySources instance than the one that the
ConfigurableEnvironment typically acts on, it is in fact possible.

In light of that possibility, this commit refactors
ConfigurableEnvironmentPropertySource so that it always obtains a fresh
PropertySources reference.

See gh-34861
2025-05-11 15:30:09 +02:00
Sam Brannen 65e30132f3 Revert "Introduce CompositePropertySource constructor that accepts Iterable<PropertySource>"
This reverts commit 0867dfca33.
2025-05-11 14:10:54 +02:00
Sam Brannen ebb44a8368 Restore support for non-EnumerablePropertySource in PropertySourcesPlaceholderConfigurer
Commit 3295289e17 fixed a number issues with placeholder resolution in
PropertySourcesPlaceholderConfigurer. However, in doing so, it replaced
a raw PropertySource with a CompositePropertySource which implements
EnumerablePropertySource.

Consequently, all property sources registered in the Environment must
now implement EnumerablePropertySource (which is not an actual
requirement). Otherwise, invocations of getPropertyNames() on the
CompositePropertySource result in an IllegalStateException, and that is
a breaking change which resulted in numerous build failures within the
Spring portfolio.

To address that regression, this commit introduces a private
ConfigurableEnvironmentPropertySource in
PropertySourcesPlaceholderConfigurer which is a "raw" PropertySource
that delegates directly to the PropertySources in a
ConfigurableEnvironment.

This commit also extracts the raw PropertySource for direct Environment
delegation into a new FallbackEnvironmentPropertySource.

See gh-17385
Closes gh-34861
2025-05-11 13:46:11 +02:00
Sam Brannen 3096bb6d0c Polishing 2025-05-11 13:24:40 +02:00
Sam Brannen 2b2a57b4a3 Polish Javadoc 2025-05-10 15:44:08 +02:00
Sam Brannen 3295289e17 Fix placeholder resolution in PropertySourcesPlaceholderConfigurer
Currently, the placeholder resolution algorithm in
PropertySourcesPlaceholderConfigurer fails in several scenarios, and
the root cause for this category of failures has actually existed since
PropertySourcesPlaceholderConfigurer was introduced in Spring Framework
3.1.

Specifically, PropertySourcesPlaceholderConfigurer creates its own
PropertySourcesPropertyResolver that indirectly delegates to another
"nested" PropertySourcesPropertyResolver to interact with
PropertySources from the Environment, which results in double
placeholder parsing and resolution attempts, and that behavior leads to
a whole category of bugs.

For example, #27947 was addressed in Spring Framework 5.3.16, and due
to #34315 and #34326 we have recently realized that additional bugs
exist with placeholder resolution: nested placeholder resolution can
fail when escape characters are used, and it is currently impossible
to disable the escape character support for nested resolution.

To address this category of bugs, we no longer indirectly use or
directly create a "nested" PropertySourcesPropertyResolver in
PropertySourcesPlaceholderConfigurer. Instead, properties from property
sources from the Environment are now accessed directly without
duplicate/nested placeholder resolution.

See gh-27947
See gh-34326
See gh-34862
Closes gh-34861
2025-05-10 15:14:50 +02:00
Sam Brannen 457e876303 Polish reference manual regarding placeholders 2025-05-10 15:10:29 +02:00
Sam Brannen 021bf6e77d Add more tests for escape character support in PlaceholderParser 2025-05-10 14:19:41 +02:00
Sam Brannen 90453643cc Simplify and revise PlaceholderParserTests for consistency 2025-05-10 14:16:49 +02:00
Sam Brannen 5a2cbc1ab3 Polish PropertySourcesPropertyResolverTests 2025-05-09 16:43:04 +02:00
Sam Brannen 348b4cd067 Polish contribution
See gh-34720
2025-05-09 16:32:13 +02:00
Fawzi Essam 9b52cfd7d5 Test escape character support in PropertySourcesPropertyResolver
See gh-34326
See gh-34720
See gh-34861

Signed-off-by: Fawzi Essam <iifawzie@gmail.com>
2025-05-09 16:31:27 +02:00
Sam Brannen dd6eede243 Properly expand reused collection parameters in R2DBC NamedParameterUtils
Prior to this commit, NamedParameterUtils in spring-r2dbc did not
properly expand reused collection parameters. Specifically, values in a
supplied collection were only expanded in the resulting query once, for
the first occurrence of the named parameter.

To address that, this commit effectively reinstates the original logic
for ExpandedQuery from NamedParameterUtils in the Spring Data R2DBC
project.

https://github.com/spring-projects/spring-data-relational/blob/94958f5eb66cbe2e8e025155cd99abf36f6f91f4/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/NamedParameterUtils.java#L486

Closes gh-34768
2025-05-09 14:27:19 +02:00
Sam Brannen de97e35189 Polish Javadoc and internals of R2DBC support 2025-05-09 14:04:10 +02:00
Sam Brannen 45a2c51fe1 Polish Javadoc for Environment-related components 2025-05-08 10:49:44 +02:00
Sam Brannen b98c3257af Introduce @⁠Disabled failing test for R2DBC NamedParameterUtils
The last assertion of the new test method currently fails since "foo"
is only bound once.

java.lang.AssertionError:
Expected size: 2 but was: 1 in:
{0="foo"}

See gh-34768
2025-05-07 17:30:07 +02:00
Sam Brannen ea8ae09cb7 Revise NamedParameterUtilsTests for consistency 2025-05-07 17:24:41 +02:00
Sam Brannen 0abfad870c Polish class-level Javadoc for SpringProperties 2025-05-07 16:14:23 +02:00
Sam Brannen 5a9af9e024 Document AotDetector.AOT_ENABLED flag in SpringProperties
Prior to this commit, the AotDetector.AOT_ENABLED flag was documented
in the reference manual not in the Javadoc for SpringProperties.
2025-05-07 16:14:07 +02:00
Sam Brannen 0867dfca33 Introduce CompositePropertySource constructor that accepts Iterable<PropertySource>
This commit introduces a new constructor for CompositePropertySource
that accepts a `String name` and an Iterable<PropertySource<?>>, which
allows a CompositePropertySource to be constructed from existing
property sources, such as an instance of MutablePropertySources.

Closes gh-34862
2025-05-07 14:25:13 +02:00
Sam Brannen b4355dc955 Polishing 2025-05-07 12:42:40 +02:00
Sam Brannen bc91e0ea96 Revise PropertyResolver Javadoc to highlight resolution semantics 2025-05-06 18:50:33 +02:00
Sam Brannen 8599ee6c2b Polish Javadoc 2025-05-06 16:07:46 +02:00
Sam Brannen bc466022b1 Fix typo in parameter name 2025-05-06 16:07:46 +02:00
Brian Clozel 6f11711e27 Fix HttpClient 5.3.x request config compatibility
As of gh-33806, the HttpComponents client request factory is forward
compatible with the 5.4+ versions of that library for configuring HTTP
request configuration.

This change would not tkae into account configuration set at the Spring
level because it would consider the default `RequestConfig` instance as
a custom one. This commit ensures that Spring sets its own configuration
if no custom configuration was set for all supported HttpComponents
generations.

Fixes gh-34851
2025-05-05 14:38:30 +02:00
rstoyanchev c067919173 Ensure Fragment can merge attributes
Use a new map when merging as the original may be immutable.

Closes gh-34848
2025-05-02 15:58:51 +01:00
rstoyanchev ac773d97e9 Polishing contribution
Closes gh-34828
2025-05-02 15:58:51 +01:00
Artur bd7007227c Provide a working example instead of unclear placeholders
See gh-34828

Signed-off-by: Artur <artur@vaadin.com>
2025-05-02 15:58:51 +01:00
Sam Brannen e8f873a349 Ensure Bean Overrides are discovered once in hierarchies
Prior to this commit, bean overrides (such as @⁠MockitoBean, etc.) were
discovered multiple times if they were declared:

- at the type-level on an interface that is implemented at more than
  one level in the type hierarchy, the enclosing class hierarchy, or a
  combination of the type and enclosing class hierarchies.

or

- on a field declared in a class which can be reached multiple times
  while traversing the type and enclosing class hierarchies in
  scenarios such as the following: the class (X) in which the field is
  declared is a supertype of an enclosing type of the test class, and X
  is also an enclosing type of a supertype of the test class.

Such scenarios resulted in an IllegalStateException stating that a
duplicate BeanOverrideHandler was discovered.

To address that, this commit revises the search algorithm in
BeanOverrideHandler so that all types (superclasses, enclosing classes,
and implemented interfaces) are only visited once while traversing the
type and enclosing class hierarchies in search of bean override
handlers.

See gh-33925
See gh-34324
Closes gh-34844
2025-05-02 11:26:33 +02:00
Sam Brannen b943817f3e Close ApplicationContext after test AOT processing
See commit 1c108054ee
Closes gh-34841
2025-05-01 10:50:38 +02:00
Juergen Hoeller 4466548f53 Align parameter javadoc with nullable signature
Closes gh-34845
2025-04-30 17:55:39 +02:00
Juergen Hoeller 9c183f9e77 Add explicit note on redeclaring in each application context
Closes gh-34843
2025-04-30 17:55:18 +02:00
Juergen Hoeller 03620fc530 Polishing 2025-04-29 11:47:47 +02:00
Juergen Hoeller 1c108054ee Close ApplicationContext after AOT processing
Closes gh-34841
2025-04-29 11:47:09 +02:00
Juergen Hoeller d0b186a1c7 Polishing 2025-04-28 16:13:04 +02:00
Juergen Hoeller 4172581f1b Try loadClass on LinkageError in case of same ClassLoader as well
Closes gh-34824
2025-04-28 16:12:45 +02:00
rstoyanchev c88ba6c90e Polishing contribution
Closes gh-34812
2025-04-28 14:23:30 +01:00
Yanming Zhou d7c13d6518 HttpEntity.EMPTY should be immutable
See gh-34812

Signed-off-by: Yanming Zhou <zhouyanming@gmail.com>
2025-04-28 14:22:59 +01:00
Sam Brannen 5013d6d771 Fail Gradle build whenever a javadoc task fails
To ensure that failures in javadoc tasks do not result in documentation
silently not being generated/published, this commit sets
`failOnError = true` for all javadoc tasks.

See gh-27497
See gh-34774
Closes gh-34837
2025-04-27 17:59:41 +02:00
Sam Brannen 9cd7b6a91e Upgrade spring-javaformat-gradle-plugin to version 0.0.43 2025-04-27 16:51:47 +02:00
Sam Brannen e9dcd64068 Upgrade to Gradle 8.14
Closes gh-34836
2025-04-27 16:29:46 +02:00
Sam Brannen ce7f47c962 Upgrade io.freefair.aggregate-javadoc plugin to version 8.13.1 2025-04-27 16:23:22 +02:00
Sam Brannen ef34464c94 Restructure TestContext framework support sections
This commit moves the JUnit Jupiter section above the JUnit 4 section
and groups all JUnit 4 sections under a new "JUnit 4 Support" heading.
2025-04-26 09:38:10 +02:00
Sam Brannen ef11a00c0b Polishing 2025-04-26 09:37:34 +02:00
Sam Brannen 176b0b09bf Migrate remaining JUnit 4 tests to JUnit Jupiter where feasible
In 49e5c84928 I unfortunately overlooked
several JUnit 4 based tests in the `junit4` package that should be
migrated to JUnit Jupiter.

This commit address those remaining test classes.

See gh-23451
See gh-34794
Closes gh-34813
2025-04-26 07:51:24 +02:00
rstoyanchev c48ff357dc HTTP Service proxy sets body type
Closes gh-34793
2025-04-25 21:03:00 +01:00
rstoyanchev 190dabb8e1 Polishing contribution
Closes gh-34789
2025-04-25 21:03:00 +01:00
blake_bauman d15abd58b4 Add option to set Principal in MockServerWebExchange
See gh-34789

Signed-off-by: blake_bauman <blake_bauman@apple.com>
2025-04-25 21:03:00 +01:00
Sam Brannen 44500cf868 Revise TestConventions to retain existing JUnit Platform options
This commit revises the implementation of TestConventions so that
existing JUnit Platform options from a pre-configured `test` task are
copied instead of overridden.

Closes gh-34827
2025-04-25 13:38:18 +02:00
Sam Brannen e384389790 Reinstate the @⁠Inject Technology Compatibility Kit (TCK)
In commit 05ebca8677, the `public` modifier was removed from the
SpringAtInjectTckTests class, which prevents it from being run as a
JUnit 3 test class.

To address that, this commit adds the missing `public` modifier as well
as a a code comment to help prevent this from happening again.

In addition, this commit updates spring-context.gradle to ensure that
the JUnit Vintage test engine is always applied. However, that Gradle
configuration is unfortunately ignored due to how our TestConventions
class has been implemented. Thus, that issue will have to be addressed
separately.

Closes gh-34800
2025-04-25 12:08:39 +02:00
Sam Brannen 49e5c84928 Migrate remaining JUnit 4 tests to JUnit Jupiter where feasible
In Spring Framework 5.2, we migrated most of the test suite from JUnit
4 to JUnit Jupiter; however, prior to this commit, several tests in the
spring-test module were still based on JUnit 4 unnecessarily.

Since we are now planning to deprecate our JUnit 4 support in 7.0, this
commit migrates our remaining JUnit 4 based tests to JUnit Jupiter
whenever feasible. In the process, test classes that previously resided
under the "junit4" package have been moved to new packages directly
under the "org.springframework.text.context" package, and several
classes have been renamed for greater clarity of purpose.

Consequently, the only remaining tests based on JUnit 4 are those tests
that are required to run with JUnit 4 in order to test our JUnit 4
support.

This commit also greatly simplifies exclusions for Checkstyle rules
pertaining to JUnit usage.

See gh-23451
See gh-34794
Closes gh-34813
2025-04-24 16:20:39 +02:00
Sébastien Deleuze 56eb135608 Fix AbstractJackson2HttpMessageConverter nullness
This commit makes AbstractJackson2HttpMessageConverter#getObjectMappersForType
return value non nullable as an empty map is returned in case of no
registrations.

Closes gh-34811
2025-04-24 10:37:30 +02:00
rstoyanchev 5c5cf73e11 Add ignoreCase variants to PatternMatchUtils
See gh-34801
2025-04-23 12:10:55 +01:00
rstoyanchev 858c2bd270 Polishing contribution
Closes gh-34783
2025-04-23 10:54:52 +01:00
whl 124582d910 Fix expansion of query param with same name
See gh-34783

Signed-off-by: whl <whlit.cola@gmail.com>
2025-04-23 10:54:52 +01:00
Juergen Hoeller 253f321e8b Early getJarFile() call for consistent jar file existence check
See gh-34796
2025-04-23 10:16:12 +02:00
Juergen Hoeller 0252e39409 Check for the existence of any actual jar entries in case of jar root
Closes gh-34796
2025-04-22 23:18:56 +02:00
Sam Brannen 2f60083cd5 Add integration tests for reused named parameters from bean properties
See gh-34768
2025-04-22 12:45:35 +02:00
Juergen Hoeller b83e07ff8c Ignore NoSuchFileException from getJarFile() as well
Closes gh-34764
2025-04-22 12:17:18 +02:00
Juergen Hoeller 1841ad3472 Consistently use local copy of volatile mainThreadPrefix field
Closes gh-34746
2025-04-22 12:17:03 +02:00
Sam Brannen 0477ba4de4 Remove obsolete RmiInvocationWrapperRTD.xml file
Closes gh-34779

(cherry picked from commit e7402bc365)
2025-04-22 12:10:18 +02:00
Sam Brannen 8c376e9cc5 Remove redundant parameter count check in AnnotationsScanner.hasSameParameterTypes()
The redundancy was reported by @TAKETODAY.

See gh-34717
2025-04-22 11:53:40 +02:00
Sam Brannen 018d3c9ef2 Add integration tests for reused named parameters
See gh-34768
2025-04-17 15:48:24 +02:00
Brian Clozel c70741f60d Next development version (v6.2.7-SNAPSHOT) 2025-04-17 09:50:20 +02:00
Sam Brannen f40d98668d Revise configuration for javadoc Gradle tasks
Closes gh-34766
2025-04-16 13:42:05 +02:00
rstoyanchev 9c13c6b695 Revert "Use optimistic locking where possible in ResponseBodyEmitter"
This reverts commit e67f892e44.

Closes gh-34762
2025-04-16 11:53:22 +01:00
rstoyanchev b49924ba37 Revert "Fix handling of timeout in SseEmitter"
This reverts commit f92f9c1d5b.

See gh-34762
2025-04-16 11:41:00 +01:00
Stéphane Nicoll 7b8c104077 Upgrade to github-changelog-generator 0.0.12
Closes gh-34755
2025-04-15 10:04:45 +02:00
Sam Brannen 8f62a8f579 Suppress recently introduced warning 2025-04-14 14:25:48 +02:00
Sam Brannen d0966dfb58 Revise contribution
See gh-34747
2025-04-14 14:15:50 +02:00
lituizi bb45a3ae69 Update AbstractAutowireCapableBeanFactory.ignoreDependencyInterface() Javadoc
Specifically, the documentation update reflects that:

- Initially, it was mentioned that only the `BeanFactoryAware`
  interface is ignored by default.

- The updated documentation now correctly states that `BeanNameAware`,
  `BeanFactoryAware`, and `BeanClassLoaderAware` interfaces are all
  ignored by default.

This change ensures a more accurate representation of the default
behavior regarding which dependency interfaces are automatically
ignored during autowiring in the context of Spring's bean factory
mechanism.

Closes gh-34747

Signed-off-by: lituizi <2811328244@qq.com>
2025-04-14 14:07:35 +02:00
Sam Brannen 7095f4cb66 Use proper casing for parameter and variable names 2025-04-14 11:25:40 +02:00
Sam Brannen a22d204681 Remove duplicate words in Java source code
Discovered using regular expression: \b(\w+)\s+\1\b[^(}]
2025-04-14 11:24:55 +02:00
Sam Brannen f27382cfb6 Consistently indent code with tabs in reference manual 2025-04-14 11:22:08 +02:00
Sam Brannen 6bc196883a Fix heading for "Context Configuration with Context Customizers" 2025-04-14 11:22:08 +02:00
Juergen Hoeller c4f66b776f Use single volatile field for indicating pre-instantiation phase
See gh-34729
2025-04-12 06:00:18 +02:00
Sam Brannen 87e04df983 Upgrade to JUnit 5.12.2 2025-04-11 16:58:23 +02:00
Sam Brannen 4d648f8b5d Clean up warnings in Gradle build 2025-04-11 16:57:57 +02:00
Juergen Hoeller 6ea9f66fd7 Remove superfluous DefaultParameterNameDiscoverer configuration 2025-04-10 18:33:39 +02:00
Juergen Hoeller eea6addd26 Avoid lenient locking for additional external bootstrap threads
Includes spring.locking.strict revision to differentiate between true, false, not set.
Includes checkFlag accessor on SpringProperties, also used in StatementCreatorUtils.

Closes gh-34729
See gh-34303
2025-04-10 18:33:21 +02:00
Juergen Hoeller 7f2c1f447f Try loadClass on LinkageError in case of ClassLoader mismatch
See gh-34677
2025-04-10 18:30:45 +02:00
Sam Brannen 26869b0e4c Polish Bean Override internals 2025-04-10 17:06:27 +02:00
Sam Brannen cd987fc104 Update Javadoc to stop mentioning 5.3.x as the status quo
Closes gh-34740
2025-04-10 16:40:04 +02:00
Sam Brannen 3f9402a56b Update testing documentation to reflect status quo 2025-04-10 15:06:52 +02:00
Sam Brannen c168e1c297 Provide first-class support for Bean Overrides with @⁠ContextHierarchy
This commit provides first-class support for Bean Overrides
(@⁠MockitoBean, @⁠MockitoSpyBean, @⁠TestBean, etc.) with
@⁠ContextHierarchy.

Specifically, bean overrides can now specify which ApplicationContext
they target within the context hierarchy by configuring the
`contextName` attribute in the annotation. The `contextName` must match
a corresponding `name` configured via @⁠ContextConfiguration.

For example, the following test class configures the name of the second
hierarchy level to be "child" and simultaneously specifies that the
ExampleService should be wrapped in a Mockito spy in the context named
"child". Consequently, Spring will only attempt to create the spy in
the "child" context and will not attempt to create the spy in the
parent context.

@⁠ExtendWith(SpringExtension.class)
@⁠ContextHierarchy({
    @⁠ContextConfiguration(classes = Config1.class),
    @⁠ContextConfiguration(classes = Config2.class, name = "child")
})
class MockitoSpyBeanContextHierarchyTests {

    @⁠MockitoSpyBean(contextName = "child")
    ExampleService service;

    // ...
}

See gh-33293
See gh-34597
See gh-34726
Closes gh-34723

Signed-off-by: Sam Brannen <104798+sbrannen@users.noreply.github.com>
2025-04-10 14:46:50 +02:00
Juergen Hoeller 3afd551174 Add rejectTasksWhenLimitReached option for concurrency limit
Closes gh-34727
2025-04-07 23:54:05 +02:00
Juergen Hoeller ffd15155ee Upgrade to Mockito 5.17 and Checkstyle 10.23 2025-04-07 22:41:45 +02:00
Juergen Hoeller 74ab5e4e25 Enforce circular reference exception between more than two threads as well
See gh-34672
2025-04-07 22:37:19 +02:00
Juergen Hoeller 463541967a Enforce circular reference exception between all thread variations
Closes gh-34672
2025-04-07 17:08:47 +02:00
Sam Brannen 4510b78dfd Include @⁠ContextCustomizerFactories in @⁠NestedTestConfiguration Javadoc 2025-04-07 15:57:20 +02:00
Sam Brannen 63f4ba4b2a Move field injection logic to BeanOverrideTestExecutionListener
For bean override support (@⁠MockitoBean, @⁠TestBean, etc.), the logic
for field injection previously resided in the BeanOverrideRegistry
which resulted in a strange mixture of concerns.

To address that, this commit moves the field injection logic to the
BeanOverrideTestExecutionListener, and the BeanOverrideRegistry now
serves a single role, namely the role of a registry.

Closes gh-34726
2025-04-07 14:05:58 +02:00
Sam Brannen 0c7bc232d6 Redesign BeanOverrideRegistry internals 2025-04-07 13:42:11 +02:00
Sam Brannen 470bf3b0bb Add missing Javadoc for BeanOverrideHandler constructor 2025-04-06 18:18:07 +02:00
Sam Brannen 2ca9f6f064 Indent with tabs instead of spaces in Gradle build scripts 2025-04-06 17:39:23 +02:00
Johnny Lim ecd8cd797e Use implementation Gradle configuration for framework-docs module
Closes gh-34719

Signed-off-by: Johnny Lim <izeye@naver.com>
2025-04-06 17:28:34 +02:00
Juergen Hoeller cc5ae23915 Suppress rollback attempt in case of timeout (connection closed)
Closes gh-34714
2025-04-05 16:03:31 +02:00
Sam Brannen dbd47ff4f9 Implement additional micro performance optimizations
See gh-34717
2025-04-04 15:51:37 +02:00
Sam Brannen 381bc4c405 Polish contribution
See gh-34717
2025-04-04 15:29:10 +02:00
Olivier Bourgain 0f2308e85f Implement micro performance optimizations
- ClassUtils.isAssignable(): Avoid Map lookup when the type is not a
  primitive.

- AnnotationsScanner: Perform low cost array length check before String
  comparisons.

- BeanFactoryUtils: Use char comparison instead of String comparison.
  The bean factory prefix is '&', so we can use a char comparison
  instead of more heavyweight String.startsWith("&").

- AbstractBeanFactory.getMergedBeanDefinition(): Perform the low cost
  check first. Map lookup, while cheap, is still more expensive than
  instanceof.

Closes gh-34717

Signed-off-by: Olivier Bourgain <olivierbourgain02@gmail.com>
2025-04-04 14:34:55 +02:00
Juergen Hoeller ee804ee8fb Avoid throwing of plain RuntimeException 2025-04-04 00:22:24 +02:00
Juergen Hoeller 4e5979c75a Consistent CacheErrorHandler processing for @Cacheable(sync=true)
Closes gh-34708
2025-04-04 00:22:12 +02:00
Juergen Hoeller e7db15b325 Perform type check before singleton check for early FactoryBean matching
Closes gh-34710
2025-04-03 11:59:22 +02:00
Sam Brannen 8f9cbcd86d Add @⁠since tags
See gh-34692
2025-04-03 10:33:19 +02:00
Juergen Hoeller 6bb964e2d0 Explicitly use original ClassLoader in case of package visibility
Closes gh-34684
2025-04-02 23:41:43 +02:00
Taeik Lim a946fe2bf8 Fix broken link for Server-Sent Events
Signed-off-by: Taeik Lim <sibera21@gmail.com>
Closes gh-34705
2025-04-02 17:43:42 +02:00
Sébastien Deleuze 671d972454 Add RestClient.RequestHeadersSpec#exchangeForRequiredValue
This commit adds a variant to RestClient.RequestHeadersSpec#exchange
suitable for functions returning non-null values.

Closes gh-34692
2025-04-02 17:10:01 +02:00
Sébastien Deleuze d9047d39e6 Refine ExchangeFunction Javadoc
See gh-34692
2025-04-02 17:10:01 +02:00
Sébastien Deleuze 4db12806d1 Revert "Add a requiredExchange extension to RestClient"
This reverts commit dcb9383ba1.

See gh-34692
2025-04-02 17:10:01 +02:00
rstoyanchev 290c9c4a19 Use form charset in ServletServerHttpRequest
Closes gh-34675
2025-04-02 09:05:52 +01:00
rstoyanchev e01ad5a08d Polishing in ServletServerHttpRequest
See gh-34675
2025-04-02 09:05:52 +01:00
Brian Clozel b8158df3d6 Create new observation context for WebClient retries
Prior to this commit, the `DefaultWebClient` observability
instrumentation would create the observation context before the reactive
pipeline is fully materialized. In case of errors and retries (with the
`retry(long)` operator), the observation context would be reused for
separate observations, which is incorrect.

This commit ensures that a new observation context is created for each
subscription.

Fixes gh-34671
2025-04-02 09:37:16 +02:00
Juergen Hoeller c4e25a1162 Upgrade to Jetty 12.0.18, Apache HttpClient 5.4.3, Protobuf 4.30.2, Checkstyle 10.22 2025-04-01 23:24:25 +02:00
Juergen Hoeller 48009c8534 Introduce support for concurrent startup phases with timeouts
Closes gh-34634
2025-04-01 22:18:26 +02:00
Juergen Hoeller 203ca30a64 Include cause in MethodInvocationException message
Closes gh-34691
2025-04-01 22:12:17 +02:00
Juergen Hoeller 34ea0461c7 Polishing 2025-04-01 22:12:09 +02:00
Dmitry Sulman fbaeaf12bd Recursively boxing Kotlin nested value classes
This commit is a follow-up to gh-34592. It introduces
recursive boxing of Kotlin nested value classes in CoroutinesUtils.

Signed-off-by: Dmitry Sulman <dmitry.sulman@gmail.com>
Closes gh-34682
2025-04-01 09:53:23 +02:00
Juergen Hoeller 7b08feeb6d Make jar caching configurable through setUseCaches
Closes gh-34678
2025-03-31 16:41:16 +02:00
Juergen Hoeller 743f32675d Only attempt load for CGLIB classes in AOT mode
Closes gh-34677
2025-03-31 16:39:18 +02:00
Juergen Hoeller 3ddc607b3e Add spring.locking.strict property to common appendix
See gh-34303
2025-03-31 16:38:28 +02:00
rstoyanchev f68fb97e7e Remove outdated notes on forwarded headers.
Closes gh-34625
2025-03-31 11:36:15 +01:00
Sam Brannen 044258f085 Support abstract @⁠Configuration classes without @⁠Bean methods again
Historically, @⁠Configuration classes that did not declare @⁠Bean
methods were allowed to be abstract. However, the changes made in
76a6b9ea79 introduced a regression that prevents such classes from
being abstract, resulting in a BeanInstantiationException. This change
in behavior is caused by the fact that such a @⁠Configuration class is
no longer replaced by a concrete subclass created dynamically by CGLIB.

This commit restores support for abstract @⁠Configuration classes
without @⁠Bean methods by modifying the "no enhancement required" check
in ConfigurationClassParser.

See gh-34486
Closes gh-34663
2025-03-31 12:18:55 +02:00
Sam Brannen 36d9357f94 Fix Kotlin compilation errors 2025-03-31 12:02:51 +02:00
Sébastien Deleuze dcb9383ba1 Add a requiredExchange extension to RestClient
Closes gh-34692
2025-03-31 11:55:41 +02:00
Tobias Hänel f8a3077da9 Fix typo in Bean Validation section of reference manual
This commit fixes a minor typo in  the "Java Bean Validation - Customizing
Validation Errors" section of the reference manual.

Closes gh-34686

Signed-off-by: Tobias Hänel <contact@tobias-haenel.de>
2025-03-31 11:55:30 +02:00
Sam Brannen 9fd1d0c6a3 Polish Javadoc
This commit also reverts the change to ASM's SymbolTable class.

See gh-34679
2025-03-29 12:57:08 +01:00
Tran Ngoc Nhan 30fcaef813 Remove unnecessary closing curly brackets in Javadoc
Closes gh-34679

Signed-off-by: Tran Ngoc Nhan <ngocnhan.tran1996@gmail.com>
2025-03-29 12:37:48 +01:00
Juergen Hoeller 75e5a75da5 Enforce circular reference exception within non-managed thread
Closes gh-34672
2025-03-28 20:46:09 +01:00
Juergen Hoeller 9bf01df230 Evaluate lenientLockingAllowed flag per DefaultListableBeanFactory instance
See gh-34303
2025-03-28 20:45:06 +01:00
Sam Brannen 8d2166139f Update SpringCoreTestSuite to include AOT 2025-03-27 16:04:51 +01:00
Sam Brannen 374c3b4545 Provide complete support for qualifier annotations with Bean Overrides
Prior to this commit, the Test Bean Override feature provided support
for overriding beans based on qualifier annotations in several
scenarios; however, qualifier annotations got lost if they were
declared on the return type of the @⁠Bean method for the bean being
overridden and the @⁠BeanOverride (such as @⁠MockitoBean) was based on
a supertype of that return type.

To address that, this commit sets the @⁠BeanOverride field as the
"qualified element" in the RootBeanDefinition to ensure that qualifier
annotations are available for subsequent autowiring candidate
resolution.

Closes gh-34646
2025-03-27 15:29:14 +01:00
Sam Brannen d7e470d3e0 Polishing 2025-03-27 14:05:25 +01:00
Stéphane Nicoll 2862c87601 Make sure the generated values are available from a static context
This commit updates the tests of property values code generated to
invoke the generated code from a `static` context. This ensures that
the test fails if that's not the case.

This commit also updated LinkedHashMap handling that did suffer from
that problem.

Closes gh-34659
2025-03-27 12:06:18 +01:00
Juergen Hoeller aa56b5001a Detect late-set primary markers for autowiring shortcut algorithm
Closes gh-34658
2025-03-26 23:47:42 +01:00
Juergen Hoeller 84430a8db2 Polishing 2025-03-25 17:09:24 +01:00
Juergen Hoeller 6905dff660 Introduce spring.locking.strict=true flag for 6.1.x style bean creation locking
Closes gh-34303
2025-03-25 17:08:55 +01:00
Juergen Hoeller 20736bd06f Introduce acknowledgeAfterListener flag for custom acknowledge handling
Closes gh-34635
2025-03-25 17:06:28 +01:00
Juergen Hoeller 37fb79e8ff Fix qualifier resolution for aliased name against parent factory
Closes gh-34644
2025-03-25 00:08:42 +01:00
Juergen Hoeller d8f8e76791 Check potentially more specific HibernateException cause as well
Closes gh-34633
2025-03-21 15:53:24 +01:00
Juergen Hoeller dc41ff569e Add javadoc notes on potential exception suppression in getBeansOfType
Closes gh-34629
2025-03-21 15:52:42 +01:00
Juergen Hoeller 47651350f3 Polishing 2025-03-21 10:58:40 +01:00
rstoyanchev 15c20c3e65 Fix regression with opaque URI determination
Before RfcUriParser we expected opaque URI's to not have ":/"
after the scheme while the new parser expect opaque URI's to
not have a slash anywhere after the scheme. This commit
restores the previous behavior.

Closes gh-34588
2025-03-21 09:05:41 +00:00
Dmitry Sulman 5455c645f0 Update deprecated Gradle task creation
This commit replaces use of the deprecated Gradle `task` method with
the new `tasks.register` method.

Closes gh-34617

Signed-off-by: Dmitry Sulman <dmitry.sulman@gmail.com>
2025-03-20 10:22:55 +01:00
Brian Clozel adb4b675fc Next development version (v6.2.6-SNAPSHOT) 2025-03-19 18:18:50 +01:00
Sam Brannen 208d52d852 Introduce Checkstyle rule for separator symbol location 2025-03-19 15:35:44 +01:00
rstoyanchev 18c3b637e4 Fix dated Javadoc in MvcUriComponentsBuilder
related to forwarded headers

Closes gh-34615
2025-03-19 12:33:01 +00:00
rstoyanchev 34c69bfc67 Allow empty comment in ServerResponse.SseBuilder
Closes gh-34608
2025-03-19 12:14:37 +00:00
rstoyanchev 37d7af42ac Allow setting ApplicationContext on MockServerWebExchange
Closes gh-34601
2025-03-19 11:06:38 +00:00
Juergen Hoeller cc986cd2e8 Defer triggerAfterCompletion invocation in doRollbackOnCommitException
Closes gh-34595
2025-03-19 10:59:46 +01:00
Sébastien Deleuze 0141725638 Polishing
Closes gh-34592
2025-03-18 17:50:28 +01:00
Dmitry Sulman 0c2ba4e38e Recursively box/unbox nested inline value classes
See gh-34592
Signed-off-by: Dmitry Sulman <dmitry.sulman@gmail.com>
2025-03-18 17:50:28 +01:00
Sam Brannen c6a9aa59a3 Remove BDDMockito Checkstyle rule
This commit removes the BDDMockito Checkstyle rule, since it did not
actually enforce the use of BDDMockito.

This commit also updates static imports to use Mockito instead of
BDDMockito where appropriate (automated via the Eclipse IDE Organize
Imports clean-up task).

Closes gh-34616
2025-03-18 16:35:57 +01:00
Juergen Hoeller ad949a7450 Add includeNonSingletons flag for ObjectProvider stream access
Closes gh-34591
2025-03-18 16:10:30 +01:00
Juergen Hoeller 86b2617c7f Suggest compilation with -parameters in case of ambiguity
Closes gh-34609
2025-03-17 19:22:56 +01:00
Juergen Hoeller 760376c318 Restore check for jar root existence (now via getEntryName/getJarEntry)
Closes gh-34607
2025-03-17 19:20:41 +01:00
Juergen Hoeller 5b6abe4c13 Upgrade to ASM 9.8 (for early Java 25 support)
Closes gh-34600
2025-03-17 19:16:42 +01:00
Sam Brannen 7a839e988a Make dependencies on AssertJ and JUnit in spring-core-test optional
This commit also removes unnecessary dependencies in
spring-core-test.gradle and updates framework-docs.gradle accordingly.

Closes gh-34612
2025-03-17 18:11:25 +01:00
Sébastien Deleuze 46859d6391 Polishing
Closes gh-34594
2025-03-17 11:39:15 +01:00
Russell Bolles 573e74b8bd Refine FormHttpMessageConverter exception handling
FormHttpMessageConverter could throw a more specific
HttpMessageNotReadableException instead of an IllegalArgumentException
when the http form data is invalid.

See gh-34594
Signed-off-by: Russell Bolles <rbolles@netflix.com>
2025-03-17 11:37:42 +01:00
Sam Brannen 6c231804a0 Upgrade to Mockito 5.16.1 2025-03-16 15:33:33 +01:00
Tran Ngoc Nhan 7c3913050a Fix formatting and update links to scripting libraries and HDIV
Closes gh-34603

Signed-off-by: Tran Ngoc Nhan <ngocnhan.tran1996@gmail.com>
Co-authored-by: Sam Brannen <104798+sbrannen@users.noreply.github.com>
(cherry picked from commit 666e2df0f3)
2025-03-15 13:51:28 +01:00
Sam Brannen ec488282a8 Upgrade to JUnit 5.12.1 2025-03-14 18:03:23 +01:00
Juergen Hoeller 911cdb2ad0 Add resolveAutowireCandidates variant with includeNonSingletons and allowEagerInit
Closes gh-34591
2025-03-13 18:48:43 +01:00
Brian Clozel 0f83c483bb Remove invalid link from reference documentation
Closes gh-34593
2025-03-13 15:26:09 +01:00
Sébastien Deleuze c9050607bc Fix StringUtils#uriDecode Javadoc
Closes gh-34590
2025-03-13 12:57:44 +01:00
Brian Clozel 5e82ee6bd7 Next development version (v6.2.5-SNAPSHOT) 2025-03-13 09:44:23 +01:00
Juergen Hoeller 387677eae8 Upgrade to JUnit 5.12
Closes gh-34416
2025-03-12 12:46:34 +01:00
Juergen Hoeller 4a314867d7 Upgrade to Reactor 2024.0.4 and Micrometer 1.14.5
Closes gh-34578
Closes gh-34580
2025-03-12 11:51:33 +01:00
Sam Brannen 020f556841 Support custom attribute with a value in MockCookie.parse()
Prior to this commit, MockCookie.parse() failed with an
IllegalArgumentException when attempting to parse a custom attribute
with a value, such as "Version=1". This is a regression that was
inadvertently introduced in 7fc4937199
when adding support for the "Partitioned" attribute which does not
support a value.

This commit addresses this regression by parsing both the name and the
value from an optional, custom attribute.

See gh-31454
Closes gh-34575
2025-03-12 11:19:38 +01:00
rstoyanchev 6ea3b5a0e8 Fix Javadoc failure
See gh-34549
2025-03-10 20:59:33 +00:00
rstoyanchev d764087dbf Correct since tag
See gh-34549
2025-03-10 18:17:47 +00:00
rstoyanchev 9ab43b138a Enhancement in HandlerMethodValidationException
Add dedicated method to Visitor for constraints directly on a
RequestBody method parameter (rather than nested).

Closes gh-34549
2025-03-10 18:01:55 +00:00
rstoyanchev 09ae080b99 isDisconnectedClientException protected for null
Closes gh-34533
2025-03-10 17:19:13 +00:00
Juergen Hoeller 70a1b2fae3 Upgrade to Checkstyle 10.21.4 2025-03-10 11:00:53 +01:00
Juergen Hoeller 90ddb40d7a Upgrade to Jetty 12.0.17 and Jetty Reactive HttpClient 4.0.9
Includes Apache HttpClient 5.4.1, Netty 4.1.119, Jackson 2.18.3, Gson 2.12.1, FreeMarker 2.3.34, Protobuf 4.30, Groovy 4.0.26, Jython 2.7.4, JRuby 9.4.12, Caffeine 3.2, QDox 2.2, Awaitility 4.3, EasyMock 5.5, HtmlUnit 4.10

Closes gh-34561
2025-03-10 11:00:35 +01:00
Juergen Hoeller 143985e862 Add tests for primary/fallback/defaultCandidate precedence
Closes gh-34449
2025-03-08 12:22:14 +01:00
Juergen Hoeller 4bd280b87e Explain availability and uniqueness (including primary/fallback/default) in javadoc
Closes gh-34447
2025-03-08 12:22:04 +01:00
Juergen Hoeller 5877a38fa1 Add explicit note on JSpecify support in Spring Framework 6.2 vs 7.0
Closes gh-34551
2025-03-08 12:21:46 +01:00
Vedran Pavic 94d29bac9f Fix typo in Spring MVC error responses documentation
Closes gh-34552

Signed-off-by: Vedran Pavic <vedran@vedranpavic.com>
2025-03-07 13:37:57 +01:00
Dmitry Sulman a6f3f18d89 Allow supertypes in ContentResultMatchersDsl matchers
Closes gh-34542
Signed-off-by: Dmitry Sulman <dmitry.sulman@gmail.com>
2025-03-07 09:29:48 +01:00
Sam Brannen 8334cb1abb Revise tests to better express intent 2025-03-06 17:32:32 +01:00
Sam Brannen 2d88f18bf1 Update Javadoc 2025-03-06 16:26:40 +01:00
Sam Brannen 15a6641677 Clean up warnings in Gradle build 2025-03-06 16:26:32 +01:00
Juergen Hoeller 63c8e7cb5d Restore lenient matching of unresolved nested bound
Closes gh-34541
2025-03-06 15:44:12 +01:00
Sam Brannen c5ecc50bfe Document wrapping behavior for TestExecutionListener callbacks
Closes gh-34422
2025-03-06 13:31:23 +01:00
Sam Brannen 0fd94f1b9f Polishing 2025-03-06 13:30:20 +01:00
Juergen Hoeller 2472126475 Add fallback to pre-6.2.3 behavior for unresolvable generics
Closes gh-34535
2025-03-05 22:45:22 +01:00
Juergen Hoeller f3b50c9a58 Ignore null beans in SimpleAutowireCandidateResolver.resolveAutowireCandidates
Closes gh-34543
2025-03-05 22:44:09 +01:00
Sam Brannen 0a6e666857 Ensure GenericTypeResolverTests compiles with Eclipse compiler 2025-03-05 14:07:52 +01:00
Juergen Hoeller 403362fe3c Polishing 2025-03-04 18:44:56 +01:00
Juergen Hoeller 534f123fe8 Add explicit FileSystemResource path check for trailing slash
Closes gh-34509
2025-03-04 18:44:25 +01:00
Sam Brannen e421104cf3 Supply RuntimeHints to an AotContextLoader
This commit introduces a new loadContextForAotProcessing(...) variant
in AotContextLoader which accepts a RuntimeHints argument. This new
method is an interface default method which delegates to the existing
loadContextForAotProcessing(MergedContextConfiguration) variant for
backward compatibility.

In addition, the original loadContextForAotProcessing(...) variant is
now deprecated and has been converted to an interface default method
which throws an UnsupportedOperationException.

Note, however, that the framework now only invokes the new
loadContextForAotProcessing(...) variant within TestContextAotGenerator.

Closes gh-34513
2025-03-03 17:39:56 +01:00
Sam Brannen adfeba23e0 Polishing 2025-03-03 17:39:47 +01:00
Sam Brannen 7789e12575 Polishing 2025-03-03 15:04:42 +01:00
Sam Brannen 30d793cefe Revise deprecation of SqlQuery.rowsExpected
Closes gh-34526
2025-03-03 14:33:19 +01:00
Yanming Zhou 78cc5df748 Deprecate unused "rowsExpected" property of SqlQuery for removal
See gh-34526

Signed-off-by: Yanming Zhou <zhouyanming@gmail.com>
2025-03-03 14:33:14 +01:00
Juergen Hoeller 5ffaea7a43 Avoid JarURLConnection resource leak in AbstractFileResolvingResource.exists()
Closes gh-34528
2025-03-03 13:32:51 +01:00
Brian Clozel 1633ad24f3 Ignore nohttp check on buildSrc/build 2025-03-03 11:23:21 +01:00
Rhett CfZhuang 5faf2ed7f4 Fix missing Partitioned cookie support in reactive HTTP clients
This commit adds support for the Partitioned cookie attribute in the
reactive HTTP clients that support this: Reactor and HttpComponents.

Closes gh-34521

Signed-off-by: Rhett CfZhuang <dark.momo985@gmail.com>
[brian.clozel@broadcom.com: rework tests and support HttpComponents]
Signed-off-by: Brian Clozel <brian.clozel@broadcom.com>
2025-03-03 11:23:13 +01:00
Juergen Hoeller d2733cea36 Notify lenientCreationFinished condition after locked creation as well
Closes gh-34522
2025-03-01 22:20:23 +01:00
Johnny Lim 108caea385 Use ORDER constant in MockitoResetTestExecutionListener
See gh-34404
Closes gh-34445

Signed-off-by: Johnny Lim <izeye@naver.com>
2025-03-01 13:35:46 +01:00
rstoyanchev 7e9ac120ac Deprecate UrlPathHelper in ServletWebSocketHandlerRegistry
Closes gh-34508
2025-02-28 14:39:29 +00:00
Juergen Hoeller c64dae3623 Avoid getTargetConnection call on transaction-aware Connection close
Closes gh-34484
2025-02-28 14:12:51 +01:00
Juergen Hoeller 559ea6c480 Defensively call isShutdown method for executor description
Closes gh-34514
2025-02-28 14:11:57 +01:00
Tran Ngoc Nhan 34315fc20f Fix web and webflux reference links
Closes gh-34517
Signed-off-by: Tran Ngoc Nhan <ngocnhan.tran1996@gmail.com>
2025-02-28 12:06:11 +01:00
Juergen Hoeller 3bb4795d43 Resolve variable bounds at outermost recursion level only
Closes gh-34504
2025-02-27 22:51:41 +01:00
Ryan Prayogo aae2952a32 Replace DefaultKeyGenerator with SimpleKeyGenerator
DefaultKeyGenerator has been deprecated and replaced with SimpleKeyGenerator

Signed-off-by: Ryan Prayogo <57620+ryanprayogo@users.noreply.github.com>
2025-02-27 18:53:10 +01:00
rstoyanchev f62251aebd Avoid pathVar-requestParam name collision
Closes gh-34499
2025-02-27 14:34:26 +00:00
rstoyanchev f92f9c1d5b Fix handling of timeout in SseEmitter
Closes gh-34426
2025-02-27 14:34:26 +00:00
Sam Brannen 2b38c00656 Upgrade to Gradle 8.13
Closes gh-34495
2025-02-26 15:26:49 +01:00
Juergen Hoeller 06721ba6c0 Lenient fallback when cached WeakReference returns null
Closes gh-34423
2025-02-26 10:24:24 +01:00
Brian Clozel b6a5402d88 Fix multiple Content-Language values in MockHttpServletResponse
Prior to this commit, `MockHttpServletResponse` would only support
adding a `Content-Language` once. Adding multiple header values would
always replace the content-language property in the response and the
entire header value.

This commit ensures that this behavior is supported.

Fixes gh-34488
2025-02-25 17:11:37 +01:00
Juergen Hoeller aff9ac72ec Avoid unnecessary CGLIB processing on configuration classes
Closes gh-34486
2025-02-25 16:20:12 +01:00
Brian Clozel f895d762cd Remove duplicate Content-Type header in error cases
Prior to this commit, the `DispatcherServlet` would try and reset the
response buffer in case of errors, if the response is not committed
already. This allows for more flexible error handling, even if the
response was being handled already when it errored.

Resetting the response buffer clears the body but leaves HTTP response
headers intact. This is done on purpose as to not clear headers
previously added by Servlet Filters. By leaving in place some headers
like "Content-Type", this does not take into account the fact that the
response body was cleared and that error handling will perform another
round of content negotiation. While this isn't a problem for some
Servlet containers which enforce a single "Content-Type" header value,
this can cause multiple/duplicate values for some others.

This commit ensures that the "Content-Type" response header is removed
at the same time as we clear the "producible media types" attribute:
another pass of content negotiation will be performed for error
handling.

Fixes gh-34366
2025-02-25 10:43:19 +01:00
Juergen Hoeller 634d1dd20b Consistent default ClassLoader fallback in hint classes
Closes gh-34470
2025-02-23 15:15:25 +01:00
Juergen Hoeller 725b02a66d Ignore entries cache if no matching root entry path found
Closes gh-34446
2025-02-23 14:02:57 +01:00
Brian Clozel 5a0bd9e5d4 Fix null value support in ContentCachingResponseWrapper
Prior to this commit, calling `setHeader` on the response wrapper would
have a separate code path for the "Content-Length" header. This did not
support calls with `null` values and would result in an exception.

This commit ensures that the cached content length value is reset in
this case and that the call is forwarded properly to the superclass.

Fixes gh-34460
2025-02-21 14:33:03 +01:00
Brian Clozel 6dd73ae3e4 Handle null values in MockHttpServletResponse#setHeader
Prior to this commit, `MockHttpServletResponse#setHeader` would not
remove the header entry when given a `null` value, as documented in the
Servlet API.
This commit ensures that this behavior is enforced.

Fixes gh-34464
2025-02-21 14:23:12 +01:00
Gang Cheng 4606337180 Prevent hung uploads in MultipartParser
This commit adds an onRequest() hook to request more data from
the source in order to avoid hung uploads in MultipartParser.

Closes gh-34388
Signed-off-by: Gang Cheng <chenggangpro@gmail.com>
2025-02-21 11:32:54 +01:00
Sébastien Deleuze f45a08d4f5 Remove BaseDefaultCodecs#synchronossMultipartPresent
Closes gh-34459
2025-02-20 09:07:26 +01:00
Sébastien Deleuze d62ce2969a Fix broken antora task
See https://github.com/spring-io/antora-extensions/pull/43

Closes gh-34454
2025-02-19 16:40:50 +01:00
Johnny Lim ed3fd12210 Add missing @since tags in MockHttpServletRequestDsl
This commit adds missing `@since` tags for formField() and formFields in
MockHttpServletRequestDsl. See gh-34412 related issue.

Closes gh-34448
Signed-off-by: Johnny Lim <izeye@naver.com>
2025-02-19 13:58:31 +01:00
Vincent Potucek 6f596e2e84 Add a project icon for IntelliJ IDEA
The icon comes from https://spring.io/img/projects/spring-framework.svg.

Closes gh-34444
Signed-off-by: Vincent Potucek <vincent.potucek@sap.com>
2025-02-19 13:45:16 +01:00
Juergen Hoeller 6786e1c3e5 Apply fallback in case of initial SmartClassLoader mismatch as well
See gh-34423
2025-02-18 20:40:13 +01:00
Juergen Hoeller 93134fd4d1 Apply fallback in case of any exception coming out of createClass
Closes gh-34423
2025-02-18 15:16:25 +01:00
Juergen Hoeller d0ceefedc6 Mark XML-configured executor/scheduler as infrastructure bean
Closes gh-34015
2025-02-18 13:13:34 +01:00
Juergen Hoeller e230ea537c Consistently resolve unique default candidate bean
Closes gh-34432
2025-02-18 13:11:36 +01:00
Juergen Hoeller 94eb6006e8 Leniently accept same singleton instance if implicitly appeared
Closes gh-34427
2025-02-18 13:01:08 +01:00
Sébastien Deleuze 2576702cda Use public interface for HTTP Interface documentation
Closes gh-34443
2025-02-18 11:57:04 +01:00
Sébastien Deleuze 2099e046d3 Polishing
See gh-34439
2025-02-18 11:40:16 +01:00
Sébastien Deleuze 2ee7a8e77a Add missing converters to DefaultRestClientBuilder
With this commit, DefaultRestClientBuilder configures the same
default converters than RestTemplate.

Closes gh-34439
2025-02-18 11:19:07 +01:00
Johnny Lim 3c40e5e501 Add Javadoc since for HandlerMethod(HandlerMethod, Object, boolean)
See 56c4d2d

Closes gh-34431
Signed-off-by: Johnny Lim <izeye@naver.com>
2025-02-16 12:10:01 +01:00
Sam Brannen 9e45178202 Stop referring to "Java 8" in AnnotationUtils 2025-02-13 15:59:20 +01:00
Sam Brannen d82e70e345 Cross reference annotation search APIs in Javadoc
Closes gh-34421
2025-02-13 15:59:08 +01:00
Stéphane Nicoll 1109892134 Next development version (v6.2.4-SNAPSHOT) 2025-02-13 14:19:02 +01:00
Sam Brannen a8be11ebaf Upgrade to TestNG 7.11 2025-02-13 13:08:54 +01:00
Juergen Hoeller dfc10c1a8d Wait for lenient bean creation in non-locked threads as well
Closes gh-34349
2025-02-12 23:06:22 +01:00
Juergen Hoeller bbb593db48 Consistently ignore non-jar files in classpath
Closes gh-34417
2025-02-12 19:18:30 +01:00
Sam Brannen b2134ee71f Avoid Gradle build warnings about @⁠SuppressFBWarnings from FindBugs
In order to avoid Gradle build warnings about @⁠SuppressFBWarnings, this
commit introduces a testCompileOnly dependency on `findbugs` in the
spring-webmvc module so that the class file for @⁠SuppressFBWarnings is
available to the compileTestJava task.

Closes gh-34418
2025-02-12 17:49:23 +01:00
Sam Brannen 440a259b71 Clean up warnings in Gradle build 2025-02-12 16:46:41 +01:00
Sam Brannen 6174055910 Link to @⁠MockitoBean, @⁠MockitoSpyBean, & @⁠TestBean Javadoc from ref docs 2025-02-12 16:31:33 +01:00
Sam Brannen e31ce359a1 Support @⁠MockitoSpyBean at the type level on test classes
Prior to this commit, @⁠MockitoSpyBean could only be declared on fields
within test classes, which prevented developers from being able to
easily reuse spy configuration across a test suite.

With this commit, @⁠MockitoSpyBean is now supported at the type level
on test classes, their superclasses, and interfaces implemented by
those classes. @⁠MockitoSpyBean is also supported on enclosing classes
for @⁠Nested test classes, their superclasses, and interfaces
implemented by those classes, while honoring @⁠NestedTestConfiguration
semantics.

In addition, @⁠MockitoSpyBean:

- has a new `types` attribute that can be used to declare the type or
  types to spy when @⁠MockitoSpyBean is declared at the type level

- can be declared as a repeatable annotation at the type level

- can be declared as a meta-annotation on a custom composed annotation
  which can be reused across a test suite (see the @⁠SharedSpies
  example in the reference manual)

To support these new features, this commit also includes the following
changes.

- MockitoSpyBeanOverrideProcessor has been revised to support
  @⁠MockitoSpyBean at the type level.

- The "Bean Overriding in Tests" and "@⁠MockitoBean and
  @⁠MockitoSpyBean" sections of the reference manual have been fully
  revised.

See gh-34408
Closes gh-33925
2025-02-12 15:54:54 +01:00
Juergen Hoeller b336bbe539 Wait for lenient bean creation in locked thread when necessary
Closes gh-34349
2025-02-12 12:17:02 +01:00
Sébastien Deleuze 056757b493 Refine tests in MockMvcExtensionsTests
Closes gh-34412
2025-02-12 11:33:42 +01:00
Kevin Houtz 79c5fec1be Add form fields support to MockMvc Kotlin DSL
See gh-34412

Signed-off-by: Kevin Houtz <kevin@khoutz.com>
2025-02-12 11:33:29 +01:00
Brian Clozel 689782cbad Make ProblemDetail Serializable
This commit makes the `ProblemDetail` type implement `Serializable` in
order to be serialized and shared in distributed systems.

Closes gh-34409
2025-02-11 22:33:05 +01:00
Juergen Hoeller 2df90e32c0 Upgrade to Netty 4.1.118 and Checkstyle 10.21.2 2025-02-11 22:10:15 +01:00
Juergen Hoeller f53da04717 Align with SmartClassLoader handling for AOP proxy classes
Closes gh-34274
2025-02-11 22:10:02 +01:00
Sam Brannen 124b38450b Remove unused code and improve assertion
See gh-34363
2025-02-11 16:30:52 +01:00
Sam Brannen b07217ab67 Use ConversionService to convert POJO to array for SpEL varargs invocations
Prior to this commit, if an appropriate Converter was registered with
the ConversionService that converts from a POJO to an array and that
ConversionService was registered with the Spring Expression Language
(SpEL) TypeConverter, an attempt to invoke a varargs method in a SpEL
expression with such a POJO would fail because the ConversionService
was not used to convert the POJO to an array suitable for the varargs
method invocation.

This commit revises the implementations of convertArguments(...) and
convertAllMethodHandleArguments(...) in ReflectionHelper to support
such use cases.

Closes gh-34371
2025-02-11 16:11:13 +01:00
Sam Brannen aa7e84c89f Polishing 2025-02-11 16:11:13 +01:00
Stéphane Nicoll bd1c7b379f Merge pull request #34400 from canattofilipe
* pr/34400:
  Polish "Use correct method to retrieve DefaultListableBeanFactory"
  Use correct method to retrieve DefaultListableBeanFactory

Closes gh-34400
2025-02-11 16:01:04 +01:00
Stéphane Nicoll 6af19244a5 Polish "Use correct method to retrieve DefaultListableBeanFactory"
See gh-34400
2025-02-11 16:00:34 +01:00
canattofilipe 328dd71f6e Use correct method to retrieve DefaultListableBeanFactory
See gh-34400

Signed-off-by: canattofilipe <canattofilipe@gmail.com>
2025-02-11 15:57:01 +01:00
rstoyanchev d04883f839 HTTP Interface client handles query param with ":"
Closes gh-34364
2025-02-11 11:11:05 +00:00
rstoyanchev 9f55296049 Nested list/map/array with constructor binding
Closes gh-34305
2025-02-11 11:11:05 +00:00
rstoyanchev 4591a67641 Handle [] leniently in constructor binding
See gh-34305
2025-02-11 11:11:05 +00:00
Sam Brannen 9797bc0acd Expose order values of TestExecutionListener implementations as constants
Prior to this commit, the order values of TestExecutionListener
implementations were hard-coded in their getOrder() methods.

To benefit users and integrators, this commit exposes those order values
as an ORDER constant in each TestExecutionListener.

See gh-34225
Closes gh-34404
2025-02-11 11:53:33 +01:00
Sam Brannen 9d3374b28d Finish incomplete sentences 2025-02-11 11:53:33 +01:00
Sam Brannen 09086fc648 Revise TestExecutionListener order values documentation
See gh-34265
2025-02-11 11:53:33 +01:00
Mengqi Xu 128e90064e Document order values for TestExecutionListener implementations
Closes gh-34265

Signed-off-by: Mengqi Xu <2663479778@qq.com>
2025-02-11 11:53:33 +01:00
Stéphane Nicoll 16e3973118 Upgrade to Reactor 2024.0.3
Closes gh-34403
2025-02-11 11:36:47 +01:00
Stéphane Nicoll 5c37d07ad3 Upgrade to RSocket 1.1.5
Closes gh-34402
2025-02-11 10:39:21 +01:00
Stéphane Nicoll 12891d1975 Upgrade to Micrometer 1.14.4
Closes gh-34401
2025-02-11 10:34:33 +01:00
Juergen Hoeller 1ca941ba9a Consistently resolve renamed type variables
Closes gh-34386
2025-02-10 15:51:39 +01:00
Sam Brannen 17a94fb110 Improve warning for unexpected use of value attribute as @⁠Component name
Prior to this commit, if a String 'value' attribute of an annotation
was annotated with @⁠AliasFor and explicitly configured to alias an
attribute other than @⁠Component.value, the value was still used as the
@⁠Component name, but the warning message that was logged stated that
the 'value' attribute should be annotated with
@⁠AliasFor(annotation=Component.class). However, it is not possible to
annotate an annotation attribute twice with @⁠AliasFor.

To address that, this commit revises the logic in
AnnotationBeanNameGenerator so that it issues a log message similar to
the following in such scenarios.

WARN o.s.c.a.AnnotationBeanNameGenerator - Although the 'value'
attribute in @⁠example.MyStereotype declares @⁠AliasFor for an
attribute other than @⁠Component's 'value' attribute, the value is
still used as the @⁠Component name based on convention. As of Spring
Framework 7.0, such a 'value' attribute will no longer be used as the
@⁠Component name.

See gh-34346
Closes gh-34317
2025-02-10 13:29:40 +01:00
Sam Brannen 2fcae65853 Polishing 2025-02-10 13:29:33 +01:00
rstoyanchev 7a0fe7d14f WebAsyncManager wraps disconnected client errors
If the Servlet container delegates a disconnected client error via
AsyncListener#onError, wrap it as AsyncRequestNotUsableException
for more targeted and consistent handling of such errors.

Closes gh-34363
2025-02-10 11:14:21 +00:00
rstoyanchev ccdaed594e Polishing contribution
Closes gh-34333
2025-02-10 11:14:21 +00:00
Branden Clark c41b0140cd Check hasNext on sessionIds in UserDestinationResult
See gh-34333

Signed-off-by: Branden Clark <brandenrayclark@gmail.com>
2025-02-10 11:14:21 +00:00
rstoyanchev ceffda7874 Polishing contribution
Closes gh-34362
2025-02-10 11:14:21 +00:00
Jared Wiltshire 49f9b40fba Support RFC 8441 upgrades over HTTP/2 CONNECT
See gh-34362

Signed-off-by: Jared Wiltshire <jazdw@users.noreply.github.com>
2025-02-10 11:14:21 +00:00
Sam Brannen d59991fcc9 Revise contribution
See gh-34006
2025-02-10 11:42:24 +01:00
Yanming Zhou cf46f391d7 Improve diagnostics when a Bean Override cannot be selected by type
See gh-34004
Closes gh-34006

Signed-off-by: Yanming Zhou <zhouyanming@gmail.com>
2025-02-10 11:06:10 +01:00
Andras Dobrosi 2b4c7d09b0 Match ContentDisposition attributes case-insensitively
This commit ensures that `ContentDisposition` parses attributes like
"filename" and "filename*" in a case insensitive fashion, per RFC 6266.

Closes gh-34383

Signed-off-by: Andras Dobrosi <dobrosi@gmail.com>
[brian.clozel@broadcom.com: apply code conventions]
Signed-off-by: Brian Clozel <brian.clozel@broadcom.com>
2025-02-10 09:21:02 +01:00
Johnny Lim 61138698c6 Fix copyright end year in ServerResponseResultHandler
See https://github.com/spring-projects/spring-framework/commit/1cea1fe962cc95544fce7be8fd2f56419e89aaaf#diff-fa20069f0305b5aee07bf90a7f8d0502a6ee139892639b7dfe470b54c3a8574aL2-R2

See gh-34066
Closes gh-34391

Signed-off-by: Johnny Lim <izeye@naver.com>
2025-02-09 11:38:24 +01:00
Daeho Kwon 3548e872b9 Fix deprecation warnings in RuntimeHintsAgentPlugin
Closes gh-34390

Signed-off-by: Daeho Kwon <trewq231@naver.com>
2025-02-08 16:19:26 +01:00
Sam Brannen 305686dbf7 Ensure Bean Overrides are discovered once in @⁠Nested hierarchies
Changes made to the Bean Override search algorithms in commit
9181cce65f resulted in a regression that caused tests to start failing
due to duplicate BeanOverrideHandlers under the following circumstances.

- An enclosing class (typically a top-level test class) declares a
  @⁠BeanOverride such as @⁠MockitoBean.

- An inner class is declared in that enclosing class.

- A @⁠Nested test class which extends that inner class is declared in
  the same enclosing class.

The reason for the duplicate detection is that the current search
algorithm visits the common enclosing class twice.

To address that, this commit revises the search algorithm in
BeanOverrideHandler so that enclosing classes are only visited once.

See gh-33925
Closes gh-34324
2025-02-08 13:30:22 +01:00
Juergen Hoeller ace2f0a3e5 Expose resolveAutowireCandidates method with basic candidate filtering
DependencyObjectProvider is aligned to check basic autowire-candidate status even in case of custom filtering.

Closes gh-34203
2025-02-07 19:00:25 +01:00
Juergen Hoeller 1a573d6e3c Lazily retrieve TypeDescriptor annotations on demand
Closes gh-33948
2025-02-07 18:55:24 +01:00
Sam Brannen bb7a8006c5 Fix @⁠Nullable declaration in DataBinder on 6.2.x 2025-02-07 18:33:03 +01:00
Sam Brannen ba56c1a8f4 Fix copy-n-paste error 2025-02-07 18:26:26 +01:00
Sam Brannen 9107f7b592 Honor @⁠Primary before fallback qualifier for Bean Overrides
Prior to this commit, test bean overrides (for example, @⁠MockitoBean,
@⁠TestBean, etc.) eagerly honored the name of the annotated field as a
fallback qualifier, effectively ignoring @⁠Primary and @⁠Fallback
semantics for certain use cases.

This led to situations where a bean override for a test would select a
different bean than the core container would for the same autowiring
metadata.

To address that, this commit revises the implementation of
BeanOverrideBeanFactoryPostProcessor so that @⁠Primary and @⁠Fallback
semantics are consistently honored before attempting to use the
annotated field's name as a fallback qualifier.

Closes gh-34374
2025-02-07 18:06:52 +01:00
rstoyanchev 1d7cb4fc13 Defer initialization of HandlerMethod validation flags
Re-create the HandlerMethod only after the original is used as a key
in the CORS lookup map.

Closes gh-34379
2025-02-07 13:20:16 +00:00
rstoyanchev 84992536c5 Defer initialization of HandlerMethod validation flags
Re-create the HandlerMethod only after the original is used as a key
in the CORS lookup map.

Closes gh-34375
2025-02-07 13:10:55 +00:00
rstoyanchev ff49b0b683 Align AnnotatedMethod#equals and #hashcode
See gh-34375
2025-02-07 13:10:55 +00:00
rstoyanchev 56c4d2d4ca Improve HandlerMethod#resolvedFromHandlerMethod initialization
Ensure the original instance is always the one returned no matter how
many times the HandlerMethod is re-created.

Make the constructor protected to allow subclasses to re-create the
HandlerMethod as the concrete subclass.

See gh-34375
2025-02-07 13:10:55 +00:00
Brian Clozel 174d0e4576 Fix "Nth day of week" Quartz-style cron expressions
Prior to this commit, `CronExpression` would support Quartz-style
expressions with "Nth occurence of a  dayOfWeek" semantics by using the
`TemporalAdjusters.dayOfWeekInMonth` JDK support. This method will
return the Nth occurence starting with the month of the given temporal,
but in some cases will overflow to the next or previous month.
This behavior is not expected for our cron expression support.

This commit ensures that when an overflow happens (meaning, the
resulting date is not in the same month as the input temporal), we
should instead have another attempt at finding a valid month for this
expression.

Fixes gh-34360
2025-02-06 18:27:07 +01:00
Sébastien Deleuze dba4881318 Refine the CGLIB error message with native
See gh-34370
2025-02-06 09:18:51 +01:00
Sébastien Deleuze 16ec08aec8 Provide a more actionable CGLIB error message with native
Closes gh-34370
2025-02-05 17:47:25 +01:00
rstoyanchev 462c2bd538 Enhance constructor binding for List/Map/Array
Support List/Map/Array of simple values, or values supported
by type conversion.

Closes gh-34305
2025-02-05 14:26:12 +00:00
rstoyanchev 7f29f0e663 Revert commit 3505c4bcad
The fix did not address the issue. It only made the constructor not
fail with tests succeeding due to setter binding instead.

See gh-34043
2025-02-05 14:26:12 +00:00
rstoyanchev 3898482d3f Revert commit 0f38c28e91
The fix is not how the issue needs to be addressed.

See gh-34121
2025-02-05 14:26:12 +00:00
rstoyanchev bb7ce21076 Expose Locale to method validation in WebFlux
Closes gh-33810
2025-02-05 14:26:12 +00:00
Stéphane Nicoll fd4dee7ce3 Merge pull request #34183 from remeio
* pr/34183:
  Polish "Add support for multidimensional arrays"
  Add support for multidimensional arrays

Closes gh-34183
2025-02-05 14:42:35 +01:00
Stéphane Nicoll ec037b1ec0 Polish "Add support for multidimensional arrays"
See gh-34183
2025-02-05 14:35:33 +01:00
xumengqi 68c1e2ac92 Add support for multidimensional arrays
See gh-34183
2025-02-05 14:23:09 +01:00
Sam Brannen 819a7c86c1 Clarify component scanning of abstract classes with @⁠Lookup methods
Due to changes in gh-19118, classes that contain @⁠Lookup methods are
no longer required to be concrete classes for use with component
scanning; however, the reference documentation still states that such
classes must not be abstract.

This commit therefore removes the outdated reference documentation and
updates the corresponding Javadoc.

See gh-19118
Closes gh-34367
2025-02-05 13:39:29 +01:00
Stéphane Nicoll 9c1346d240 Merge pull request #34316 from JoshuaChen
* pr/34316:
  Polish "Handle arbitrary JoinPoint argument index"
  Handle arbitrary JoinPoint argument index

Closes gh-34316
2025-02-05 11:50:35 +01:00
Stéphane Nicoll fb6e86551a Polish "Handle arbitrary JoinPoint argument index"
See gh-34316
2025-02-05 11:49:56 +01:00
Joshua Chen 13ba770428 Handle arbitrary JoinPoint argument index
See gh-34316

Signed-off-by: Joshua Chen <27291761@qq.com>
2025-02-05 11:44:51 +01:00
Stéphane Nicoll a4d99d6201 Merge pull request #33957 from anaconda875
* pr/33957:
  Polish "Use proper return type in AsyncExecutionInterceptor"
  Use proper return type in AsyncExecutionInterceptor

Closes gh-33957
2025-02-04 16:01:38 +01:00
Stéphane Nicoll 3923150dad Polish "Use proper return type in AsyncExecutionInterceptor"
See gh-33957
2025-02-04 15:59:35 +01:00
Bao.Ngo 8eb24458f8 Use proper return type in AsyncExecutionInterceptor
See gh-33957
2025-02-04 15:58:53 +01:00
Juergen Hoeller cda7e98643 Lazily override default editors when actually needed
Closes gh-34361
2025-02-04 13:26:16 +01:00
Stéphane Nicoll e92809d470 Update CI actions 2025-02-04 09:29:51 +01:00
rstoyanchev f477c1653d Allow WebSocket over HTTP CONNECT
Closes gh-34044
2025-02-03 15:28:27 +00:00
rstoyanchev 1cea1fe962 Polishing contribution
Closes gh-34066
2025-02-03 15:15:21 +00:00
Tarek Mues 8b07f93620 Add getters ServerResponseResultHandler
See gh-34066
2025-02-03 15:11:47 +00:00
Pierre Rossato a9ecf90524 Minor update in WebSocket STOMP documentation
Closes gh-34353

Signed-off-by: Pierre Rossato <pierre.rossato@gmail.com>
2025-02-03 15:10:32 +00:00
Daeho Kwon 7536777a18 Replace hardcoded "Sec-WebSocket-Version" with constant
Closes gh-34319

Signed-off-by: Daeho Kwon <trewq231@naver.com>
2025-02-03 15:06:28 +00:00
Juergen Hoeller 1b18928bf0 Explicitly set custom ClassLoader on CGLIB Enhancer
Closes gh-34274
2025-02-03 15:23:51 +01:00
Juergen Hoeller 8c2b44bd6c Support filtered/unfiltered stream access on ObjectProvider
Closes gh-34318
Closes gh-34203
2025-02-03 15:04:27 +01:00
Juergen Hoeller 2df8ea94e0 Clean root entry path to match jar entries format in cache
Closes gh-34348
2025-01-31 22:26:14 +01:00
Juergen Hoeller 323e52b5a9 Continue with pre-instantiation when current bean is in creation already
Closes gh-34349
2025-01-31 15:16:52 +01:00
Juergen Hoeller ed994dcd97 Resolve bounds for type variable before emptiness check
Closes gh-34328
2025-01-30 15:35:14 +01:00
Juergen Hoeller 4c3b435d23 Enforce exact match for bounds of nested type variable
Closes gh-34300
2025-01-30 15:34:55 +01:00
Brian Clozel cfe2db0581 Improve reference docs on RestClient.retrieve()
As of Spring Framework 6.2, the `RestClient.retrieve()` method is a
no-op and developers must invoke a terminal operation on the returned
`ResponseSpec` to have any side effect.

This has been documented in the Javadoc and the wiki release notes, but
this commit highlights this as well in the reference documentation.

Closes gh-34334
2025-01-29 18:26:12 +01:00
Sam Brannen 53afe27109 Clean up warnings in Gradle build
See gh-34332
2025-01-29 17:52:27 +01:00
Sam Brannen b9e43d05bd Restore property binding support for a Map that implements Iterable
The changes in commit c20a2e4763 introduced a regression with regard to
binding to a Map property when the Map also happens to implement
Iterable.

Although that is perhaps not a very common scenario, this commit
reorders the if-blocks in AbstractNestablePropertyAccessor's
getPropertyValue(PropertyTokenHolder) method so that a Map is
considered before an Iterable, thereby allowing an Iterable-Map to be
accessed as a Map.

See gh-907
Closes gh-34332
2025-01-29 17:34:47 +01:00
Brian Clozel d80de043ce Fix filtered HTTP headers in data binding
Prior to this commit, several common HTTP headers were ignored from the
data binding process when collecting property values, in gh-34039 and
gh-34182.

This commit completes the initial enhancement by ensuring that the
default header predicate is also considering cases where constructor
binding is applied and the Java type has a lowercase variant of the HTTP
header name to filter.

Fixes gh-34292
2025-01-29 16:06:19 +01:00
Brian Clozel 7c5b6f1e1c Delete failing Freemarker test
This test was already ignored as of Java 21 because of a Java behavior
change, and now it started failing as of 17.0.14.
This commit removes the test entirely.
2025-01-29 15:55:58 +01:00
rstoyanchev a6b6d19545 Update localAddress Javadoc in WebSocketSession
Closes gh-34304
2025-01-28 15:36:26 +00:00
rstoyanchev 9b58df8857 Update HandlerMethod#createWithResolvedBean
Avoid re-creating the instance unless it is a bean name that
needs to be resolved through the BeanFactory.

Closes gh-34277
2025-01-28 15:36:26 +00:00
rstoyanchev 1cc767e90b Polishing in ExtendedServletRequestDataBinder 2025-01-28 15:36:19 +00:00
Sam Brannen 34d6dd9b62 Polishing 2025-01-27 15:19:00 +01:00
Sébastien Deleuze 5499878de0 Support properties in kotlinx.serialization converters
This commit adds support for Kotlin properties in Spring WebMVC
controllers, supported for reasons explained in gh-31856, with
kotlinx.serialization converters.

Closes gh-34284
2025-01-27 15:06:57 +01:00
Sébastien Deleuze a970fc16aa Support properties in kotlinx.serialization codecs
This commit adds support for Kotlin properties in Spring WebFlux
controllers, supported for reasons explained in gh-31856, with
kotlinx.serialization codecs.

See gh-34284
2025-01-27 15:06:50 +01:00
Sam Brannen 683733a682 Link to AspectJ "runtime-api" Javadoc site
The AspectJ team has confirmed that the following Javadoc site hosts
the latest version (currently 1.9.22.1) of the runtime API for AspectJ,
generated with a modern version of the javadoc tool which generates the
element-list file required by the Spring Framework build.

https://eclipse.dev/aspectj/doc/latest/runtime-api/

See https://github.com/eclipse-aspectj/aspectj/issues/321
See gh-34293
2025-01-24 15:33:24 +01:00
Sam Brannen 11fe2987d0 Use appropriate link for Testing chapter
This commit effectively removes a reference to a nonexistent "#testing"
fragment.
2025-01-23 10:58:48 +01:00
Juergen Hoeller 90423a98b0 Enforce match for resolved part of unresolvable target type
Closes gh-34298
2025-01-21 19:15:20 +01:00
Brian Clozel e9dc6be51c Fix Javadoc for field reflection hints
Closes gh-34297
2025-01-21 17:25:15 +01:00
Sam Brannen 6863b904b5 Polish contribution
See gh-34295
2025-01-21 17:22:49 +01:00
Mengqi Xu 016a4c4260 Improve Javadoc for SpringProperties.getFlag()
getFlag() returns true when the property is equal, ignoring case, to the
string "true", not just "true"; "TrUe" also means true.

Closes gh-34295

Signed-off-by: Mengqi Xu <2663479778@qq.com>
2025-01-21 17:19:17 +01:00
rstoyanchev 233f755416 Update Javadoc that links to RestTemplate
Ensure RestClient is mentioned where appropriate.

Closes gh-34270
2025-01-21 12:21:22 +00:00
rstoyanchev b026680e1c Enhance DisconnectedClientHelper exception type checks
We now look for the target exception types in cause chain as well,
but return false if we encounter a RestClient or WebClient
exception in the chain.

Closes gh-34264
2025-01-21 12:21:22 +00:00
rstoyanchev e47cc19964 Restore exception phrase in DisconnectedClientHelper
Effectively revert 203fa7, and add implementation comment and tests.

See gh-34264
2025-01-21 12:21:22 +00:00
Sam Brannen 15b1310470 Link to AspectJ runtime 1.9.22.1 Javadoc site
Closes gh-34293
2025-01-21 11:31:14 +01:00
Juergen Hoeller ee60eb7207 Fall back to HTTP GET in case of 405 from HTTP HEAD
Closes gh-34217
2025-01-20 18:17:49 +01:00
Sam Brannen cecebd0ef1 Polish HttpHeadersAssertTests 2025-01-20 16:48:10 +01:00
Johnny Lim aac4dbf420 Polish HttpHeadersAssert
See gh-34286

Signed-off-by: Johnny Lim <izeye@naver.com>
2025-01-20 16:46:16 +01:00
Stéphane Nicoll 82bc4ff71d Handle TextPart with escaped separator
This commit harmonizes how a candidate value is parsed to extract its
key and default, if any. Rather than returning {@code null} if no
default is available, `splitKeyAndValue` now consistently returns a
non-null array.

This prevents an escaped separator character to be mistakenly identified
as a placeholder in certain cases.

Closes gh-34289
2025-01-20 15:04:06 +01:00
Brian Clozel 356d5c2cf2 Polishing 2025-01-20 08:02:57 +01:00
Brian Clozel 46c13adfa8 Document custom HttpServiceArgumentResolver usage
This commit adds a reference documentation section explaining how
applications can use custom `HttpServiceArgumentResolver`
implementations to support complex application types as method
parameters in HTTP interfaces.

Closes gh-34227
2025-01-20 07:57:56 +01:00
Sam Brannen 886ca7f2db Polish contribution and SimpleCommandLineArgs-related code
Closes gh-34282
2025-01-18 17:03:04 +01:00
puppy4c c463b937b8 Update Javadoc for SimpleCommandLinePropertySource
For ComandLineArgs, supplying the same option multiple times with
different values is valid, and the values will be stored in a List.

This commit also updates the Javadoc for SimpleCommandLineArgsParser.

See gh-34282

Signed-off-by: puppy4c <puppy4c@foxmail.com>
2025-01-18 16:26:14 +01:00
Sam Brannen 4783c321d9 Polish formatting for consistency across implementations 2025-01-18 15:04:25 +01:00
Sam Brannen 0dfcb44821 Remove unnecessary @⁠Nullable declaration 2025-01-18 15:03:47 +01:00
Sam Brannen 8c3d5621db Upgrade to AssertJ 3.27.3 2025-01-18 14:02:07 +01:00
Sam Brannen 12a6a84829 Polishing 2025-01-18 14:02:07 +01:00
Sam Brannen 6662ecc8cc Upgrade to Gradle Develocity plugin version 3.19
In light of the fact that the Develocity service on ge.spring.io has
been upgraded to 2024.3.1.
2025-01-17 11:47:47 +01:00
Stéphane Nicoll 4b0446fbef Next development version (v6.2.3-SNAPSHOT) 2025-01-16 10:06:12 +01:00
840 changed files with 17403 additions and 6606 deletions
@@ -15,7 +15,7 @@ runs:
using: composite
steps:
- name: Generate Changelog
uses: spring-io/github-changelog-generator@185319ad7eaa75b0e8e72e4b6db19c8b2cb8c4c1 #v0.0.11
uses: spring-io/github-changelog-generator@86958813a62af8fb223b3fd3b5152035504bcb83 #v0.0.12
with:
config-file: .github/actions/create-github-release/changelog-generator.yml
milestone: ${{ inputs.milestone }}
@@ -31,7 +31,7 @@ runs:
${{ inputs.java-early-access == 'true' && format('{0}-ea', inputs.java-version) || inputs.java-version }}
${{ inputs.java-toolchain == 'true' && '17' || '' }}
- name: Set Up Gradle
uses: gradle/actions/setup-gradle@cc4fc85e6b35bafd578d5ffbc76a5518407e1af0 # v4.2.1
uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
with:
cache-read-only: false
develocity-access-key: ${{ inputs.develocity-access-key }}
@@ -20,7 +20,7 @@ runs:
using: composite
steps:
- name: Set Up JFrog CLI
uses: jfrog/setup-jfrog-cli@dff217c085c17666e8849ebdbf29c8fe5e3995e6 # v4.5.2
uses: jfrog/setup-jfrog-cli@f748a0599171a192a2668afee8d0497f7c1069df # v4.5.6
env:
JF_ENV_SPRING: ${{ inputs.jfrog-cli-config-token }}
- name: Download Release Artifacts
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
publish: true
- name: Stage Release
uses: spring-io/artifactory-deploy-action@26bbe925a75f4f863e1e529e85be2d0093cac116 # v0.0.1
uses: spring-io/artifactory-deploy-action@dc1913008c0599f0c4b1fdafb6ff3c502b3565ea # v0.0.2
with:
artifact-properties: |
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
+1 -1
View File
@@ -46,7 +46,7 @@ jobs:
distribution: 'liberica'
java-version: 17
- name: Set Up Gradle
uses: gradle/actions/setup-gradle@cc4fc85e6b35bafd578d5ffbc76a5518407e1af0 # v4.2.1
uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
with:
cache-read-only: false
- name: Configure Gradle Properties
+2 -1
View File
@@ -42,7 +42,8 @@ spring-*/src/main/java/META-INF/MANIFEST.MF
*.iml
*.ipr
*.iws
.idea
.idea/*
!.idea/icon.svg
out
test-output
atlassian-ide-plugin.xml
+52
View File
@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 510 510" style="enable-background:new 0 0 510 510;" xml:space="preserve">
<style type="text/css">
.st0{fill:#6DB33F;}
</style>
<title>icon-framework</title>
<g id="Layer_2_1_">
<g id="Layer_1-2">
<path class="st0" d="M479.2,162.5c-5.6-34.3-20.5-64.4-43.9-87.8S382,36.5,347.6,30.9C320.1,11.2,288.5,0,255,0
s-65.1,11.2-92.6,30.9C128,36.5,98,51.3,74.7,74.7C50.9,98.4,36.5,128.7,31,162.2C11.2,189.8,0,221.4,0,255s11.2,65.2,31,92.8
c5.5,33.5,19.9,63.8,43.7,87.5c23.3,23.3,53.3,38.2,87.7,43.8C190,498.8,221.5,510,255,510s65-11.2,92.6-30.9
c34.4-5.6,64.4-20.5,87.7-43.8c23.8-23.8,38.1-54.1,43.7-87.5c19.8-27.6,31-59.2,31-92.8S498.8,190,479.2,162.5z M473,224.2
c-0.8,6-1.9,11.9-3.2,17.9c-7.2-22.4-17.1-43.8-29.4-63.8c-5.5-22.8-13.6-44.9-24.4-65.8c22.9,14.8,42.2,32.6,56.7,52.6
C475.7,184.6,475.8,204.6,473,224.2z M444.7,199.6c9.3,17.7,16.6,36.3,21.9,55.6c-5.3,19.3-12.7,37.9-22,55.6
C450.9,274,451,236.4,444.7,199.6L444.7,199.6z M175.4,341.1c6.5-4.5,13.7-7.8,21.3-9.8c32.2-7.5,46.7-9.9,66.3-18.4
c36.8-15.9,74.8-53.3,84.6-94c-16.1,38.8-59.5,70.2-97.9,81.6c-26.2,7.8-79.8,13.6-80.1,13.6l-1.4-1.2
c-30.6-17.4-27-85.8,31.8-104.1c25.7-8,55.9-0.7,83.5-5.4c29.4-5,64.4-24.5,79.9-52c12.3,46.4,26.1,118.5-9.7,160.2
C309.4,363,211.8,318,175.4,341.1z M310.8,444.6c-17.7,9.4-36.5,16.8-55.8,22.1c-19.4-5.3-38.1-12.7-55.8-22.1
C236.1,450.9,273.9,450.9,310.8,444.6z M117.6,392.4c13.1,13.1,27.3,25.1,42.3,35.8c-19-5.9-37.2-13.8-54.5-23.7
c-9.9-17.3-17.8-35.7-23.7-54.7C92.4,365.1,104.5,379.3,117.6,392.4z M81.7,160.1c5.9-19,13.9-37.4,23.7-54.7
c17.3-9.8,35.5-17.8,54.5-23.7c-15.1,10.8-29.2,22.7-42.3,35.8C104.5,130.7,92.4,144.9,81.7,160.1z M149.1,346.4
c-8.5,0.2-15.5-6.5-15.7-15c-0.2-8.5,6.5-15.5,15-15.7c8.5-0.2,15.5,6.5,15.7,15c0,0,0,0.1,0,0.1
C164.2,339.3,157.5,346.2,149.1,346.4L149.1,346.4z M199.2,65.4c17.7-9.4,36.5-16.8,55.8-22.1c19.4,5.3,38.1,12.7,55.8,22.1
C273.9,59.1,236.1,59.1,199.2,65.4L199.2,65.4z M392.4,392.4c13.1-13.1,25.1-27.3,35.9-42.4c-5.9,19-13.9,37.3-23.7,54.5
c-17.3,9.8-35.5,17.7-54.5,23.7C365.2,417.5,379.3,405.5,392.4,392.4L392.4,392.4z M392.4,117.6c-13.1-13.1-27.2-25.1-42.3-35.8
c19,5.9,37.2,13.9,54.5,23.7c9.8,17.3,17.8,35.6,23.7,54.7C417.6,144.9,405.5,130.7,392.4,117.6L392.4,117.6z M430.5,79.5
c19.4,19.3,32.6,43.6,39.3,71.1c-16.2-19.2-36.6-36.2-60.4-50c-13.8-23.8-30.8-44.2-50-60.4C387.1,46.9,411.2,60.1,430.5,79.5
L430.5,79.5z M397.6,94c-20.8-10.7-42.8-18.8-65.6-24.2c-20-12.4-41.5-22.4-63.9-29.6c15.5-3.4,31.3-5.1,47.2-5.2
c10,0,19.9,0.8,29.7,2.3C365.1,51.8,382.8,71.1,397.6,94z M255,6.8c27.6,0,53.8,8,77.4,22.2c-5.6-0.5-11.3-0.7-17.1-0.7
c-20.4,0.1-40.6,2.8-60.3,8.1c-19.7-5.3-39.9-8-60.3-8.1c-5.8,0-11.5,0.2-17.1,0.7C201.2,14.7,227.4,6.8,255,6.8z M165,37.3
c9.8-1.5,19.8-2.3,29.7-2.3c15.9,0.1,31.7,1.8,47.2,5.2C219.5,47.4,198,57.3,178,69.8c-22.7,5.4-44.8,13.6-65.6,24.2
C127.2,71.1,144.9,51.8,165,37.3z M79.5,79.5c19.3-19.3,43.5-32.6,71.1-39.3c-19.2,16.2-36.2,36.6-50,60.4
c-23.7,13.8-44,30.6-60.2,49.8C47,123.4,59.8,99.1,79.5,79.5z M37.4,164.9c14.6-20,33.8-37.7,56.6-52.4
c-10.7,20.9-18.8,43-24.3,65.8c-12.3,20-22.2,41.4-29.4,63.8C34.6,215.4,33.5,189.3,37.4,164.9z M65.4,310.4
c-9.3-17.6-16.6-36.2-21.9-55.4c5.3-19.2,12.6-37.8,21.9-55.4C59.1,236.3,59.1,273.7,65.4,310.4L65.4,310.4z M29.2,332.8
c-14.4-23.7-22.4-50-22.4-77.8s8.1-54.1,22.4-77.8c-2.1,25,0.4,51.3,7.4,77.8C29.5,281.5,27,307.8,29.2,332.8z M40.3,268
c7.2,22.3,17.1,43.7,29.4,63.7c5.5,22.8,13.6,44.9,24.3,65.8c-22.8-14.7-42-32.5-56.6-52.4C33.5,320.7,34.6,294.6,40.3,268z
M79.5,430.5c-19.6-19.6-32.5-43.9-39.1-70.9c16.1,19.1,36.5,36,60.2,49.8c13.8,23.7,30.8,44.2,50,60.4
C122.9,463.1,98.8,449.9,79.5,430.5z M112.5,416c20.8,10.7,42.8,18.8,65.5,24.2c20,12.4,41.5,22.4,63.9,29.6
c-15.5,3.4-31.3,5.1-47.2,5.1c-10,0-19.9-0.8-29.7-2.3C144.9,458.1,127.2,438.9,112.5,416L112.5,416z M255,503.2
c-27.6,0-53.8-8-77.4-22.2c5.6,0.5,11.3,0.7,17.1,0.7c20.4-0.1,40.6-2.8,60.3-8.1c19.7,5.3,39.9,8,60.3,8.1
c5.8,0,11.5-0.2,17.1-0.7C308.8,495.3,282.6,503.2,255,503.2z M345,472.7c-9.8,1.5-19.8,2.3-29.7,2.3c-15.9-0.1-31.7-1.8-47.2-5.1
c22.4-7.2,43.9-17.2,63.9-29.6c22.7-5.4,44.8-13.6,65.6-24.2C382.8,438.9,365.1,458.1,345,472.7z M430.5,430.5
c-19.3,19.3-43.5,32.6-71.1,39.3c19.2-16.2,36.2-36.6,50-60.4c23.7-13.8,44-30.6,60.2-49.8C463.1,386.6,450.1,410.9,430.5,430.5
L430.5,430.5z M472.6,345.1c-14.6,20-33.8,37.7-56.6,52.4c10.7-20.8,18.8-42.8,24.3-65.6c12.3-20,22.2-41.3,29.5-63.7
C475.4,294.7,476.5,320.8,472.6,345.1L472.6,345.1z M473.5,255.3c2.6-9.9,4.7-20,6.1-30.1c2.2-15.7,2.7-31.7,1.4-47.5
c14.2,23.6,22.2,49.8,22.2,77.4s-8.1,54.1-22.4,77.8C482.9,307.9,480.5,281.6,473.5,255.3L473.5,255.3z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

+3 -3
View File
@@ -9,7 +9,7 @@ plugins {
id 'io.github.goooler.shadow' version '8.1.8' apply false
id 'me.champeau.jmh' version '0.7.2' apply false
id 'me.champeau.mrjar' version '0.1.1'
id "net.ltgt.errorprone" version "3.1.0" apply false
id "net.ltgt.errorprone" version "4.1.0" apply false
}
ext {
@@ -91,7 +91,7 @@ configure([rootProject] + javaProjects) { project ->
"https://docs.oracle.com/en/java/javase/17/docs/api/",
"https://jakarta.ee/specifications/platform/9/apidocs/",
"https://docs.jboss.org/hibernate/orm/5.6/javadocs/",
"https://eclipse.dev/aspectj/doc/released/aspectj5rt-api",
"https://eclipse.dev/aspectj/doc/latest/runtime-api/",
"https://www.quartz-scheduler.org/api/2.3.0/",
"https://fasterxml.github.io/jackson-core/javadoc/2.14/",
"https://fasterxml.github.io/jackson-databind/javadoc/2.14/",
@@ -102,7 +102,7 @@ configure([rootProject] + javaProjects) { project ->
// TODO Uncomment link to JUnit 5 docs once we execute Gradle with Java 18+.
// See https://github.com/spring-projects/spring-framework/issues/27497
//
// "https://junit.org/junit5/docs/5.11.4/api/",
// "https://junit.org/junit5/docs/5.12.2/api/",
"https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/",
//"https://javadoc.io/static/io.rsocket/rsocket-core/1.1.1/",
"https://r2dbc.io/spec/1.0.0.RELEASE/api/",
+1 -1
View File
@@ -1,2 +1,2 @@
org.gradle.caching=true
javaFormatVersion=0.0.42
javaFormatVersion=0.0.43
@@ -50,7 +50,7 @@ public class CheckstyleConventions {
project.getPlugins().apply(CheckstylePlugin.class);
project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g"));
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
checkstyle.setToolVersion("10.21.1");
checkstyle.setToolVersion("10.23.1");
checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle"));
String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion();
DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies();
@@ -64,7 +64,7 @@ public class CheckstyleConventions {
NoHttpExtension noHttp = project.getExtensions().getByType(NoHttpExtension.class);
noHttp.setAllowlistFile(project.file("src/nohttp/allowlist.lines"));
noHttp.getSource().exclude("**/test-output/**", "**/.settings/**",
"**/.classpath", "**/.project", "**/.gradle/**", "**/node_modules/**");
"**/.classpath", "**/.project", "**/.gradle/**", "**/node_modules/**", "buildSrc/build/**");
List<String> buildFolders = List.of("bin", "build", "out");
project.allprojects(subproject -> {
Path rootPath = project.getRootDir().toPath();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,8 @@ import java.util.Map;
import org.gradle.api.Project;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.tasks.testing.Test;
import org.gradle.api.tasks.testing.TestFrameworkOptions;
import org.gradle.api.tasks.testing.junitplatform.JUnitPlatformOptions;
import org.gradle.testretry.TestRetryPlugin;
import org.gradle.testretry.TestRetryTaskExtension;
@@ -34,6 +36,7 @@ import org.gradle.testretry.TestRetryTaskExtension;
*
* @author Brian Clozel
* @author Andy Wilkinson
* @author Sam Brannen
*/
class TestConventions {
@@ -50,7 +53,12 @@ class TestConventions {
}
private void configureTests(Project project, Test test) {
test.useJUnitPlatform();
TestFrameworkOptions existingOptions = test.getOptions();
test.useJUnitPlatform(options -> {
if (existingOptions instanceof JUnitPlatformOptions junitPlatformOptions) {
options.copyFrom(junitPlatformOptions);
}
});
test.include("**/*Tests.class", "**/*Test.class");
test.setSystemProperties(Map.of(
"java.awt.headless", "true",
@@ -27,6 +27,7 @@ import org.gradle.api.attributes.Usage;
import org.gradle.api.attributes.java.TargetJvmVersion;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.jvm.JvmTestSuite;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.api.tasks.testing.Test;
import org.gradle.testing.base.TestingExtension;
@@ -52,7 +53,7 @@ public class RuntimeHintsAgentPlugin implements Plugin<Project> {
TestingExtension testing = project.getExtensions().getByType(TestingExtension.class);
JvmTestSuite jvmTestSuite = (JvmTestSuite) testing.getSuites().getByName("test");
RuntimeHintsAgentExtension agentExtension = createRuntimeHintsAgentExtension(project);
Test agentTest = project.getTasks().create(RUNTIMEHINTS_TEST_TASK, Test.class, test -> {
TaskProvider<Test> agentTest = project.getTasks().register(RUNTIMEHINTS_TEST_TASK, Test.class, test -> {
test.useJUnitPlatform(options -> {
options.includeTags("RuntimeHintsTests");
});
@@ -63,7 +64,7 @@ public class RuntimeHintsAgentPlugin implements Plugin<Project> {
test.setClasspath(jvmTestSuite.getSources().getRuntimeClasspath());
test.getJvmArgumentProviders().add(createRuntimeHintsAgentArgumentProvider(project, agentExtension));
});
project.getTasks().getByName("check", task -> task.dependsOn(agentTest));
project.getTasks().named("check", task -> task.dependsOn(agentTest));
project.getDependencies().add(CONFIGURATION_NAME, project.project(":spring-core-test"));
});
}
+5 -3
View File
@@ -1,6 +1,6 @@
plugins {
id 'java-platform'
id 'io.freefair.aggregate-javadoc' version '8.3'
id 'io.freefair.aggregate-javadoc' version '8.13.1'
}
description = "Spring Framework API Docs"
@@ -21,6 +21,7 @@ dependencies {
javadoc {
title = "${rootProject.description} ${version} API"
failOnError = true
options {
encoding = "UTF-8"
memberLevel = JavadocMemberLevel.PROTECTED
@@ -31,8 +32,9 @@ javadoc {
destinationDir = project.java.docsDir.dir("javadoc-api").get().asFile
splitIndex = true
links(rootProject.ext.javadocLinks)
addBooleanOption('Xdoclint:syntax,reference', true) // only check syntax and reference with doclint
addBooleanOption('Werror', true) // fail build on Javadoc warnings
// Check for 'syntax' and 'reference' during linting.
addBooleanOption('Xdoclint:syntax,reference', true)
addBooleanOption('Werror', true) // fail build on Javadoc warnings
}
maxMemory = "1024m"
doFirst {
+28 -27
View File
@@ -42,33 +42,34 @@ repositories {
}
dependencies {
api(project(":spring-aspects"))
api(project(":spring-context"))
api(project(":spring-context-support"))
api(project(":spring-jdbc"))
api(project(":spring-jms"))
api(project(":spring-test"))
api(project(":spring-web"))
api(project(":spring-webflux"))
api(project(":spring-webmvc"))
api(project(":spring-websocket"))
api("com.fasterxml.jackson.core:jackson-databind")
api("com.fasterxml.jackson.module:jackson-module-parameter-names")
api("com.mchange:c3p0:0.9.5.5")
api("com.oracle.database.jdbc:ojdbc11")
api("io.projectreactor.netty:reactor-netty-http")
api("jakarta.jms:jakarta.jms-api")
api("jakarta.servlet:jakarta.servlet-api")
api("jakarta.resource:jakarta.resource-api")
api("jakarta.validation:jakarta.validation-api")
api("javax.cache:cache-api")
api("org.apache.activemq:activemq-ra:6.1.2")
api("org.apache.commons:commons-dbcp2:2.11.0")
api("org.aspectj:aspectjweaver")
api("org.eclipse.jetty.websocket:jetty-websocket-jetty-api")
api("org.jetbrains.kotlin:kotlin-stdlib")
implementation(project(":spring-aspects"))
implementation(project(":spring-context"))
implementation(project(":spring-context-support"))
implementation(project(":spring-core-test"))
implementation(project(":spring-jdbc"))
implementation(project(":spring-jms"))
implementation(project(":spring-test"))
implementation(project(":spring-web"))
implementation(project(":spring-webflux"))
implementation(project(":spring-webmvc"))
implementation(project(":spring-websocket"))
implementation("com.fasterxml.jackson.core:jackson-databind")
implementation("com.fasterxml.jackson.module:jackson-module-parameter-names")
implementation("com.mchange:c3p0:0.9.5.5")
implementation("com.oracle.database.jdbc:ojdbc11")
implementation("io.projectreactor.netty:reactor-netty-http")
implementation("jakarta.jms:jakarta.jms-api")
implementation("jakarta.servlet:jakarta.servlet-api")
implementation("jakarta.resource:jakarta.resource-api")
implementation("jakarta.validation:jakarta.validation-api")
implementation("jakarta.websocket:jakarta.websocket-client-api")
implementation("javax.cache:cache-api")
implementation("org.apache.activemq:activemq-ra:6.1.2")
implementation("org.apache.commons:commons-dbcp2:2.11.0")
implementation("org.aspectj:aspectjweaver")
implementation("org.assertj:assertj-core")
implementation("org.eclipse.jetty.websocket:jetty-websocket-jetty-api")
implementation("org.jetbrains.kotlin:kotlin-stdlib")
implementation("org.junit.jupiter:junit-jupiter-api")
}
@@ -92,11 +92,25 @@ the repeated JNDI lookup overhead. See
{spring-framework-api}++/jndi/JndiLocatorDelegate.html#IGNORE_JNDI_PROPERTY_NAME++[`JndiLocatorDelegate`]
for details.
| `spring.locking.strict`
| Instructs Spring to enforce strict locking during bean creation, rather than the mix of
strict and lenient locking that 6.2 applies by default. See
{spring-framework-api}++/beans/factory/support/DefaultListableBeanFactory.html#STRICT_LOCKING_PROPERTY_NAME++[`DefaultListableBeanFactory`]
for details.
| `spring.objenesis.ignore`
| Instructs Spring to ignore Objenesis, not even attempting to use it. See
{spring-framework-api}++/objenesis/SpringObjenesis.html#IGNORE_OBJENESIS_PROPERTY_NAME++[`SpringObjenesis`]
for details.
| `spring.placeholder.escapeCharacter.default`
| The default escape character for property placeholder support. If not set, `'\'` will
be used. Can be set to a custom escape character or an empty string to disable support
for an escape character. The default escape character be explicitly overridden in
`PropertySourcesPlaceholderConfigurer` and subclasses of `AbstractPropertyResolver`. See
{spring-framework-api}++/core/env/AbstractPropertyResolver.html#DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME++[`AbstractPropertyResolver`]
for details.
| `spring.test.aot.processing.failOnError`
| A boolean flag that controls whether errors encountered during AOT processing in the
_Spring TestContext Framework_ should result in an exception that fails the overall process.
@@ -469,20 +469,20 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
RootBeanDefinition beanDefinition = new RootBeanDefinition(ClientFactoryBean.class);
beanDefinition.setTargetType(ResolvableType.forClassWithGenerics(ClientFactoryBean.class, MyClient.class));
// ...
registry.registerBeanDefinition("myClient", beanDefinition);
RootBeanDefinition beanDefinition = new RootBeanDefinition(ClientFactoryBean.class);
beanDefinition.setTargetType(ResolvableType.forClassWithGenerics(ClientFactoryBean.class, MyClient.class));
// ...
registry.registerBeanDefinition("myClient", beanDefinition);
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val beanDefinition = RootBeanDefinition(ClientFactoryBean::class.java)
beanDefinition.setTargetType(ResolvableType.forClassWithGenerics(ClientFactoryBean::class.java, MyClient::class.java));
// ...
registry.registerBeanDefinition("myClient", beanDefinition)
val beanDefinition = RootBeanDefinition(ClientFactoryBean::class.java)
beanDefinition.setTargetType(ResolvableType.forClassWithGenerics(ClientFactoryBean::class.java, MyClient::class.java));
// ...
registry.registerBeanDefinition("myClient", beanDefinition)
----
======
@@ -444,9 +444,9 @@ through Java 8's `java.util.Optional`, as the following example shows:
}
----
You can also use a `@Nullable` annotation (of any kind in any package -- for example,
`javax.annotation.Nullable` from JSR-305) or just leverage Kotlin built-in null-safety
support:
You can also use a parameter-level `@Nullable` annotation (of any kind in any package --
for example, `javax.annotation.Nullable` from JSR-305) or just leverage Kotlin built-in
null-safety support:
[tabs]
======
@@ -477,6 +477,13 @@ Kotlin::
----
======
[NOTE]
====
A type-level `@Nullable` annotation such as from JSpecify is not supported in Spring
Framework 6.2 yet. You need to upgrade to Spring Framework 7.0 where the framework
detects type-level annotations and consistently declares JSpecify in its own codebase.
====
You can also use `@Autowired` for interfaces that are well-known resolvable
dependencies: `BeanFactory`, `ApplicationContext`, `Environment`, `ResourceLoader`,
`ApplicationEventPublisher`, and `MessageSource`. These interfaces and their extended
@@ -9,15 +9,15 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@Component
public class MovieRecommender {
@Component
public class MovieRecommender {
private final String catalog;
private final String catalog;
public MovieRecommender(@Value("${catalog.name}") String catalog) {
this.catalog = catalog;
}
}
public MovieRecommender(@Value("${catalog.name}") String catalog) {
this.catalog = catalog;
}
}
----
Kotlin::
@@ -37,9 +37,9 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }
----
Kotlin::
@@ -56,7 +56,7 @@ And the following `application.properties` file:
[source,java,indent=0,subs="verbatim,quotes"]
----
catalog.name=MovieCatalog
catalog.name=MovieCatalog
----
In that case, the `catalog` parameter and field will be equal to the `MovieCatalog` value.
@@ -101,8 +101,11 @@ NOTE: When configuring a `PropertySourcesPlaceholderConfigurer` using JavaConfig
Using the above configuration ensures Spring initialization failure if any `${}`
placeholder could not be resolved. It is also possible to use methods like
`setPlaceholderPrefix`, `setPlaceholderSuffix`, `setValueSeparator`, or
`setEscapeCharacter` to customize placeholders.
`setPlaceholderPrefix()`, `setPlaceholderSuffix()`, `setValueSeparator()`, or
`setEscapeCharacter()` to customize the placeholder syntax. In addition, the default
escape character can be changed or disabled globally by setting the
`spring.placeholder.escapeCharacter.default` property via a JVM system property (or via
the xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism).
NOTE: Spring Boot configures by default a `PropertySourcesPlaceholderConfigurer` bean that
will get properties from `application.properties` and `application.yml` files.
@@ -119,15 +122,15 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@Component
public class MovieRecommender {
@Component
public class MovieRecommender {
private final String catalog;
private final String catalog;
public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
this.catalog = catalog;
}
}
public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
this.catalog = catalog;
}
}
----
Kotlin::
@@ -150,16 +153,16 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@Configuration
public class AppConfig {
@Configuration
public class AppConfig {
@Bean
public ConversionService conversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
conversionService.addConverter(new MyCustomConverter());
return conversionService;
}
}
@Bean
public ConversionService conversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
conversionService.addConverter(new MyCustomConverter());
return conversionService;
}
}
----
Kotlin::
@@ -188,15 +191,15 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@Component
public class MovieRecommender {
@Component
public class MovieRecommender {
private final String catalog;
private final String catalog;
public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
this.catalog = catalog;
}
}
public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
this.catalog = catalog;
}
}
----
Kotlin::
@@ -217,16 +220,16 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@Component
public class MovieRecommender {
@Component
public class MovieRecommender {
private final Map<String, Integer> countOfMoviesPerCatalog;
private final Map<String, Integer> countOfMoviesPerCatalog;
public MovieRecommender(
@Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
}
}
public MovieRecommender(
@Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
}
}
----
Kotlin::
@@ -119,7 +119,7 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val context = ClassPathXmlApplicationContext("services.xml", "daos.xml")
val context = ClassPathXmlApplicationContext("services.xml", "daos.xml")
----
======
@@ -310,16 +310,16 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
import org.springframework.beans.factory.getBean
import org.springframework.beans.factory.getBean
// create and configure beans
val context = ClassPathXmlApplicationContext("services.xml", "daos.xml")
val context = ClassPathXmlApplicationContext("services.xml", "daos.xml")
// retrieve configured instance
val service = context.getBean<PetStoreService>("petStore")
// retrieve configured instance
val service = context.getBean<PetStoreService>("petStore")
// use configured instance
var userList = service.getUsernameList()
// use configured instance
var userList = service.getUsernameList()
----
======
@@ -513,7 +513,7 @@ the classes above:
<property name="notificationAddress" value="blockedlist@example.org"/>
</bean>
<!-- optional: a custom ApplicationEventMulticaster definition -->
<!-- optional: a custom ApplicationEventMulticaster definition -->
<bean id="applicationEventMulticaster" class="org.springframework.context.event.SimpleApplicationEventMulticaster">
<property name="taskExecutor" ref="..."/>
<property name="errorHandler" ref="..."/>
@@ -57,9 +57,9 @@ The following table describes these properties:
In addition to bean definitions that contain information on how to create a specific
bean, the `ApplicationContext` implementations also permit the registration of existing
objects that are created outside the container (by users). This is done by accessing the
ApplicationContext's `BeanFactory` through the `getBeanFactory()` method, which returns
the `DefaultListableBeanFactory` implementation. `DefaultListableBeanFactory` supports
this registration through the `registerSingleton(..)` and `registerBeanDefinition(..)`
ApplicationContext's `BeanFactory` through the `getAutowireCapableBeanFactory()` method,
which returns the `DefaultListableBeanFactory` implementation. `DefaultListableBeanFactory`
supports this registration through the `registerSingleton(..)` and `registerBeanDefinition(..)`
methods. However, typical applications work solely with beans defined through regular
bean definition metadata.
@@ -120,8 +120,6 @@ dynamically generate a subclass that overrides the method.
subclasses cannot be `final`, and the method to be overridden cannot be `final`, either.
* Unit-testing a class that has an `abstract` method requires you to subclass the class
yourself and to supply a stub implementation of the `abstract` method.
* Concrete methods are also necessary for component scanning, which requires concrete
classes to pick up.
* A further key limitation is that lookup methods do not work with factory methods and
in particular not with `@Bean` methods in configuration classes, since, in that case,
the container is not in charge of creating the instance and therefore cannot create
@@ -293,11 +291,6 @@ Kotlin::
----
======
Note that you should typically declare such annotated lookup methods with a concrete
stub implementation, in order for them to be compatible with Spring's component
scanning rules where abstract classes get ignored by default. This limitation does not
apply to explicitly registered or explicitly imported bean classes.
[TIP]
====
Another way of accessing differently scoped target beans is an `ObjectFactory`/
@@ -226,7 +226,7 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
import org.springframework.beans.factory.getBean
import org.springframework.beans.factory.getBean
fun main() {
val ctx = ClassPathXmlApplicationContext("scripting/beans.xml")
@@ -314,7 +314,7 @@ Thus, marking it for lazy initialization will be ignored, and the
[[beans-factory-placeholderconfigurer]]
=== Example: The Class Name Substitution `PropertySourcesPlaceholderConfigurer`
=== Example: Property Placeholder Substitution with `PropertySourcesPlaceholderConfigurer`
You can use the `PropertySourcesPlaceholderConfigurer` to externalize property values
from a bean definition in a separate file by using the standard Java `Properties` format.
@@ -341,8 +341,8 @@ with placeholder values is defined:
The example shows properties configured from an external `Properties` file. At runtime,
a `PropertySourcesPlaceholderConfigurer` is applied to the metadata that replaces some
properties of the DataSource. The values to replace are specified as placeholders of the
form pass:q[`${property-name}`], which follows the Ant and log4j and JSP EL style.
properties of the `DataSource`. The values to replace are specified as placeholders of the
form pass:q[`${property-name}`], which follows the Ant, log4j, and JSP EL style.
The actual values come from another file in the standard Java `Properties` format:
@@ -355,11 +355,15 @@ jdbc.password=root
----
Therefore, the `${jdbc.username}` string is replaced at runtime with the value, 'sa', and
the same applies for other placeholder values that match keys in the properties file.
The `PropertySourcesPlaceholderConfigurer` checks for placeholders in most properties and
attributes of a bean definition. Furthermore, you can customize the placeholder prefix and suffix.
the same applies for other placeholder values that match keys in the properties file. The
`PropertySourcesPlaceholderConfigurer` checks for placeholders in most properties and
attributes of a bean definition. Furthermore, you can customize the placeholder prefix,
suffix, default value separator, and escape character. In addition, the default escape
character can be changed or disabled globally by setting the
`spring.placeholder.escapeCharacter.default` property via a JVM system property (or via
the xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism).
With the `context` namespace introduced in Spring 2.5, you can configure property placeholders
With the `context` namespace, you can configure property placeholders
with a dedicated configuration element. You can provide one or more locations as a
comma-separated list in the `location` attribute, as the following example shows:
@@ -31,7 +31,7 @@ Java::
----
public class MyBean {
private final Log log = LogFactory.getLog(getClass());
// ...
// ...
}
----
@@ -40,8 +40,8 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
class MyBean {
private val log = LogFactory.getLog(javaClass)
// ...
private val log = LogFactory.getLog(javaClass)
// ...
}
----
======
@@ -399,7 +399,7 @@ A `ConstraintViolation` on the `degrees` method parameter is adapted to a
`MessageSourceResolvable` with the following:
- Error codes `"Max.myService#addStudent.degrees"`, `"Max.degrees"`, `"Max.int"`, `"Max"`
- Message arguments "degrees2 and 2 (the field name and the constraint attribute)
- Message arguments "degrees" and 2 (the field name and the constraint attribute)
- Default message "must be less than or equal to 2"
To customize the above default message, you can add a property such as:
@@ -78,27 +78,27 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@Configuration
public class DataSourceConfig {
@Configuration
public class DataSourceConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setDatabaseConfigurer(EmbeddedDatabaseConfigurers
.customizeConfigurer(H2, this::customize))
.addScript("schema.sql")
.build();
}
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setDatabaseConfigurer(EmbeddedDatabaseConfigurers
.customizeConfigurer(H2, this::customize))
.addScript("schema.sql")
.build();
}
private EmbeddedDatabaseConfigurer customize(EmbeddedDatabaseConfigurer defaultConfigurer) {
return new EmbeddedDatabaseConfigurerDelegate(defaultConfigurer) {
@Override
public void configureConnectionProperties(ConnectionProperties properties, String databaseName) {
super.configureConnectionProperties(properties, databaseName);
properties.setDriverClass(CustomDriver.class);
}
};
}
private EmbeddedDatabaseConfigurer customize(EmbeddedDatabaseConfigurer defaultConfigurer) {
return new EmbeddedDatabaseConfigurerDelegate(defaultConfigurer) {
@Override
public void configureConnectionProperties(ConnectionProperties properties, String databaseName) {
super.configureConnectionProperties(properties, databaseName);
properties.setDriverClass(CustomDriver.class);
}
};
}
}
----
@@ -136,7 +136,7 @@ Java::
[source,java,indent=0,subs="verbatim,quotes"]
----
Mono<Void> completion = client.sql("CREATE TABLE person (id VARCHAR(255) PRIMARY KEY, name VARCHAR(255), age INTEGER);")
.then();
.then();
----
Kotlin::
@@ -144,7 +144,7 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
client.sql("CREATE TABLE person (id VARCHAR(255) PRIMARY KEY, name VARCHAR(255), age INTEGER);")
.await()
.await()
----
======
@@ -173,7 +173,7 @@ Java::
[source,java,indent=0,subs="verbatim,quotes"]
----
Mono<Map<String, Object>> first = client.sql("SELECT id, name FROM person")
.fetch().first();
.fetch().first();
----
Kotlin::
@@ -181,7 +181,7 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val first = client.sql("SELECT id, name FROM person")
.fetch().awaitSingle()
.fetch().awaitSingle()
----
======
@@ -194,8 +194,8 @@ Java::
[source,java,indent=0,subs="verbatim,quotes"]
----
Mono<Map<String, Object>> first = client.sql("SELECT id, name FROM person WHERE first_name = :fn")
.bind("fn", "Joe")
.fetch().first();
.bind("fn", "Joe")
.fetch().first();
----
Kotlin::
@@ -203,8 +203,8 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val first = client.sql("SELECT id, name FROM person WHERE first_name = :fn")
.bind("fn", "Joe")
.fetch().awaitSingle()
.bind("fn", "Joe")
.fetch().awaitSingle()
----
======
@@ -240,8 +240,8 @@ Java::
[source,java,indent=0,subs="verbatim,quotes"]
----
Flux<String> names = client.sql("SELECT name FROM person")
.map(row -> row.get("name", String.class))
.all();
.map(row -> row.get("name", String.class))
.all();
----
Kotlin::
@@ -249,8 +249,8 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val names = client.sql("SELECT name FROM person")
.map{ row: Row -> row.get("name", String.class) }
.flow()
.map{ row: Row -> row.get("name", String.class) }
.flow()
----
======
@@ -301,8 +301,8 @@ Java::
[source,java,indent=0,subs="verbatim,quotes"]
----
Mono<Integer> affectedRows = client.sql("UPDATE person SET first_name = :fn")
.bind("fn", "Joe")
.fetch().rowsUpdated();
.bind("fn", "Joe")
.fetch().rowsUpdated();
----
Kotlin::
@@ -310,8 +310,8 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val affectedRows = client.sql("UPDATE person SET first_name = :fn")
.bind("fn", "Joe")
.fetch().awaitRowsUpdated()
.bind("fn", "Joe")
.fetch().awaitRowsUpdated()
----
======
@@ -337,9 +337,9 @@ The following example shows parameter binding for a query:
[source,java]
----
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
.bind("id", "joe")
.bind("name", "Joe")
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
.bind("id", "joe")
.bind("name", "Joe")
.bind("age", 34);
----
@@ -369,9 +369,9 @@ Indices are zero based.
[source,java]
----
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
.bind(0, "joe")
.bind(1, "Joe")
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
.bind(0, "joe")
.bind(1, "Joe")
.bind(2, 34);
----
@@ -379,9 +379,9 @@ In case your application is binding to many parameters, the same can be achieved
[source,java]
----
List<?> values = List.of("joe", "Joe", 34);
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
.bindValues(values);
List<?> values = List.of("joe", "Joe", 34);
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
.bindValues(values);
----
@@ -428,7 +428,7 @@ Java::
tuples.add(new Object[] {"Ann", 50});
client.sql("SELECT id, name, state FROM table WHERE (name, age) IN (:tuples)")
.bind("tuples", tuples);
.bind("tuples", tuples);
----
Kotlin::
@@ -440,7 +440,7 @@ Kotlin::
tuples.add(arrayOf("Ann", 50))
client.sql("SELECT id, name, state FROM table WHERE (name, age) IN (:tuples)")
.bind("tuples", tuples)
.bind("tuples", tuples)
----
======
@@ -455,7 +455,7 @@ Java::
[source,java,indent=0,subs="verbatim,quotes"]
----
client.sql("SELECT id, name, state FROM table WHERE age IN (:ages)")
.bind("ages", Arrays.asList(35, 50));
.bind("ages", Arrays.asList(35, 50));
----
Kotlin::
@@ -463,7 +463,7 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
client.sql("SELECT id, name, state FROM table WHERE age IN (:ages)")
.bind("ages", arrayOf(35, 50))
.bind("ages", arrayOf(35, 50))
----
======
@@ -490,9 +490,9 @@ Java::
[source,java,indent=0,subs="verbatim,quotes"]
----
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter((s, next) -> next.execute(s.returnGeneratedValues("id")))
.bind("name", …)
.bind("state", …);
.filter((s, next) -> next.execute(s.returnGeneratedValues("id")))
.bind("name", …)
.bind("state", …);
----
Kotlin::
@@ -516,10 +516,10 @@ Java::
[source,java,indent=0,subs="verbatim,quotes"]
----
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter(statement -> s.returnGeneratedValues("id"));
.filter(statement -> s.returnGeneratedValues("id"));
client.sql("SELECT id, name, state FROM table")
.filter(statement -> s.fetchSize(25));
.filter(statement -> s.fetchSize(25));
----
Kotlin::
@@ -527,10 +527,10 @@ Kotlin::
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter { statement -> s.returnGeneratedValues("id") }
.filter { statement -> s.returnGeneratedValues("id") }
client.sql("SELECT id, name, state FROM table")
.filter { statement -> s.fetchSize(25) }
.filter { statement -> s.fetchSize(25) }
----
======
+1 -1
View File
@@ -7,7 +7,7 @@ xref:overview.adoc[Overview] :: History, Design Philosophy, Feedback,
Getting Started.
xref:core.adoc[Core] :: IoC Container, Events, Resources, i18n,
Validation, Data Binding, Type Conversion, SpEL, AOP, AOT.
<<testing.adoc#testing, Testing>> :: Mock Objects, TestContext Framework,
xref:testing.adoc[Testing] :: Mock Objects, TestContext Framework,
Spring MVC Test, WebTestClient.
xref:data-access.adoc[Data Access] :: Transactions, DAO Support,
JDBC, R2DBC, O/R Mapping, XML Marshalling.
@@ -55,11 +55,11 @@ a "shared objects file" source, as shown in the following example:
[source,shell,indent=0,subs="verbatim"]
----
[0.064s][info][class,load] org.springframework.core.env.EnvironmentCapable source: shared objects file (top)
[0.064s][info][class,load] org.springframework.beans.factory.BeanFactory source: shared objects file (top)
[0.064s][info][class,load] org.springframework.beans.factory.ListableBeanFactory source: shared objects file (top)
[0.064s][info][class,load] org.springframework.beans.factory.HierarchicalBeanFactory source: shared objects file (top)
[0.065s][info][class,load] org.springframework.context.MessageSource source: shared objects file (top)
[0.064s][info][class,load] org.springframework.core.env.EnvironmentCapable source: shared objects file (top)
[0.064s][info][class,load] org.springframework.beans.factory.BeanFactory source: shared objects file (top)
[0.064s][info][class,load] org.springframework.beans.factory.ListableBeanFactory source: shared objects file (top)
[0.064s][info][class,load] org.springframework.beans.factory.HierarchicalBeanFactory source: shared objects file (top)
[0.065s][info][class,load] org.springframework.context.MessageSource source: shared objects file (top)
----
If CDS can't be enabled or if you have a large number of classes that are not loaded from the cache, make sure that
@@ -30,36 +30,36 @@ Java::
+
[source,java,indent=0,subs="verbatim"]
----
RestClient defaultClient = RestClient.create();
RestClient customClient = RestClient.builder()
.requestFactory(new HttpComponentsClientHttpRequestFactory())
.messageConverters(converters -> converters.add(new MyCustomMessageConverter()))
.baseUrl("https://example.com")
.defaultUriVariables(Map.of("variable", "foo"))
.defaultHeader("My-Header", "Foo")
.defaultCookie("My-Cookie", "Bar")
.requestInterceptor(myCustomInterceptor)
.requestInitializer(myCustomInitializer)
.build();
RestClient defaultClient = RestClient.create();
RestClient customClient = RestClient.builder()
.requestFactory(new HttpComponentsClientHttpRequestFactory())
.messageConverters(converters -> converters.add(new MyCustomMessageConverter()))
.baseUrl("https://example.com")
.defaultUriVariables(Map.of("variable", "foo"))
.defaultHeader("My-Header", "Foo")
.defaultCookie("My-Cookie", "Bar")
.requestInterceptor(myCustomInterceptor)
.requestInitializer(myCustomInitializer)
.build();
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim"]
----
val defaultClient = RestClient.create()
val customClient = RestClient.builder()
.requestFactory(HttpComponentsClientHttpRequestFactory())
.messageConverters { converters -> converters.add(MyCustomMessageConverter()) }
.baseUrl("https://example.com")
.defaultUriVariables(mapOf("variable" to "foo"))
.defaultHeader("My-Header", "Foo")
.defaultCookie("My-Cookie", "Bar")
.requestInterceptor(myCustomInterceptor)
.requestInitializer(myCustomInitializer)
.build()
val defaultClient = RestClient.create()
val customClient = RestClient.builder()
.requestFactory(HttpComponentsClientHttpRequestFactory())
.messageConverters { converters -> converters.add(MyCustomMessageConverter()) }
.baseUrl("https://example.com")
.defaultUriVariables(mapOf("variable" to "foo"))
.defaultHeader("My-Header", "Foo")
.defaultCookie("My-Cookie", "Bar")
.requestInterceptor(myCustomInterceptor)
.requestInitializer(myCustomInitializer)
.build()
----
======
@@ -81,20 +81,20 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
int id = 42;
restClient.get()
.uri("https://example.com/orders/{id}", id)
....
int id = 42;
restClient.get()
.uri("https://example.com/orders/{id}", id)
// ...
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val id = 42
restClient.get()
.uri("https://example.com/orders/{id}", id)
...
val id = 42
restClient.get()
.uri("https://example.com/orders/{id}", id)
// ...
----
======
@@ -115,11 +115,15 @@ Finally, the body can be set to a callback function that writes to an `OutputStr
==== Retrieving the response
Once the request has been set up, the HTTP response is accessed by invoking `retrieve()`.
The response body can be accessed by using `body(Class)` or `body(ParameterizedTypeReference)` for parameterized types like lists.
Once the request has been set up, it can be sent by chaining method calls after `retrieve()`.
For example, the response body can be accessed by using `retrieve().body(Class)` or `retrieve().body(ParameterizedTypeReference)` for parameterized types like lists.
The `body` method converts the response contents into various types for instance, bytes can be converted into a `String`, JSON can be converted into objects using Jackson, and so on (see <<rest-message-conversion>>).
The response can also be converted into a `ResponseEntity`, giving access to the response headers as well as the body.
The response can also be converted into a `ResponseEntity`, giving access to the response headers as well as the body, with `retrieve().toEntity(Class)`
NOTE: Calling `retrieve()` by itself is a no-op and returns a `ResponseSpec`.
Applications must invoke a terminal operation on the `ResponseSpec` to have any side effect.
If consuming the response has no interest for your use case, you can use `retrieve().toBodilessEntity()`.
This sample shows how `RestClient` can be used to perform a simple `GET` request.
@@ -129,12 +133,12 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
String result = restClient.get() <1>
.uri("https://example.com") <2>
.retrieve() <3>
.body(String.class); <4>
System.out.println(result); <5>
String result = restClient.get() <1>
.uri("https://example.com") <2>
.retrieve() <3>
.body(String.class); <4>
System.out.println(result); <5>
----
<1> Set up a GET request
<2> Specify the URL to connect to
@@ -146,12 +150,12 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val result= restClient.get() <1>
.uri("https://example.com") <2>
.retrieve() <3>
.body<String>() <4>
println(result) <5>
val result= restClient.get() <1>
.uri("https://example.com") <2>
.retrieve() <3>
.body<String>() <4>
println(result) <5>
----
<1> Set up a GET request
<2> Specify the URL to connect to
@@ -168,14 +172,14 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
ResponseEntity<String> result = restClient.get() <1>
.uri("https://example.com") <1>
.retrieve()
.toEntity(String.class); <2>
System.out.println("Response status: " + result.getStatusCode()); <3>
System.out.println("Response headers: " + result.getHeaders()); <3>
System.out.println("Contents: " + result.getBody()); <3>
ResponseEntity<String> result = restClient.get() <1>
.uri("https://example.com") <1>
.retrieve()
.toEntity(String.class); <2>
System.out.println("Response status: " + result.getStatusCode()); <3>
System.out.println("Response headers: " + result.getHeaders()); <3>
System.out.println("Contents: " + result.getBody()); <3>
----
<1> Set up a GET request for the specified URL
<2> Convert the response into a `ResponseEntity`
@@ -185,14 +189,14 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val result = restClient.get() <1>
.uri("https://example.com") <1>
.retrieve()
.toEntity<String>() <2>
println("Response status: " + result.statusCode) <3>
println("Response headers: " + result.headers) <3>
println("Contents: " + result.body) <3>
val result = restClient.get() <1>
.uri("https://example.com") <1>
.retrieve()
.toEntity<String>() <2>
println("Response status: " + result.statusCode) <3>
println("Response headers: " + result.headers) <3>
println("Contents: " + result.body) <3>
----
<1> Set up a GET request for the specified URL
<2> Convert the response into a `ResponseEntity`
@@ -208,12 +212,12 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
int id = ...;
Pet pet = restClient.get()
.uri("https://petclinic.example.com/pets/{id}", id) <1>
.accept(APPLICATION_JSON) <2>
.retrieve()
.body(Pet.class); <3>
int id = ...;
Pet pet = restClient.get()
.uri("https://petclinic.example.com/pets/{id}", id) <1>
.accept(APPLICATION_JSON) <2>
.retrieve()
.body(Pet.class); <3>
----
<1> Using URI variables
<2> Set the `Accept` header to `application/json`
@@ -223,12 +227,12 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val id = ...
val pet = restClient.get()
.uri("https://petclinic.example.com/pets/{id}", id) <1>
.accept(APPLICATION_JSON) <2>
.retrieve()
.body<Pet>() <3>
val id = ...
val pet = restClient.get()
.uri("https://petclinic.example.com/pets/{id}", id) <1>
.accept(APPLICATION_JSON) <2>
.retrieve()
.body<Pet>() <3>
----
<1> Using URI variables
<2> Set the `Accept` header to `application/json`
@@ -243,13 +247,13 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
Pet pet = ... <1>
ResponseEntity<Void> response = restClient.post() <2>
.uri("https://petclinic.example.com/pets/new") <2>
.contentType(APPLICATION_JSON) <3>
.body(pet) <4>
.retrieve()
.toBodilessEntity(); <5>
Pet pet = ... <1>
ResponseEntity<Void> response = restClient.post() <2>
.uri("https://petclinic.example.com/pets/new") <2>
.contentType(APPLICATION_JSON) <3>
.body(pet) <4>
.retrieve()
.toBodilessEntity(); <5>
----
<1> Create a `Pet` domain object
<2> Set up a POST request, and the URL to connect to
@@ -261,13 +265,13 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val pet: Pet = ... <1>
val response = restClient.post() <2>
.uri("https://petclinic.example.com/pets/new") <2>
.contentType(APPLICATION_JSON) <3>
.body(pet) <4>
.retrieve()
.toBodilessEntity() <5>
val pet: Pet = ... <1>
val response = restClient.post() <2>
.uri("https://petclinic.example.com/pets/new") <2>
.contentType(APPLICATION_JSON) <3>
.body(pet) <4>
.retrieve()
.toBodilessEntity() <5>
----
<1> Create a `Pet` domain object
<2> Set up a POST request, and the URL to connect to
@@ -287,13 +291,13 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
String result = restClient.get() <1>
.uri("https://example.com/this-url-does-not-exist") <1>
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, (request, response) -> { <2>
throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()); <3>
})
.body(String.class);
String result = restClient.get() <1>
.uri("https://example.com/this-url-does-not-exist") <1>
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, (request, response) -> { <2>
throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()); <3>
})
.body(String.class);
----
<1> Create a GET request for a URL that returns a 404 status code
<2> Set up a status handler for all 4xx status codes
@@ -303,12 +307,12 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val result = restClient.get() <1>
.uri("https://example.com/this-url-does-not-exist") <1>
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError) { _, response -> <2>
throw MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()) } <3>
.body<String>()
val result = restClient.get() <1>
.uri("https://example.com/this-url-does-not-exist") <1>
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError) { _, response -> <2>
throw MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()) } <3>
.body<String>()
----
<1> Create a GET request for a URL that returns a 404 status code
<2> Set up a status handler for all 4xx status codes
@@ -326,18 +330,18 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
Pet result = restClient.get()
.uri("https://petclinic.example.com/pets/{id}", id)
.accept(APPLICATION_JSON)
.exchange((request, response) -> { <1>
if (response.getStatusCode().is4xxClientError()) { <2>
throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()); <2>
}
else {
Pet pet = convertResponse(response); <3>
return pet;
}
});
Pet result = restClient.get()
.uri("https://petclinic.example.com/pets/{id}", id)
.accept(APPLICATION_JSON)
.exchange((request, response) -> { <1>
if (response.getStatusCode().is4xxClientError()) { <2>
throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()); <2>
}
else {
Pet pet = convertResponse(response); <3>
return pet;
}
});
----
<1> `exchange` provides the request and response
<2> Throw an exception when the response has a 4xx status code
@@ -347,17 +351,17 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val result = restClient.get()
.uri("https://petclinic.example.com/pets/{id}", id)
.accept(MediaType.APPLICATION_JSON)
.exchange { request, response -> <1>
if (response.getStatusCode().is4xxClientError()) { <2>
throw MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()) <2>
} else {
val pet: Pet = convertResponse(response) <3>
pet
}
}
val result = restClient.get()
.uri("https://petclinic.example.com/pets/{id}", id)
.accept(MediaType.APPLICATION_JSON)
.exchange { request, response -> <1>
if (response.getStatusCode().is4xxClientError()) { <2>
throw MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()) <2>
} else {
val pet: Pet = convertResponse(response) <3>
pet
}
}
----
<1> `exchange` provides the request and response
<2> Throw an exception when the response has a 4xx status code
@@ -376,15 +380,14 @@ To serialize only a subset of the object properties, you can specify a {baeldung
[source,java,indent=0,subs="verbatim"]
----
MappingJacksonValue value = new MappingJacksonValue(new User("eric", "7!jd#h23"));
value.setSerializationView(User.WithoutPasswordView.class);
ResponseEntity<Void> response = restClient.post() // or RestTemplate.postForEntity
.contentType(APPLICATION_JSON)
.body(value)
.retrieve()
.toBodilessEntity();
MappingJacksonValue value = new MappingJacksonValue(new User("eric", "7!jd#h23"));
value.setSerializationView(User.WithoutPasswordView.class);
ResponseEntity<Void> response = restClient.post() // or RestTemplate.postForEntity
.contentType(APPLICATION_JSON)
.body(value)
.retrieve()
.toBodilessEntity();
----
==== Multipart
@@ -394,24 +397,24 @@ For example:
[source,java,indent=0,subs="verbatim"]
----
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));
// send using RestClient.post or RestTemplate.postForEntity
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));
// send using RestClient.post or RestTemplate.postForEntity
----
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 use it as the body of a `POST` request, using `RestClient.post().body(parts)` (or `RestTemplate.postForObject`).
Once the `MultiValueMap` is ready, you can use it as the body of a `POST` request, using `RestClient.post().body(parts)` (or `RestTemplate.postForObject`).
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` defaults to `application/x-www-form-urlencoded`.
@@ -853,7 +856,7 @@ Start by creating the interface with `@HttpExchange` methods:
[source,java,indent=0,subs="verbatim,quotes"]
----
interface RepositoryService {
public interface RepositoryService {
@GetExchange("/repos/{owner}/{repo}")
Repository getRepository(@PathVariable String owner, @PathVariable String repo);
@@ -904,7 +907,7 @@ For `RestTemplate`:
[source,java,indent=0,subs="verbatim,quotes"]
----
@HttpExchange(url = "/repos/{owner}/{repo}", accept = "application/vnd.github.v3+json")
interface RepositoryService {
public interface RepositoryService {
@GetExchange
Repository getRepository(@PathVariable String owner, @PathVariable String repo);
@@ -988,6 +991,27 @@ parameter annotation) is set to `false`, or the parameter is marked optional as
[[rest-http-interface.custom-resolver]]
=== Custom argument resolver
For more complex cases, HTTP interfaces do not support `RequestEntity` types as method parameters.
This would take over the entire HTTP request and not improve the semantics of the interface.
Instead of adding many method parameters, developers can combine them into a custom type
and configure a dedicated `HttpServiceArgumentResolver` implementation.
In the following HTTP interface, we are using a custom `Search` type as a parameter:
include-code::./CustomHttpServiceArgumentResolver[tag=httpinterface,indent=0]
We can implement our own `HttpServiceArgumentResolver` that supports our custom `Search` type
and writes its data in the outgoing HTTP request.
include-code::./CustomHttpServiceArgumentResolver[tag=argumentresolver,indent=0]
Finally, we can use this argument resolver during the setup and use our HTTP interface.
include-code::./CustomHttpServiceArgumentResolver[tag=usage,indent=0]
[[rest-http-interface-return-values]]
=== Return Values
@@ -1112,11 +1136,11 @@ performed through the client:
[source,java,indent=0,subs="verbatim,quotes"]
----
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(myErrorHandler);
RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(myErrorHandler);
RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
----
For more details and options, see the Javadoc of `setErrorHandler` in `RestTemplate` and
@@ -215,45 +215,43 @@ For suspending functions, a `TransactionalOperator.executeAndAwait` extension is
[source,kotlin,indent=0]
----
import org.springframework.transaction.reactive.executeAndAwait
import org.springframework.transaction.reactive.executeAndAwait
class PersonRepository(private val operator: TransactionalOperator) {
class PersonRepository(private val operator: TransactionalOperator) {
suspend fun initDatabase() = operator.executeAndAwait {
insertPerson1()
insertPerson2()
}
suspend fun initDatabase() = operator.executeAndAwait {
insertPerson1()
insertPerson2()
}
private suspend fun insertPerson1() {
// INSERT SQL statement
}
private suspend fun insertPerson1() {
// INSERT SQL statement
}
private suspend fun insertPerson2() {
// INSERT SQL statement
}
}
private suspend fun insertPerson2() {
// INSERT SQL statement
}
}
----
For Kotlin `Flow`, a `Flow<T>.transactional` extension is provided.
[source,kotlin,indent=0]
----
import org.springframework.transaction.reactive.transactional
import org.springframework.transaction.reactive.transactional
class PersonRepository(private val operator: TransactionalOperator) {
class PersonRepository(private val operator: TransactionalOperator) {
fun updatePeople() = findPeople().map(::updatePerson).transactional(operator)
fun updatePeople() = findPeople().map(::updatePerson).transactional(operator)
private fun findPeople(): Flow<Person> {
// SELECT SQL statement
}
private fun findPeople(): Flow<Person> {
// SELECT SQL statement
}
private suspend fun updatePerson(person: Person): Person {
// UPDATE SQL statement
}
}
private suspend fun updatePerson(person: Person): Person {
// UPDATE SQL statement
}
}
----
@@ -190,7 +190,7 @@ NOTE: If you use Spring Boot, you should probably use
instead of `@Value` annotations.
As an alternative, you can customize the property placeholder prefix by declaring the
following configuration beans:
following `PropertySourcesPlaceholderConfigurer` bean:
[source,kotlin,indent=0]
----
@@ -200,8 +200,10 @@ following configuration beans:
}
----
You can customize existing code (such as Spring Boot actuators or `@LocalServerPort`)
that uses the `${...}` syntax, with configuration beans, as the following example shows:
You can support components (such as Spring Boot actuators or `@LocalServerPort`) that use
the standard `${...}` syntax alongside components that use the custom `%{...}` syntax by
declaring multiple `PropertySourcesPlaceholderConfigurer` beans, as the following example
shows:
[source,kotlin,indent=0]
----
@@ -215,6 +217,9 @@ that uses the `${...}` syntax, with configuration beans, as the following exampl
fun defaultPropertyConfigurer() = PropertySourcesPlaceholderConfigurer()
----
In addition, the default escape character can be changed or disabled globally by setting
the `spring.placeholder.escapeCharacter.default` property via a JVM system property (or
via the xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism).
[[checked-exceptions]]
@@ -296,17 +301,17 @@ for example when writing a `org.springframework.core.convert.converter.Converter
[source,kotlin,indent=0]
----
class ListOfFooConverter : Converter<List<Foo>, CustomJavaList<out Foo>> {
// ...
}
class ListOfFooConverter : Converter<List<Foo>, CustomJavaList<out Foo>> {
// ...
}
----
When converting any kind of objects, star projection with `*` can be used instead of `out Any`.
[source,kotlin,indent=0]
----
class ListOfAnyConverter : Converter<List<*>, CustomJavaList<*>> {
// ...
}
class ListOfAnyConverter : Converter<List<*>, CustomJavaList<*>> {
// ...
}
----
NOTE: Spring Framework does not leverage yet declaration-site variance type information for injecting beans,
@@ -340,13 +345,14 @@ file with a `spring.test.constructor.autowire.mode = all` property.
[source,kotlin,indent=0]
----
@SpringJUnitConfig(TestConfig::class)
@TestConstructor(autowireMode = AutowireMode.ALL)
class OrderServiceIntegrationTests(val orderService: OrderService,
val customerService: CustomerService) {
// tests that use the injected OrderService and CustomerService
}
@SpringJUnitConfig(TestConfig::class)
@TestConstructor(autowireMode = AutowireMode.ALL)
class OrderServiceIntegrationTests(
val orderService: OrderService,
val customerService: CustomerService) {
// tests that use the injected OrderService and CustomerService
}
----
@@ -368,29 +374,29 @@ The following example demonstrates `@BeforeAll` and `@AfterAll` annotations on n
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class IntegrationTests {
val application = Application(8181)
val client = WebClient.create("http://localhost:8181")
val application = Application(8181)
val client = WebClient.create("http://localhost:8181")
@BeforeAll
fun beforeAll() {
application.start()
}
@BeforeAll
fun beforeAll() {
application.start()
}
@Test
fun `Find all users on HTML page`() {
client.get().uri("/users")
.accept(TEXT_HTML)
.retrieve()
.bodyToMono<String>()
.test()
.expectNextMatches { it.contains("Foo") }
.verifyComplete()
}
@Test
fun `Find all users on HTML page`() {
client.get().uri("/users")
.accept(TEXT_HTML)
.retrieve()
.bodyToMono<String>()
.test()
.expectNextMatches { it.contains("Foo") }
.verifyComplete()
}
@AfterAll
fun afterAll() {
application.stop()
}
@AfterAll
fun afterAll() {
application.stop()
}
}
----
@@ -403,26 +409,27 @@ The following example shows how to do so:
[source,kotlin,indent=0]
----
class SpecificationLikeTests {
class SpecificationLikeTests {
@Nested
@DisplayName("a calculator")
inner class Calculator {
@Nested
@DisplayName("a calculator")
inner class Calculator {
val calculator = SampleCalculator()
@Test
fun `should return the result of adding the first number to the second number`() {
val sum = calculator.sum(2, 4)
assertEquals(6, sum)
}
@Test
fun `should return the result of subtracting the second number from the first number`() {
val subtract = calculator.subtract(4, 2)
assertEquals(2, subtract)
}
}
}
val calculator = SampleCalculator()
@Test
fun `should return the result of adding the first number to the second number`() {
val sum = calculator.sum(2, 4)
assertEquals(6, sum)
}
@Test
fun `should return the result of subtracting the second number from the first number`() {
val subtract = calculator.subtract(4, 2)
assertEquals(2, subtract)
}
}
}
----
@@ -1,8 +1,6 @@
[[kotlin-web]]
= Web
[[router-dsl]]
== Router DSL
@@ -16,27 +14,27 @@ These DSL let you write clean and idiomatic Kotlin code to build a `RouterFuncti
[source,kotlin,indent=0]
----
@Configuration
class RouterRouterConfiguration {
@Bean
fun mainRouter(userHandler: UserHandler) = router {
accept(TEXT_HTML).nest {
GET("/") { ok().render("index") }
GET("/sse") { ok().render("sse") }
GET("/users", userHandler::findAllView)
}
"/api".nest {
accept(APPLICATION_JSON).nest {
GET("/users", userHandler::findAll)
@Configuration
class RouterRouterConfiguration {
@Bean
fun mainRouter(userHandler: UserHandler) = router {
accept(TEXT_HTML).nest {
GET("/") { ok().render("index") }
GET("/sse") { ok().render("sse") }
GET("/users", userHandler::findAllView)
}
accept(TEXT_EVENT_STREAM).nest {
GET("/users", userHandler::stream)
"/api".nest {
accept(APPLICATION_JSON).nest {
GET("/users", userHandler::findAll)
}
accept(TEXT_EVENT_STREAM).nest {
GET("/users", userHandler::stream)
}
}
resources("/**", ClassPathResource("static/"))
}
resources("/**", ClassPathResource("static/"))
}
}
----
NOTE: This DSL is programmatic, meaning that it allows custom registration logic of beans
@@ -55,22 +53,22 @@ idiomatic Kotlin API and to allow better discoverability (no usage of static met
[source,kotlin,indent=0]
----
val mockMvc: MockMvc = ...
mockMvc.get("/person/{name}", "Lee") {
secure = true
accept = APPLICATION_JSON
headers {
contentLanguage = Locale.FRANCE
val mockMvc: MockMvc = ...
mockMvc.get("/person/{name}", "Lee") {
secure = true
accept = APPLICATION_JSON
headers {
contentLanguage = Locale.FRANCE
}
principal = Principal { "foo" }
}.andExpect {
status { isOk }
content { contentType(APPLICATION_JSON) }
jsonPath("$.name") { value("Lee") }
content { json("""{"someBoolean": false}""", false) }
}.andDo {
print()
}
principal = Principal { "foo" }
}.andExpect {
status { isOk }
content { contentType(APPLICATION_JSON) }
jsonPath("$.name") { value("Lee") }
content { json("""{"someBoolean": false}""", false) }
}.andDo {
print()
}
----
@@ -89,9 +87,9 @@ is possible to use such feature to render Kotlin-based templates with
`build.gradle.kts`
[source,kotlin,indent=0]
----
dependencies {
runtime("org.jetbrains.kotlin:kotlin-scripting-jsr223:${kotlinVersion}")
}
dependencies {
runtime("org.jetbrains.kotlin:kotlin-scripting-jsr223:${kotlinVersion}")
}
----
Configuration is usually done with `ScriptTemplateConfigurer` and `ScriptTemplateViewResolver` beans.
@@ -99,23 +97,23 @@ Configuration is usually done with `ScriptTemplateConfigurer` and `ScriptTemplat
`KotlinScriptConfiguration.kt`
[source,kotlin,indent=0]
----
@Configuration
class KotlinScriptConfiguration {
@Bean
fun kotlinScriptConfigurer() = ScriptTemplateConfigurer().apply {
engineName = "kotlin"
setScripts("scripts/render.kts")
renderFunction = "render"
isSharedEngine = false
@Configuration
class KotlinScriptConfiguration {
@Bean
fun kotlinScriptConfigurer() = ScriptTemplateConfigurer().apply {
engineName = "kotlin"
setScripts("scripts/render.kts")
renderFunction = "render"
isSharedEngine = false
}
@Bean
fun kotlinScriptViewResolver() = ScriptTemplateViewResolver().apply {
setPrefix("templates/")
setSuffix(".kts")
}
}
@Bean
fun kotlinScriptViewResolver() = ScriptTemplateViewResolver().apply {
setPrefix("templates/")
setSuffix(".kts")
}
}
----
See the https://github.com/sdeleuze/kotlin-script-templating[kotlin-script-templating] example
@@ -127,7 +125,7 @@ project for more details.
== Kotlin multiplatform serialization
{kotlin-github-org}/kotlinx.serialization[Kotlin multiplatform serialization] is
supported in Spring MVC, Spring WebFlux and Spring Messaging (RSocket). The builtin support currently targets CBOR, JSON, and ProtoBuf formats.
supported in Spring MVC, Spring WebFlux and Spring Messaging (RSocket). The built-in support currently targets CBOR, JSON, and ProtoBuf formats.
To enable it, follow {kotlin-github-org}/kotlinx.serialization#setup[those instructions] to add the related dependency and plugin.
With Spring MVC and WebFlux, both Kotlin serialization and Jackson will be configured by default if they are in the classpath since
@@ -135,6 +133,3 @@ Kotlin serialization is designed to serialize only Kotlin classes annotated with
With Spring Messaging (RSocket), make sure that neither Jackson, GSON or JSONB are in the classpath if you want automatic configuration,
if Jackson is needed configure `KotlinSerializationJsonMessageConverter` manually.
@@ -2,8 +2,9 @@
= Spring JUnit 4 Testing Annotations
The following annotations are supported only when used in conjunction with the
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-runner[SpringRunner], xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules[Spring's JUnit 4 rules]
, or xref:testing/testcontext-framework/support-classes.adoc#testcontext-support-classes-junit4[Spring's JUnit 4 support classes]:
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-runner[SpringRunner],
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules[Spring's JUnit 4 rules], or
xref:testing/testcontext-framework/support-classes.adoc#testcontext-support-classes-junit4[Spring's JUnit 4 support classes]:
* xref:testing/annotations/integration-junit4.adoc#integration-testing-annotations-junit4-ifprofilevalue[`@IfProfileValue`]
* xref:testing/annotations/integration-junit4.adoc#integration-testing-annotations-junit4-profilevaluesourceconfiguration[`@ProfileValueSourceConfiguration`]
@@ -1,34 +1,77 @@
[[spring-testing-annotation-beanoverriding-mockitobean]]
= `@MockitoBean` and `@MockitoSpyBean`
`@MockitoBean` and `@MockitoSpyBean` are used on non-static fields in test classes to
override beans in the test's `ApplicationContext` with a Mockito _mock_ or _spy_,
respectively. In the latter case, an early instance of the original bean is captured and
wrapped by the spy.
{spring-framework-api}/test/context/bean/override/mockito/MockitoBean.html[`@MockitoBean`] and
{spring-framework-api}/test/context/bean/override/mockito/MockitoSpyBean.html[`@MockitoSpyBean`]
can be used in test classes to override a bean in the test's `ApplicationContext` with a
Mockito _mock_ or _spy_, respectively. In the latter case, an early instance of the
original bean is captured and wrapped by the spy.
By default, the annotated field's type is used to search for candidate beans to override.
If multiple candidates match, `@Qualifier` can be provided to narrow the candidate to
override. Alternatively, a candidate whose bean name matches the name of the field will
match.
The annotations can be applied in the following ways.
* On a non-static field in a test class or any of its superclasses.
* On a non-static field in an enclosing class for a `@Nested` test class or in any class
in the type hierarchy or enclosing class hierarchy above the `@Nested` test class.
* At the type level on a test class or any superclass or implemented interface in the
type hierarchy above the test class.
* At the type level on an enclosing class for a `@Nested` test class or on any class or
interface in the type hierarchy or enclosing class hierarchy above the `@Nested` test
class.
When `@MockitoBean` or `@MockitoSpyBean` is declared on a field, the bean to mock or spy
is inferred from the type of the annotated field. If multiple candidates exist in the
`ApplicationContext`, a `@Qualifier` annotation can be declared on the field to help
disambiguate. In the absence of a `@Qualifier` annotation, the name of the annotated
field will be used as a _fallback qualifier_. Alternatively, you can explicitly specify a
bean name to mock or spy by setting the `value` or `name` attribute in the annotation.
When `@MockitoBean` or `@MockitoSpyBean` is declared at the type level, the type of bean
(or beans) to mock or spy must be supplied via the `types` attribute in the annotation
for example, `@MockitoBean(types = {OrderService.class, UserService.class})`. If multiple
candidates exist in the `ApplicationContext`, you can explicitly specify a bean name to
mock or spy by setting the `name` attribute. Note, however, that the `types` attribute
must contain a single type if an explicit bean `name` is configured for example,
`@MockitoBean(name = "ps1", types = PrintingService.class)`.
To support reuse of mock configuration, `@MockitoBean` and `@MockitoSpyBean` may be used
as meta-annotations to create custom _composed annotations_ for example, to define
common mock or spy configuration in a single annotation that can be reused across a test
suite. `@MockitoBean` and `@MockitoSpyBean` can also be used as repeatable annotations at
the type level — for example, to mock or spy several beans by name.
[WARNING]
====
Qualifiers, including the name of the field, are used to determine if a separate
Qualifiers, including the name of a field, are used to determine if a separate
`ApplicationContext` needs to be created. If you are using this feature to mock or spy
the same bean in several test classes, make sure to name the field consistently to avoid
the same bean in several test classes, make sure to name the fields consistently to avoid
creating unnecessary contexts.
====
[WARNING]
====
Using `@MockitoBean` or `@MockitoSpyBean` in conjunction with `@ContextHierarchy` can
lead to undesirable results since each `@MockitoBean` or `@MockitoSpyBean` will be
applied to all context hierarchy levels by default. To ensure that a particular
`@MockitoBean` or `@MockitoSpyBean` is applied to a single context hierarchy level, set
the `contextName` attribute to match a configured `@ContextConfiguration` name for
example, `@MockitoBean(contextName = "app-config")` or
`@MockitoSpyBean(contextName = "app-config")`.
See
xref:testing/testcontext-framework/ctx-management/hierarchies.adoc#testcontext-ctx-management-ctx-hierarchies-with-bean-overrides[context
hierarchies with bean overrides] for further details and examples.
====
Each annotation also defines Mockito-specific attributes to fine-tune the mocking behavior.
The `@MockitoBean` annotation uses the `REPLACE_OR_CREATE`
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-custom[strategy for test bean overriding].
If no existing bean matches, a new bean is created on the fly. However, you can switch to
the `REPLACE` strategy by setting the `enforceOverride` attribute to `true`. See the
following section for an example.
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-strategy[strategy for bean overrides].
If a corresponding bean does not exist, a new bean will be created. However, you can
switch to the `REPLACE` strategy by setting the `enforceOverride` attribute to `true`
for example, `@MockitoBean(enforceOverride = true)`.
The `@MockitoSpyBean` annotation uses the `WRAP`
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-custom[strategy],
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-strategy[strategy],
and the original instance is wrapped in a Mockito spy. This strategy requires that
exactly one candidate bean exists.
@@ -56,15 +99,8 @@ or `private` depending on the needs or coding practices of the project.
[[spring-testing-annotation-beanoverriding-mockitobean-examples]]
== `@MockitoBean` Examples
When using `@MockitoBean`, a new bean will be created if a corresponding bean does not
exist. However, if you would like for the test to fail when a corresponding bean does not
exist, you can set the `enforceOverride` attribute to `true` for example,
`@MockitoBean(enforceOverride = true)`.
To use a by-name override rather than a by-type override, specify the `name` (or `value`)
attribute of the annotation.
The following example shows how to use the default behavior of the `@MockitoBean` annotation:
The following example shows how to use the default behavior of the `@MockitoBean`
annotation.
[tabs]
======
@@ -81,7 +117,7 @@ Java::
// tests...
}
----
<1> Replace the bean with type `CustomService` with a Mockito `mock`.
<1> Replace the bean with type `CustomService` with a Mockito mock.
======
In the example above, we are creating a mock for `CustomService`. If more than one bean
@@ -90,7 +126,8 @@ will fail, and you will need to provide a qualifier of some sort to identify whi
`CustomService` beans you want to override. If no such bean exists, a bean will be
created with an auto-generated bean name.
The following example uses a by-name lookup, rather than a by-type lookup:
The following example uses a by-name lookup, rather than a by-type lookup. If no bean
named `service` exists, one is created.
[tabs]
======
@@ -108,32 +145,9 @@ Java::
}
----
<1> Replace the bean named `service` with a Mockito `mock`.
<1> Replace the bean named `service` with a Mockito mock.
======
If no bean named `service` exists, one is created.
`@MockitoBean` can also be used at the type level:
- on a test class or any superclass or implemented interface in the type hierarchy above
the test class
- on an enclosing class for a `@Nested` test class or on any class or interface in the
type hierarchy or enclosing class hierarchy above the `@Nested` test class
When `@MockitoBean` is declared at the type level, the type of bean (or beans) to mock
must be supplied via the `types` attribute for example,
`@MockitoBean(types = {OrderService.class, UserService.class})`. If multiple candidates
exist in the application context, you can explicitly specify a bean name to mock by
setting the `name` attribute. Note, however, that the `types` attribute must contain a
single type if an explicit bean `name` is configured for example,
`@MockitoBean(name = "ps1", types = PrintingService.class)`.
To support reuse of mock configuration, `@MockitoBean` may be used as a meta-annotation
to create custom _composed annotations_ — for example, to define common mock
configuration in a single annotation that can be reused across a test suite.
`@MockitoBean` can also be used as a repeatable annotation at the type level — for
example, to mock several beans by name.
The following `@SharedMocks` annotation registers two mocks by-type and one mock by-name.
[tabs]
@@ -191,7 +205,7 @@ APIs.
== `@MockitoSpyBean` Examples
The following example shows how to use the default behavior of the `@MockitoSpyBean`
annotation:
annotation.
[tabs]
======
@@ -208,7 +222,7 @@ Java::
// tests...
}
----
<1> Wrap the bean with type `CustomService` with a Mockito `spy`.
<1> Wrap the bean with type `CustomService` with a Mockito spy.
======
In the example above, we are wrapping the bean with type `CustomService`. If more than
@@ -216,7 +230,7 @@ one bean of that type exists, the bean named `customService` is considered. Othe
the test will fail, and you will need to provide a qualifier of some sort to identify
which of the `CustomService` beans you want to spy.
The following example uses a by-name lookup, rather than a by-type lookup:
The following example uses a by-name lookup, rather than a by-type lookup.
[tabs]
======
@@ -233,5 +247,58 @@ Java::
// tests...
}
----
<1> Wrap the bean named `service` with a Mockito `spy`.
<1> Wrap the bean named `service` with a Mockito spy.
======
The following `@SharedSpies` annotation registers two spies by-type and one spy by-name.
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@MockitoSpyBean(types = {OrderService.class, UserService.class}) // <1>
@MockitoSpyBean(name = "ps1", types = PrintingService.class) // <2>
public @interface SharedSpies {
}
----
<1> Register `OrderService` and `UserService` spies by-type.
<2> Register `PrintingService` spy by-name.
======
The following demonstrates how `@SharedSpies` can be used on a test class.
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig.class)
@SharedSpies // <1>
class BeanOverrideTests {
@Autowired OrderService orderService; // <2>
@Autowired UserService userService; // <2>
@Autowired PrintingService ps1; // <2>
// Inject other components that rely on the spies.
@Test
void testThatDependsOnMocks() {
// ...
}
}
----
<1> Register common spies via the custom `@SharedSpies` annotation.
<2> Optionally inject spies to _stub_ or _verify_ them.
======
TIP: The spies can also be injected into `@Configuration` classes or other test-related
components in the `ApplicationContext` in order to configure them with Mockito's stubbing
APIs.
@@ -1,8 +1,9 @@
[[spring-testing-annotation-beanoverriding-testbean]]
= `@TestBean`
`@TestBean` is used on a non-static field in a test class to override a specific bean in
the test's `ApplicationContext` with an instance provided by a factory method.
{spring-framework-api}/test/context/bean/override/convention/TestBean.html[`@TestBean`]
is used on a non-static field in a test class to override a specific bean in the test's
`ApplicationContext` with an instance provided by a factory method.
The associated factory method name is derived from the annotated field's name, or the
bean name if specified. The factory method must be `static`, accept no arguments, and
@@ -30,6 +31,19 @@ same bean in several tests, make sure to name the field consistently to avoid cr
unnecessary contexts.
====
[WARNING]
====
Using `@TestBean` in conjunction with `@ContextHierarchy` can lead to undesirable results
since each `@TestBean` will be applied to all context hierarchy levels by default. To
ensure that a particular `@TestBean` is applied to a single context hierarchy level, set
the `contextName` attribute to match a configured `@ContextConfiguration` name for
example, `@TestBean(contextName = "app-config")`.
See
xref:testing/testcontext-framework/ctx-management/hierarchies.adoc#testcontext-ctx-management-ctx-hierarchies-with-bean-overrides[context
hierarchies with bean overrides] for further details and examples.
====
[NOTE]
====
There are no restrictions on the visibility of `@TestBean` fields or factory methods.
@@ -26,16 +26,16 @@ Java::
@Test
void test() throws Exception {
MvcResult mvcResult = this.mockMvc.perform(get("/path"))
.andExpect(status().isOk()) <1>
.andExpect(request().asyncStarted()) <2>
.andExpect(request().asyncResult("body")) <3>
.andReturn();
MvcResult mvcResult = this.mockMvc.perform(get("/path"))
.andExpect(status().isOk()) <1>
.andExpect(request().asyncStarted()) <2>
.andExpect(request().asyncResult("body")) <3>
.andReturn();
this.mockMvc.perform(asyncDispatch(mvcResult)) <4>
.andExpect(status().isOk()) <5>
.andExpect(content().string("body"));
}
this.mockMvc.perform(asyncDispatch(mvcResult)) <4>
.andExpect(status().isOk()) <5>
.andExpect(content().string("body"));
}
----
<1> Check response status is still unchanged
<2> Async processing must have started
@@ -2,8 +2,8 @@
= Bean Overriding in Tests
Bean overriding in tests refers to the ability to override specific beans in the
`ApplicationContext` for a test class, by annotating one or more non-static fields in the
test class.
`ApplicationContext` for a test class, by annotating the test class or one or more
non-static fields in the test class.
NOTE: This feature is intended as a less risky alternative to the practice of registering
a bean via `@Bean` with the `DefaultListableBeanFactory`
@@ -42,15 +42,16 @@ The `spring-test` module registers implementations of the latter two
{spring-framework-code}/spring-test/src/main/resources/META-INF/spring.factories[`META-INF/spring.factories`
properties file].
The bean overriding infrastructure searches in test classes for any non-static field that
is meta-annotated with `@BeanOverride` and instantiates the corresponding
`BeanOverrideProcessor` which is responsible for creating an appropriate
`BeanOverrideHandler`.
The bean overriding infrastructure searches for annotations on test classes as well as
annotations on non-static fields in test classes that are meta-annotated with
`@BeanOverride` and instantiates the corresponding `BeanOverrideProcessor` which is
responsible for creating an appropriate `BeanOverrideHandler`.
The internal `BeanOverrideBeanFactoryPostProcessor` then uses bean override handlers to
alter the test's `ApplicationContext` by creating, replacing, or wrapping beans as
defined by the corresponding `BeanOverrideStrategy`:
[[testcontext-bean-overriding-strategy]]
`REPLACE`::
Replaces the bean. Throws an exception if a corresponding bean does not exist.
`REPLACE_OR_CREATE`::
@@ -4,7 +4,7 @@
Once the TestContext framework loads an `ApplicationContext` (or `WebApplicationContext`)
for a test, that context is cached and reused for all subsequent tests that declare the
same unique context configuration within the same test suite. To understand how caching
works, it is important to understand what is meant by "`unique`" and "`test suite.`"
works, it is important to understand what is meant by "unique" and "test suite."
An `ApplicationContext` can be uniquely identified by the combination of configuration
parameters that is used to load it. Consequently, the unique combination of configuration
@@ -15,8 +15,8 @@ framework uses the following configuration parameters to build the context cache
* `classes` (from `@ContextConfiguration`)
* `contextInitializerClasses` (from `@ContextConfiguration`)
* `contextCustomizers` (from `ContextCustomizerFactory`) this includes
`@DynamicPropertySource` methods as well as various features from Spring Boot's
testing support such as `@MockBean` and `@SpyBean`.
`@DynamicPropertySource` methods, bean overrides (such as `@TestBean`, `@MockitoBean`,
`@MockitoSpyBean` etc.), as well as various features from Spring Boot's testing support.
* `contextLoader` (from `@ContextConfiguration`)
* `parent` (from `@ContextHierarchy`)
* `activeProfiles` (from `@ActiveProfiles`)
@@ -1,5 +1,5 @@
[[testcontext-context-customizers]]
= Configuration Configuration with Context Customizers
= Context Configuration with Context Customizers
A `ContextCustomizer` is responsible for customizing the supplied
`ConfigurableApplicationContext` after bean definitions have been loaded into the context
@@ -22,8 +22,19 @@ given level in the hierarchy, the configuration resource type (that is, XML conf
files or component classes) must be consistent. Otherwise, it is perfectly acceptable to
have different levels in a context hierarchy configured using different resource types.
The remaining JUnit Jupiter based examples in this section show common configuration
scenarios for integration tests that require the use of context hierarchies.
[NOTE]
====
If you use `@DirtiesContext` in a test whose context is configured as part of a context
hierarchy, you can use the `hierarchyMode` flag to control how the context cache is
cleared.
For further details, see the discussion of `@DirtiesContext` in
xref:testing/annotations/integration-spring/annotation-dirtiescontext.adoc[Spring Testing Annotations]
and the {spring-framework-api}/test/annotation/DirtiesContext.html[`@DirtiesContext`] javadoc.
====
The JUnit Jupiter based examples in this section show common configuration scenarios for
integration tests that require the use of context hierarchies.
**Single test class with context hierarchy**
--
@@ -229,12 +240,118 @@ Kotlin::
class ExtendedTests : BaseTests() {}
----
======
.Dirtying a context within a context hierarchy
NOTE: If you use `@DirtiesContext` in a test whose context is configured as part of a
context hierarchy, you can use the `hierarchyMode` flag to control how the context cache
is cleared. For further details, see the discussion of `@DirtiesContext` in
xref:testing/annotations/integration-spring/annotation-dirtiescontext.adoc[Spring Testing Annotations] and the
{spring-framework-api}/test/annotation/DirtiesContext.html[`@DirtiesContext`] javadoc.
--
[[testcontext-ctx-management-ctx-hierarchies-with-bean-overrides]]
**Context hierarchies with bean overrides**
--
When `@ContextHierarchy` is used in conjunction with
xref:testing/testcontext-framework/bean-overriding.adoc[bean overrides] such as
`@TestBean`, `@MockitoBean`, or `@MockitoSpyBean`, it may be desirable or necessary to
have the override applied to a single level in the context hierarchy. To achieve that,
the bean override must specify a context name that matches a name configured via the
`name` attribute in `@ContextConfiguration`.
The following test class configures the name of the second hierarchy level to be
`"user-config"` and simultaneously specifies that the `UserService` should be wrapped in
a Mockito spy in the context named `"user-config"`. Consequently, Spring will only
attempt to create the spy in the `"user-config"` context and will not attempt to create
the spy in the parent context.
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@ExtendWith(SpringExtension.class)
@ContextHierarchy({
@ContextConfiguration(classes = AppConfig.class),
@ContextConfiguration(classes = UserConfig.class, name = "user-config")
})
class IntegrationTests {
@MockitoSpyBean(contextName = "user-config")
UserService userService;
// ...
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@ExtendWith(SpringExtension::class)
@ContextHierarchy(
ContextConfiguration(classes = [AppConfig::class]),
ContextConfiguration(classes = [UserConfig::class], name = "user-config"))
class IntegrationTests {
@MockitoSpyBean(contextName = "user-config")
lateinit var userService: UserService
// ...
}
----
======
When applying bean overrides in different levels of the context hierarchy, you may need
to have all of the bean override instances injected into the test class in order to
interact with them — for example, to configure stubbing for mocks. However, `@Autowired`
will always inject a matching bean found in the lowest level of the context hierarchy.
Thus, to inject bean override instances from specific levels in the context hierarchy,
you need to annotate fields with appropriate bean override annotations and configure the
name of the context level.
The following test class configures the names of the hierarchy levels to be `"parent"`
and `"child"`. It also declares two `PropertyService` fields that are configured to
create or replace `PropertyService` beans with Mockito mocks in the respective contexts,
named `"parent"` and `"child"`. Consequently, the mock from the `"parent"` context will
be injected into the `propertyServiceInParent` field, and the mock from the `"child"`
context will be injected into the `propertyServiceInChild` field.
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@ExtendWith(SpringExtension.class)
@ContextHierarchy({
@ContextConfiguration(classes = ParentConfig.class, name = "parent"),
@ContextConfiguration(classes = ChildConfig.class, name = "child")
})
class IntegrationTests {
@MockitoBean(contextName = "parent")
PropertyService propertyServiceInParent;
@MockitoBean(contextName = "child")
PropertyService propertyServiceInChild;
// ...
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@ExtendWith(SpringExtension::class)
@ContextHierarchy(
ContextConfiguration(classes = [ParentConfig::class], name = "parent"),
ContextConfiguration(classes = [ChildConfig::class], name = "child"))
class IntegrationTests {
@MockitoBean(contextName = "parent")
lateinit var propertyServiceInParent: PropertyService
@MockitoBean(contextName = "child")
lateinit var propertyServiceInChild: PropertyService
// ...
}
----
======
--
@@ -18,9 +18,9 @@ Do not run tests in parallel if the tests:
* Use Spring Framework's `@DirtiesContext` support.
* Use Spring Framework's `@MockitoBean` or `@MockitoSpyBean` support.
* Use Spring Boot's `@MockBean` or `@SpyBean` support.
* Use JUnit 4's `@FixMethodOrder` support or any testing framework feature
that is designed to ensure that test methods run in a particular order. Note,
however, that this does not apply if entire test classes are run in parallel.
* Use JUnit Jupiter's `@TestMethodOrder` support or any testing framework feature that is
designed to ensure that test methods run in a particular order. Note, however, that
this does not apply if entire test classes are run in parallel.
* Change the state of shared services or systems such as a database, message broker,
filesystem, and others. This applies to both embedded and external systems.
@@ -1,166 +1,9 @@
[[testcontext-support-classes]]
= TestContext Framework Support Classes
This section describes the various classes that support the Spring TestContext Framework.
This section describes the various classes that support the Spring TestContext Framework
in JUnit and TestNG.
[[testcontext-junit4-runner]]
== Spring JUnit 4 Runner
The Spring TestContext Framework offers full integration with JUnit 4 through a custom
runner (supported on JUnit 4.12 or higher). By annotating test classes with
`@RunWith(SpringJUnit4ClassRunner.class)` or the shorter `@RunWith(SpringRunner.class)`
variant, developers can implement standard JUnit 4-based unit and integration tests and
simultaneously reap the benefits of the TestContext framework, such as support for
loading application contexts, dependency injection of test instances, transactional test
method execution, and so on. If you want to use the Spring TestContext Framework with an
alternative runner (such as JUnit 4's `Parameterized` runner) or third-party runners
(such as the `MockitoJUnitRunner`), you can, optionally, use
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules[Spring's support for JUnit rules] instead.
The following code listing shows the minimal requirements for configuring a test class to
run with the custom Spring `Runner`:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@RunWith(SpringRunner.class)
@TestExecutionListeners({})
public class SimpleTest {
@Test
public void testMethod() {
// test logic...
}
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@RunWith(SpringRunner::class)
@TestExecutionListeners
class SimpleTest {
@Test
fun testMethod() {
// test logic...
}
}
----
======
In the preceding example, `@TestExecutionListeners` is configured with an empty list, to
disable the default listeners, which otherwise would require an `ApplicationContext` to
be configured through `@ContextConfiguration`.
[[testcontext-junit4-rules]]
== Spring JUnit 4 Rules
The `org.springframework.test.context.junit4.rules` package provides the following JUnit
4 rules (supported on JUnit 4.12 or higher):
* `SpringClassRule`
* `SpringMethodRule`
`SpringClassRule` is a JUnit `TestRule` that supports class-level features of the Spring
TestContext Framework, whereas `SpringMethodRule` is a JUnit `MethodRule` that supports
instance-level and method-level features of the Spring TestContext Framework.
In contrast to the `SpringRunner`, Spring's rule-based JUnit support has the advantage of
being independent of any `org.junit.runner.Runner` implementation and can, therefore, be
combined with existing alternative runners (such as JUnit 4's `Parameterized`) or
third-party runners (such as the `MockitoJUnitRunner`).
To support the full functionality of the TestContext framework, you must combine a
`SpringClassRule` with a `SpringMethodRule`. The following example shows the proper way
to declare these rules in an integration test:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
// Optionally specify a non-Spring Runner via @RunWith(...)
@ContextConfiguration
public class IntegrationTest {
@ClassRule
public static final SpringClassRule springClassRule = new SpringClassRule();
@Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
@Test
public void testMethod() {
// test logic...
}
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
// Optionally specify a non-Spring Runner via @RunWith(...)
@ContextConfiguration
class IntegrationTest {
@Rule
val springMethodRule = SpringMethodRule()
@Test
fun testMethod() {
// test logic...
}
companion object {
@ClassRule
val springClassRule = SpringClassRule()
}
}
----
======
[[testcontext-support-classes-junit4]]
== JUnit 4 Support Classes
The `org.springframework.test.context.junit4` package provides the following support
classes for JUnit 4-based test cases (supported on JUnit 4.12 or higher):
* `AbstractJUnit4SpringContextTests`
* `AbstractTransactionalJUnit4SpringContextTests`
`AbstractJUnit4SpringContextTests` is an abstract base test class that integrates the
Spring TestContext Framework with explicit `ApplicationContext` testing support in a
JUnit 4 environment. When you extend `AbstractJUnit4SpringContextTests`, you can access a
`protected` `applicationContext` instance variable that you can use to perform explicit
bean lookups or to test the state of the context as a whole.
`AbstractTransactionalJUnit4SpringContextTests` is an abstract transactional extension of
`AbstractJUnit4SpringContextTests` that adds some convenience functionality for JDBC
access. This class expects a `javax.sql.DataSource` bean and a
`PlatformTransactionManager` bean to be defined in the `ApplicationContext`. When you
extend `AbstractTransactionalJUnit4SpringContextTests`, you can access a `protected`
`jdbcTemplate` instance variable that you can use to run SQL statements to query the
database. You can use such queries to confirm database state both before and after
running database-related application code, and Spring ensures that such queries run in
the scope of the same transaction as the application code. When used in conjunction with
an ORM tool, be sure to avoid xref:testing/testcontext-framework/tx.adoc#testcontext-tx-false-positives[false positives].
As mentioned in xref:testing/support-jdbc.adoc[JDBC Testing Support],
`AbstractTransactionalJUnit4SpringContextTests` also provides convenience methods that
delegate to methods in `JdbcTestUtils` by using the aforementioned `jdbcTemplate`.
Furthermore, `AbstractTransactionalJUnit4SpringContextTests` provides an
`executeSqlScript(..)` method for running SQL scripts against the configured `DataSource`.
TIP: These classes are a convenience for extension. If you do not want your test classes
to be tied to a Spring-specific class hierarchy, you can configure your own custom test
classes by using `@RunWith(SpringRunner.class)` or xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules[Spring's JUnit rules]
.
[[testcontext-junit-jupiter-extension]]
== SpringExtension for JUnit Jupiter
@@ -177,14 +20,17 @@ following features above and beyond the feature set that Spring supports for JUn
TestNG:
* Dependency injection for test constructors, test methods, and test lifecycle callback
methods. See xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-di[Dependency Injection with the `SpringExtension`] for further details.
methods. See xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-di[Dependency
Injection with the `SpringExtension`] for further details.
* Powerful support for link:https://junit.org/junit5/docs/current/user-guide/#extensions-conditions[conditional
test execution] based on SpEL expressions, environment variables, system properties,
and so on. See the documentation for `@EnabledIf` and `@DisabledIf` in
xref:testing/annotations/integration-junit-jupiter.adoc[Spring JUnit Jupiter Testing Annotations] for further details and examples.
xref:testing/annotations/integration-junit-jupiter.adoc[Spring JUnit Jupiter Testing Annotations]
for further details and examples.
* Custom composed annotations that combine annotations from Spring and JUnit Jupiter. See
the `@TransactionalDevTestConfig` and `@TransactionalIntegrationTest` examples in
xref:testing/annotations/integration-meta.adoc[Meta-Annotation Support for Testing] for further details.
xref:testing/annotations/integration-meta.adoc[Meta-Annotation Support for Testing] for
further details.
The following code listing shows how to configure a test class to use the
`SpringExtension` in conjunction with `@ContextConfiguration`:
@@ -307,7 +153,8 @@ Kotlin::
======
See the documentation for `@SpringJUnitConfig` and `@SpringJUnitWebConfig` in
xref:testing/annotations/integration-junit-jupiter.adoc[Spring JUnit Jupiter Testing Annotations] for further details.
xref:testing/annotations/integration-junit-jupiter.adoc[Spring JUnit Jupiter Testing Annotations]
for further details.
[[testcontext-junit-jupiter-di]]
=== Dependency Injection with the `SpringExtension`
@@ -318,10 +165,9 @@ extension API from JUnit Jupiter, which lets Spring provide dependency injection
constructors, test methods, and test lifecycle callback methods.
Specifically, the `SpringExtension` can inject dependencies from the test's
`ApplicationContext` into test constructors and methods that are annotated with
Spring's `@BeforeTransaction` and `@AfterTransaction` or JUnit's `@BeforeAll`,
`@AfterAll`, `@BeforeEach`, `@AfterEach`, `@Test`, `@RepeatedTest`, `@ParameterizedTest`,
and others.
`ApplicationContext` into test constructors and methods that are annotated with Spring's
`@BeforeTransaction` and `@AfterTransaction` or JUnit's `@BeforeAll`, `@AfterAll`,
`@BeforeEach`, `@AfterEach`, `@Test`, `@RepeatedTest`, `@ParameterizedTest`, and others.
[[testcontext-junit-jupiter-di-constructor]]
@@ -341,8 +187,9 @@ autowirable if one of the following conditions is met (in order of precedence).
attribute set to `ALL`.
* The default _test constructor autowire mode_ has been changed to `ALL`.
See xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-testconstructor[`@TestConstructor`] for details on the use of
`@TestConstructor` and how to change the global _test constructor autowire mode_.
See xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-testconstructor[`@TestConstructor`]
for details on the use of `@TestConstructor` and how to change the global _test
constructor autowire mode_.
WARNING: If the constructor for a test class is considered to be _autowirable_, Spring
assumes the responsibility for resolving arguments for all parameters in the constructor.
@@ -407,8 +254,9 @@ Kotlin::
Note that this feature lets test dependencies be `final` and therefore immutable.
If the `spring.test.constructor.autowire.mode` property is to `all` (see
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-testconstructor[`@TestConstructor`]), we can omit the declaration of
`@Autowired` on the constructor in the previous example, resulting in the following.
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-testconstructor[`@TestConstructor`]),
we can omit the declaration of `@Autowired` on the constructor in the previous example,
resulting in the following.
[tabs]
======
@@ -553,17 +401,19 @@ honor `@NestedTestConfiguration` semantics.
In order to allow development teams to change the default to `OVERRIDE` for example,
for compatibility with Spring Framework 5.0 through 5.2 the default mode can be changed
globally via a JVM system property or a `spring.properties` file in the root of the
classpath. See the xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration["Changing the default enclosing configuration inheritance mode"]
note for details.
classpath. See the
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration["Changing the default enclosing configuration inheritance mode"]
note for details.
Although the following "Hello World" example is very simplistic, it shows how to declare
common configuration on a top-level class that is inherited by its `@Nested` test
classes. In this particular example, only the `TestConfig` configuration class is
inherited. Each nested test class provides its own set of active profiles, resulting in a
distinct `ApplicationContext` for each nested test class (see
xref:testing/testcontext-framework/ctx-management/caching.adoc[Context Caching] for details). Consult the list of
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration[supported annotations] to see
which annotations can be inherited in `@Nested` test classes.
xref:testing/testcontext-framework/ctx-management/caching.adoc[Context Caching] for details).
Consult the list of
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration[supported annotations]
to see which annotations can be inherited in `@Nested` test classes.
[tabs]
======
@@ -626,8 +476,174 @@ Kotlin::
----
======
[[testcontext-junit4-support]]
== JUnit 4 Support
[[testcontext-junit4-runner]]
=== Spring JUnit 4 Runner
The Spring TestContext Framework offers full integration with JUnit 4 through a custom
runner (supported on JUnit 4.12 or higher). By annotating test classes with
`@RunWith(SpringJUnit4ClassRunner.class)` or the shorter `@RunWith(SpringRunner.class)`
variant, developers can implement standard JUnit 4-based unit and integration tests and
simultaneously reap the benefits of the TestContext framework, such as support for
loading application contexts, dependency injection of test instances, transactional test
method execution, and so on. If you want to use the Spring TestContext Framework with an
alternative runner (such as JUnit 4's `Parameterized` runner) or third-party runners
(such as the `MockitoJUnitRunner`), you can, optionally, use
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules[Spring's support for JUnit rules]
instead.
The following code listing shows the minimal requirements for configuring a test class to
run with the custom Spring `Runner`:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@RunWith(SpringRunner.class)
@TestExecutionListeners({})
public class SimpleTest {
@Test
public void testMethod() {
// test logic...
}
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@RunWith(SpringRunner::class)
@TestExecutionListeners
class SimpleTest {
@Test
fun testMethod() {
// test logic...
}
}
----
======
In the preceding example, `@TestExecutionListeners` is configured with an empty list, to
disable the default listeners, which otherwise would require an `ApplicationContext` to
be configured through `@ContextConfiguration`.
[[testcontext-junit4-rules]]
=== Spring JUnit 4 Rules
The `org.springframework.test.context.junit4.rules` package provides the following JUnit
4 rules (supported on JUnit 4.12 or higher):
* `SpringClassRule`
* `SpringMethodRule`
`SpringClassRule` is a JUnit `TestRule` that supports class-level features of the Spring
TestContext Framework, whereas `SpringMethodRule` is a JUnit `MethodRule` that supports
instance-level and method-level features of the Spring TestContext Framework.
In contrast to the `SpringRunner`, Spring's rule-based JUnit support has the advantage of
being independent of any `org.junit.runner.Runner` implementation and can, therefore, be
combined with existing alternative runners (such as JUnit 4's `Parameterized`) or
third-party runners (such as the `MockitoJUnitRunner`).
To support the full functionality of the TestContext framework, you must combine a
`SpringClassRule` with a `SpringMethodRule`. The following example shows the proper way
to declare these rules in an integration test:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
// Optionally specify a non-Spring Runner via @RunWith(...)
@ContextConfiguration
public class IntegrationTest {
@ClassRule
public static final SpringClassRule springClassRule = new SpringClassRule();
@Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
@Test
public void testMethod() {
// test logic...
}
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
// Optionally specify a non-Spring Runner via @RunWith(...)
@ContextConfiguration
class IntegrationTest {
@Rule
val springMethodRule = SpringMethodRule()
@Test
fun testMethod() {
// test logic...
}
companion object {
@ClassRule
val springClassRule = SpringClassRule()
}
}
----
======
[[testcontext-support-classes-junit4]]
=== JUnit 4 Base Classes
The `org.springframework.test.context.junit4` package provides the following support
classes for JUnit 4-based test cases (supported on JUnit 4.12 or higher):
* `AbstractJUnit4SpringContextTests`
* `AbstractTransactionalJUnit4SpringContextTests`
`AbstractJUnit4SpringContextTests` is an abstract base test class that integrates the
Spring TestContext Framework with explicit `ApplicationContext` testing support in a
JUnit 4 environment. When you extend `AbstractJUnit4SpringContextTests`, you can access a
`protected` `applicationContext` instance variable that you can use to perform explicit
bean lookups or to test the state of the context as a whole.
`AbstractTransactionalJUnit4SpringContextTests` is an abstract transactional extension of
`AbstractJUnit4SpringContextTests` that adds some convenience functionality for JDBC
access. This class expects a `javax.sql.DataSource` bean and a
`PlatformTransactionManager` bean to be defined in the `ApplicationContext`. When you
extend `AbstractTransactionalJUnit4SpringContextTests`, you can access a `protected`
`jdbcTemplate` instance variable that you can use to run SQL statements to query the
database. You can use such queries to confirm database state both before and after
running database-related application code, and Spring ensures that such queries run in
the scope of the same transaction as the application code. When used in conjunction with
an ORM tool, be sure to avoid
xref:testing/testcontext-framework/tx.adoc#testcontext-tx-false-positives[false positives].
As mentioned in xref:testing/support-jdbc.adoc[JDBC Testing Support],
`AbstractTransactionalJUnit4SpringContextTests` also provides convenience methods that
delegate to methods in `JdbcTestUtils` by using the aforementioned `jdbcTemplate`.
Furthermore, `AbstractTransactionalJUnit4SpringContextTests` provides an
`executeSqlScript(..)` method for running SQL scripts against the configured `DataSource`.
TIP: These classes are a convenience for extension. If you do not want your test classes
to be tied to a Spring-specific class hierarchy, you can configure your own custom test
classes by using `@RunWith(SpringRunner.class)` or
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit4-rules[Spring's JUnit rules].
[[testcontext-support-classes-testng]]
== TestNG Support Classes
== TestNG Support
The `org.springframework.test.context.testng` package provides the following support
classes for TestNG based test cases:
@@ -650,7 +666,8 @@ extend `AbstractTransactionalTestNGSpringContextTests`, you can access a `protec
database. You can use such queries to confirm database state both before and after
running database-related application code, and Spring ensures that such queries run in
the scope of the same transaction as the application code. When used in conjunction with
an ORM tool, be sure to avoid xref:testing/testcontext-framework/tx.adoc#testcontext-tx-false-positives[false positives].
an ORM tool, be sure to avoid
xref:testing/testcontext-framework/tx.adoc#testcontext-tx-false-positives[false positives].
As mentioned in xref:testing/support-jdbc.adoc[JDBC Testing Support],
`AbstractTransactionalTestNGSpringContextTests` also provides convenience methods that
delegate to methods in `JdbcTestUtils` by using the aforementioned `jdbcTemplate`.
@@ -7,16 +7,17 @@ by default, exactly in the following order:
* `ServletTestExecutionListener`: Configures Servlet API mocks for a
`WebApplicationContext`.
* `DirtiesContextBeforeModesTestExecutionListener`: Handles the `@DirtiesContext`
annotation for "`before`" modes.
annotation for "before" modes.
* `ApplicationEventsTestExecutionListener`: Provides support for
xref:testing/testcontext-framework/application-events.adoc[`ApplicationEvents`].
* `BeanOverrideTestExecutionListener`: Provides support for xref:testing/testcontext-framework/bean-overriding.adoc[] .
* `BeanOverrideTestExecutionListener`: Provides support for
xref:testing/testcontext-framework/bean-overriding.adoc[].
* `DependencyInjectionTestExecutionListener`: Provides dependency injection for the test
instance.
* `MicrometerObservationRegistryTestExecutionListener`: Provides support for
Micrometer's `ObservationRegistry`.
* `DirtiesContextTestExecutionListener`: Handles the `@DirtiesContext` annotation for
"`after`" modes.
"after" modes.
* `CommonCachesTestExecutionListener`: Clears resource caches in the test's
`ApplicationContext` if necessary.
* `TransactionalTestExecutionListener`: Provides transactional test execution with
@@ -161,15 +162,16 @@ change from release to release -- for example, `SqlScriptsTestExecutionListener`
introduced in Spring Framework 4.1, and `DirtiesContextBeforeModesTestExecutionListener`
was introduced in Spring Framework 4.2. Furthermore, third-party frameworks like Spring
Boot and Spring Security register their own default `TestExecutionListener`
implementations by using the aforementioned xref:testing/testcontext-framework/tel-config.adoc#testcontext-tel-config-automatic-discovery[automatic discovery mechanism]
.
implementations by using the aforementioned
xref:testing/testcontext-framework/tel-config.adoc#testcontext-tel-config-automatic-discovery[automatic discovery mechanism].
To avoid having to be aware of and re-declare all default listeners, you can set the
`mergeMode` attribute of `@TestExecutionListeners` to `MergeMode.MERGE_WITH_DEFAULTS`.
`MERGE_WITH_DEFAULTS` indicates that locally declared listeners should be merged with the
default listeners. The merging algorithm ensures that duplicates are removed from the
list and that the resulting set of merged listeners is sorted according to the semantics
of `AnnotationAwareOrderComparator`, as described in xref:testing/testcontext-framework/tel-config.adoc#testcontext-tel-config-ordering[Ordering `TestExecutionListener` Implementations].
of `AnnotationAwareOrderComparator`, as described in
xref:testing/testcontext-framework/tel-config.adoc#testcontext-tel-config-ordering[Ordering `TestExecutionListener` Implementations].
If a listener implements `Ordered` or is annotated with `@Order`, it can influence the
position in which it is merged with the defaults. Otherwise, locally declared listeners
are appended to the list of default listeners when merged.
@@ -47,8 +47,7 @@ out-of-container tests for code that depends on environment-specific properties.
The `org.springframework.mock.web` package contains a comprehensive set of Servlet API
mock objects that are useful for testing web contexts, controllers, and filters. These
mock objects are targeted at usage with Spring's Web MVC framework and are generally more
convenient to use than dynamic mock objects (such as https://easymock.org/[EasyMock])
or alternative Servlet API mock objects (such as http://www.mockobjects.com[MockObjects]).
convenient to use than dynamic mock objects (such as https://easymock.org/[EasyMock]).
TIP: Since Spring Framework 6.0, the mock objects in `org.springframework.mock.web` are
based on the Servlet 6.0 API.
@@ -396,7 +396,7 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
import org.springframework.test.web.reactive.server.expectBody
import org.springframework.test.web.reactive.server.expectBody
client.get().uri("/persons/1")
.exchange()
@@ -1,5 +1,6 @@
[[webflux-view]]
= View Technologies
[.small]#xref:web/webmvc-view.adoc[See equivalent in the Servlet stack]#
The rendering of views in Spring WebFlux is pluggable. Whether you decide to
@@ -224,9 +225,9 @@ The following table shows the templating libraries that we have tested on differ
|Scripting Library |Scripting Engine
|https://handlebarsjs.com/[Handlebars] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|https://mustache.github.io/[Mustache] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|https://facebook.github.io/react/[React] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|https://www.embeddedjs.com/[EJS] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|https://www.stuartellis.name/articles/erb/[ERB] |https://www.jruby.org[JRuby]
|https://react.dev/[React] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|https://ejs.co/[EJS] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|https://docs.ruby-lang.org/en/master/ERB.html[ERB] |https://www.jruby.org[JRuby]
|https://docs.python.org/2/library/string.html#template-strings[String templates] |https://www.jython.org/[Jython]
|https://github.com/sdeleuze/kotlin-script-templating[Kotlin Script templating] |{kotlin-site}[Kotlin]
|===
@@ -306,34 +306,34 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
Resource resource = ...
Mono<String> result = webClient
.post()
.uri("https://example.com")
.body(Flux.concat(
FormPartEvent.create("field", "field value"),
FilePartEvent.create("file", resource)
), PartEvent.class)
.retrieve()
.bodyToMono(String.class);
Resource resource = ...
Mono<String> result = webClient
.post()
.uri("https://example.com")
.body(Flux.concat(
FormPartEvent.create("field", "field value"),
FilePartEvent.create("file", resource)
), PartEvent.class)
.retrieve()
.bodyToMono(String.class);
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
var resource: Resource = ...
var result: Mono<String> = webClient
.post()
.uri("https://example.com")
.body(
Flux.concat(
FormPartEvent.create("field", "field value"),
FilePartEvent.create("file", resource)
var resource: Resource = ...
var result: Mono<String> = webClient
.post()
.uri("https://example.com")
.body(
Flux.concat(
FormPartEvent.create("field", "field value"),
FilePartEvent.create("file", resource)
)
)
)
.retrieve()
.bodyToMono()
.retrieve()
.bodyToMono()
----
======
@@ -390,29 +390,29 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
HttpClient httpClient = HttpClient.newBuilder()
.followRedirects(Redirect.NORMAL)
.connectTimeout(Duration.ofSeconds(20))
.build();
HttpClient httpClient = HttpClient.newBuilder()
.followRedirects(Redirect.NORMAL)
.connectTimeout(Duration.ofSeconds(20))
.build();
ClientHttpConnector connector =
new JdkClientHttpConnector(httpClient, new DefaultDataBufferFactory());
ClientHttpConnector connector =
new JdkClientHttpConnector(httpClient, new DefaultDataBufferFactory());
WebClient webClient = WebClient.builder().clientConnector(connector).build();
WebClient webClient = WebClient.builder().clientConnector(connector).build();
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val httpClient = HttpClient.newBuilder()
.followRedirects(Redirect.NORMAL)
.connectTimeout(Duration.ofSeconds(20))
.build()
val httpClient = HttpClient.newBuilder()
.followRedirects(Redirect.NORMAL)
.connectTimeout(Duration.ofSeconds(20))
.build()
val connector = JdkClientHttpConnector(httpClient, DefaultDataBufferFactory())
val connector = JdkClientHttpConnector(httpClient, DefaultDataBufferFactory())
val webClient = WebClient.builder().clientConnector(connector).build()
val webClient = WebClient.builder().clientConnector(connector).build()
----
======
@@ -158,65 +158,65 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
public class MultipartExchangeFilterFunction implements ExchangeFilterFunction {
@Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
if (MediaType.MULTIPART_FORM_DATA.includes(request.headers().getContentType())
&& (request.method() == HttpMethod.PUT || request.method() == HttpMethod.POST)) {
return next.exchange(ClientRequest.from(request).body((outputMessage, context) ->
request.body().insert(new BufferingDecorator(outputMessage), context)).build()
);
} else {
return next.exchange(request);
}
}
private static final class BufferingDecorator extends ClientHttpRequestDecorator {
private BufferingDecorator(ClientHttpRequest delegate) {
super(delegate);
}
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
return DataBufferUtils.join(body).flatMap(buffer -> {
getHeaders().setContentLength(buffer.readableByteCount());
return super.writeWith(Mono.just(buffer));
});
}
}
}
public class MultipartExchangeFilterFunction implements ExchangeFilterFunction {
@Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
if (MediaType.MULTIPART_FORM_DATA.includes(request.headers().getContentType())
&& (request.method() == HttpMethod.PUT || request.method() == HttpMethod.POST)) {
return next.exchange(ClientRequest.from(request).body((outputMessage, context) ->
request.body().insert(new BufferingDecorator(outputMessage), context)).build()
);
} else {
return next.exchange(request);
}
}
private static final class BufferingDecorator extends ClientHttpRequestDecorator {
private BufferingDecorator(ClientHttpRequest delegate) {
super(delegate);
}
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
return DataBufferUtils.join(body).flatMap(buffer -> {
getHeaders().setContentLength(buffer.readableByteCount());
return super.writeWith(Mono.just(buffer));
});
}
}
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
class MultipartExchangeFilterFunction : ExchangeFilterFunction {
override fun filter(request: ClientRequest, next: ExchangeFunction): Mono<ClientResponse> {
return if (MediaType.MULTIPART_FORM_DATA.includes(request.headers().getContentType())
&& (request.method() == HttpMethod.PUT || request.method() == HttpMethod.POST)) {
next.exchange(ClientRequest.from(request)
.body { message, context -> request.body().insert(BufferingDecorator(message), context) }
.build())
}
else {
next.exchange(request)
}
}
private class BufferingDecorator(delegate: ClientHttpRequest) : ClientHttpRequestDecorator(delegate) {
override fun writeWith(body: Publisher<out DataBuffer>): Mono<Void> {
return DataBufferUtils.join(body)
.flatMap {
headers.contentLength = it.readableByteCount().toLong()
super.writeWith(Mono.just(it))
}
}
}
}
class MultipartExchangeFilterFunction : ExchangeFilterFunction {
override fun filter(request: ClientRequest, next: ExchangeFunction): Mono<ClientResponse> {
return if (MediaType.MULTIPART_FORM_DATA.includes(request.headers().getContentType())
&& (request.method() == HttpMethod.PUT || request.method() == HttpMethod.POST)) {
next.exchange(ClientRequest.from(request)
.body { message, context -> request.body().insert(BufferingDecorator(message), context) }
.build())
}
else {
next.exchange(request)
}
}
private class BufferingDecorator(delegate: ClientHttpRequest) : ClientHttpRequestDecorator(delegate) {
override fun writeWith(body: Publisher<out DataBuffer>): Mono<Void> {
return DataBufferUtils.join(body)
.flatMap {
headers.contentLength = it.readableByteCount().toLong()
super.writeWith(Mono.just(it))
}
}
}
}
----
======
======
@@ -207,14 +207,14 @@ Kotlin::
class ExampleHandler : WebSocketHandler {
override fun handle(session: WebSocketSession): Mono<Void> {
return session.receive() // <1>
return session.receive() // <1>
.doOnNext {
// ... // <2>
}
.concatMap {
// ... // <3>
}
.then() // <4>
.then() // <4>
}
}
----
@@ -268,16 +268,16 @@ Kotlin::
override fun handle(session: WebSocketSession): Mono<Void> {
val output = session.receive() // <1>
val output = session.receive() // <1>
.doOnNext {
// ...
}
.concatMap {
// ...
}
.map { session.textMessage("Echo $it") } // <2>
.map { session.textMessage("Echo $it") } // <2>
return session.send(output) // <3>
return session.send(output) // <3>
}
}
----
@@ -149,7 +149,7 @@ Java::
DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
registrar.setUseIsoFormat(true);
registrar.registerFormatters(registry);
}
}
}
----
@@ -62,7 +62,7 @@ Java::
----
class Account {
private final String firstName;
private final String firstName;
public Account(@BindParam("first-name") String firstName) {
this.firstName = firstName;
@@ -93,8 +93,8 @@ Java::
----
@ModelAttribute
public void addAccount(@RequestParam String number) {
Mono<Account> accountMono = accountRepository.findAccount(number);
model.addAttribute("account", accountMono);
Mono<Account> accountMono = accountRepository.findAccount(number);
model.addAttribute("account", accountMono);
}
@PostMapping("/accounts")
@@ -234,8 +234,8 @@ Kotlin::
--
URI path patterns can also have embedded `${...}` placeholders that are resolved on startup
through `PropertySourcesPlaceholderConfigurer` against local, system, environment, and
other property sources. You can use this to, for example, parameterize a base URL based on
by using `PropertySourcesPlaceholderConfigurer` against local, system, environment, and
other property sources. You can use this, for example, to parameterize a base URL based on
some external configuration.
NOTE: Spring WebFlux uses `PathPattern` and the `PathPatternParser` for URI path matching support.
@@ -57,7 +57,7 @@ locale and language specific resource bundles.
For further custom handling of method validation errors, you can extend
`ResponseEntityExceptionHandler` or use an `@ExceptionHandler` method in a controller
or in a `@ControllerAdvice`, and handle `HandlerMethodValidationException` directly.
The exception contains a list of``ParameterValidationResult``s that group validation errors
The exception contains a list of ``ParameterValidationResult``s that group validation errors
by method parameter. You can either iterate over those, or provide a visitor with callback
methods by controller method parameter type:
@@ -104,21 +104,21 @@ Kotlin::
override fun requestHeader(requestHeader: RequestHeader, result: ParameterValidationResult) {
// ...
}
}
override fun requestParam(requestParam: RequestParam?, result: ParameterValidationResult) {
// ...
}
}
override fun modelAttribute(modelAttribute: ModelAttribute?, errors: ParameterErrors) {
// ...
}
}
// ...
override fun other(result: ParameterValidationResult) {
// ...
}
}
})
----
======
@@ -782,8 +782,8 @@ Java::
----
WebClient webClient = WebClient.builder()
.codecs(configurer -> {
CustomDecoder decoder = new CustomDecoder();
configurer.customCodecs().registerWithDefaultConfig(decoder);
CustomDecoder decoder = new CustomDecoder();
configurer.customCodecs().registerWithDefaultConfig(decoder);
})
.build();
----
@@ -794,8 +794,8 @@ Kotlin::
----
val webClient = WebClient.builder()
.codecs({ configurer ->
val decoder = CustomDecoder()
configurer.customCodecs().registerWithDefaultConfig(decoder)
val decoder = CustomDecoder()
configurer.customCodecs().registerWithDefaultConfig(decoder)
})
.build()
----
@@ -276,7 +276,7 @@ ServerResponse.async(asyncResponse);
----
======
https://www.w3.org/TR/eventsource/[Server-Sent Events] can be provided via the
https://html.spec.whatwg.org/multipage/server-sent-events.html[Server-Sent Events] can be provided via the
static `sse` method on `ServerResponse`. The builder provided by that method
allows you to send Strings, or other objects as JSON. For example:
@@ -781,7 +781,7 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
ClassPathResource index = new ClassPathResource("static/index.html");
ClassPathResource index = new ClassPathResource("static/index.html");
List<String> extensions = List.of("js", "css", "ico", "png", "jpg", "gif");
RequestPredicate spaPredicate = path("/api/**").or(path("/error")).or(pathExtension(extensions::contains)).negate();
RouterFunction<ServerResponse> redirectToIndex = route()
@@ -793,7 +793,7 @@ Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val redirectToIndex = router {
val redirectToIndex = router {
val index = ClassPathResource("static/index.html")
val extensions = listOf("js", "css", "ico", "png", "jpg", "gif")
val spaPredicate = !(path("/api/**") or path("/error") or
@@ -814,16 +814,16 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
Resource location = new FileUrlResource("public-resources/");
RouterFunction<ServerResponse> resources = RouterFunctions.resources("/resources/**", location);
Resource location = new FileUrlResource("public-resources/");
RouterFunction<ServerResponse> resources = RouterFunctions.resources("/resources/**", location);
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
val location = FileUrlResource("public-resources/")
val resources = router { resources("/resources/**", location) }
val location = FileUrlResource("public-resources/")
val resources = router { resources("/resources/**", location) }
----
======
@@ -1,6 +1,7 @@
[[mvc-view]]
= View Technologies
:page-section-summary-toc: 1
[.small]#xref:web/webflux-view.adoc[See equivalent in the Reactive stack]#
The rendering of views in Spring MVC is pluggable. Whether you decide to use
@@ -130,6 +130,7 @@ the `Configuration` object.
[[mvc-view-freemarker-forms]]
== Form Handling
[.small]#xref:web/webflux-view.adoc#webflux-view-freemarker-forms[See equivalent in the Reactive stack]#
Spring provides a tag library for use in JSPs that contains, among others, a
`<spring:bind/>` element. This element primarily lets forms display values from
@@ -13,9 +13,9 @@ templating libraries on different script engines:
|Scripting Library |Scripting Engine
|https://handlebarsjs.com/[Handlebars] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|https://mustache.github.io/[Mustache] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|https://facebook.github.io/react/[React] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|https://www.embeddedjs.com/[EJS] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|https://www.stuartellis.name/articles/erb/[ERB] |https://www.jruby.org[JRuby]
|https://react.dev/[React] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|https://ejs.co/[EJS] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|https://docs.ruby-lang.org/en/master/ERB.html[ERB] |https://www.jruby.org[JRuby]
|https://docs.python.org/2/library/string.html#template-strings[String templates] |https://www.jython.org/[Jython]
|https://github.com/sdeleuze/kotlin-script-templating[Kotlin Script templating] |{kotlin-site}[Kotlin]
|===
@@ -47,7 +47,7 @@ through https://www.webjars.org/[WebJars].
[[mvc-view-script-integrate]]
== Script Templates
[.small]#xref:web/webflux-view.adoc#webflux-view-script[See equivalent in the Reactive stack]#
[.small]#xref:web/webflux-view.adoc#webflux-view-script-integrate[See equivalent in the Reactive stack]#
You can declare a `ScriptTemplateConfigurer` bean to specify the script engine to use,
the script files to load, what function to call to render templates, and so on.
@@ -281,7 +281,7 @@ invokes the configured exception resolvers and completes the request.
=== SSE
`SseEmitter` (a subclass of `ResponseBodyEmitter`) provides support for
https://www.w3.org/TR/eventsource/[Server-Sent Events], where events sent from the server
https://html.spec.whatwg.org/multipage/server-sent-events.html[Server-Sent Events], where events sent from the server
are formatted according to the W3C SSE specification. To produce an SSE
stream from a controller, return `SseEmitter`, as the following example shows:
@@ -37,7 +37,7 @@ already set.
"application/problem+json" over "application/json" when rendering a `ProblemDetail`,
and also falls back on it if no compatible media type is found.
To enable RFC 9457 responses for Spring WebFlux exceptions and for any
To enable RFC 9457 responses for Spring MVC exceptions and for any
`ErrorResponseException`, extend `ResponseEntityExceptionHandler` and declare it as an
xref:web/webmvc/mvc-controller/ann-advice.adoc[@ControllerAdvice] in Spring configuration. The handler
has an `@ExceptionHandler` method that handles any `ErrorResponse` exception, which
@@ -97,7 +97,7 @@ Java::
----
class Account {
private final String firstName;
private final String firstName;
public Account(@BindParam("first-name") String firstName) {
this.firstName = firstName;
@@ -57,7 +57,7 @@ locale and language specific resource bundles.
For further custom handling of method validation errors, you can extend
`ResponseEntityExceptionHandler` or use an `@ExceptionHandler` method in a controller
or in a `@ControllerAdvice`, and handle `HandlerMethodValidationException` directly.
The exception contains a list of``ParameterValidationResult``s that group validation errors
The exception contains a list of ``ParameterValidationResult``s that group validation errors
by method parameter. You can either iterate over those, or provide a visitor with callback
methods by controller method parameter type:
@@ -73,12 +73,12 @@ Java::
@Override
public void requestHeader(RequestHeader requestHeader, ParameterValidationResult result) {
// ...
// ...
}
@Override
public void requestParam(@Nullable RequestParam requestParam, ParameterValidationResult result) {
// ...
// ...
}
@Override
@@ -88,7 +88,7 @@ Java::
@Override
public void other(ParameterValidationResult result) {
// ...
// ...
}
});
----
@@ -103,22 +103,22 @@ Kotlin::
ex.visitResults(object : HandlerMethodValidationException.Visitor {
override fun requestHeader(requestHeader: RequestHeader, result: ParameterValidationResult) {
// ...
}
// ...
}
override fun requestParam(requestParam: RequestParam?, result: ParameterValidationResult) {
// ...
}
// ...
}
override fun modelAttribute(modelAttribute: ModelAttribute?, errors: ParameterErrors) {
// ...
}
// ...
}
// ...
override fun other(result: ParameterValidationResult) {
// ...
}
// ...
}
})
----
======
@@ -13,7 +13,7 @@ reference documentation, including:
* {docs-spring-security}/features/exploits/csrf.html#csrf-protection[CSRF protection]
* {docs-spring-security}/features/exploits/headers.html[Security Response Headers]
https://hdiv.org/[HDIV] is another web security framework that integrates with Spring MVC.
https://github.com/hdiv/hdiv[HDIV] is another web security framework that integrates with Spring MVC.
@@ -22,11 +22,11 @@ The following example code is based on it:
[source,javascript,indent=0,subs="verbatim,quotes"]
----
const stompClient = new StompJs.Client({
brokerURL: 'ws://domain.com/portfolio',
onConnect: () => {
// ...
}
});
brokerURL: 'ws://domain.com/portfolio',
onConnect: () => {
// ...
}
});
----
Alternatively, if you connect through SockJS, you can enable the
@@ -47,5 +47,3 @@ interactive web application] -- a getting started guide.
* https://github.com/rstoyanchev/spring-websocket-portfolio[Stock Portfolio] -- a sample
application.
@@ -19,6 +19,6 @@ from where they are handled according to their destination prefix. As the channe
a `ThreadPoolExecutor`, messages are processed in different threads, and the resulting sequence
of handling may not match the exact order in which they were received.
To enable ordered publishing, set the `setPreserveReceiveOrder` flag as follows:
To enable ordered receiving, set the `setPreserveReceiveOrder` flag as follows:
include-code::./ReceiveOrderWebSocketConfiguration[tag=snippet,indent=0]
+1
View File
@@ -5,6 +5,7 @@
"@antora/collector-extension": "1.0.0-alpha.3",
"@asciidoctor/tabs": "1.0.0-beta.6",
"@springio/antora-extensions": "1.14.2",
"fast-xml-parser": "4.5.2",
"@springio/asciidoctor-extensions": "1.0.0-alpha.10"
}
}
@@ -0,0 +1,105 @@
/*
* Copyright 2002-2025 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.resthttpinterface.customresolver;
import java.util.List;
import org.springframework.core.MethodParameter;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.support.RestClientAdapter;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.invoker.HttpRequestValues;
import org.springframework.web.service.invoker.HttpServiceArgumentResolver;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;
public class CustomHttpServiceArgumentResolver {
// tag::httpinterface[]
public interface RepositoryService {
@GetExchange("/repos/search")
List<Repository> searchRepository(Search search);
}
// end::httpinterface[]
class Sample {
void sample() {
// tag::usage[]
RestClient restClient = RestClient.builder().baseUrl("https://api.github.com/").build();
RestClientAdapter adapter = RestClientAdapter.create(restClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory
.builderFor(adapter)
.customArgumentResolver(new SearchQueryArgumentResolver())
.build();
RepositoryService repositoryService = factory.createClient(RepositoryService.class);
Search search = Search.create()
.owner("spring-projects")
.language("java")
.query("rest")
.build();
List<Repository> repositories = repositoryService.searchRepository(search);
// end::usage[]
}
}
// tag::argumentresolver[]
static class SearchQueryArgumentResolver implements HttpServiceArgumentResolver {
@Override
public boolean resolve(Object argument, MethodParameter parameter, HttpRequestValues.Builder requestValues) {
if (parameter.getParameterType().equals(Search.class)) {
Search search = (Search) argument;
requestValues.addRequestParameter("owner", search.owner());
requestValues.addRequestParameter("language", search.language());
requestValues.addRequestParameter("query", search.query());
return true;
}
return false;
}
}
// end::argumentresolver[]
record Search (String query, String owner, String language) {
static Builder create() {
return new Builder();
}
static class Builder {
Builder query(String query) { return this;}
Builder owner(String owner) { return this;}
Builder language(String language) { return this;}
Search build() {
return new Search(null, null, null);
}
}
}
record Repository(String name) {
}
}
@@ -0,0 +1,78 @@
/*
* Copyright 2002-2025 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.resthttpinterface.customresolver
import org.springframework.core.MethodParameter
import org.springframework.web.client.RestClient
import org.springframework.web.client.support.RestClientAdapter
import org.springframework.web.service.annotation.GetExchange
import org.springframework.web.service.invoker.HttpRequestValues
import org.springframework.web.service.invoker.HttpServiceArgumentResolver
import org.springframework.web.service.invoker.HttpServiceProxyFactory
class CustomHttpServiceArgumentResolver {
// tag::httpinterface[]
interface RepositoryService {
@GetExchange("/repos/search")
fun searchRepository(search: Search): List<Repository>
}
// end::httpinterface[]
class Sample {
fun sample() {
// tag::usage[]
val restClient = RestClient.builder().baseUrl("https://api.github.com/").build()
val adapter = RestClientAdapter.create(restClient)
val factory = HttpServiceProxyFactory
.builderFor(adapter)
.customArgumentResolver(SearchQueryArgumentResolver())
.build()
val repositoryService = factory.createClient<RepositoryService>(RepositoryService::class.java)
val search = Search(owner = "spring-projects", language = "java", query = "rest")
val repositories = repositoryService.searchRepository(search)
// end::usage[]
repositories.size
}
}
// tag::argumentresolver[]
class SearchQueryArgumentResolver : HttpServiceArgumentResolver {
override fun resolve(
argument: Any?,
parameter: MethodParameter,
requestValues: HttpRequestValues.Builder
): Boolean {
if (parameter.getParameterType() == Search::class.java) {
val search = argument as Search
requestValues.addRequestParameter("owner", search.owner)
.addRequestParameter("language", search.language)
.addRequestParameter("query", search.query)
return true
}
return false
}
}
// end::argumentresolver[]
data class Search(val query: String, val owner: String, val language: String)
data class Repository(val name: String)
}
+31 -31
View File
@@ -7,31 +7,31 @@ javaPlatform {
}
dependencies {
api(platform("com.fasterxml.jackson:jackson-bom:2.18.2"))
api(platform("io.micrometer:micrometer-bom:1.14.3"))
api(platform("io.netty:netty-bom:4.1.117.Final"))
api(platform("com.fasterxml.jackson:jackson-bom:2.18.4"))
api(platform("io.micrometer:micrometer-bom:1.14.7"))
api(platform("io.netty:netty-bom:4.1.121.Final"))
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
api(platform("io.projectreactor:reactor-bom:2024.0.2"))
api(platform("io.rsocket:rsocket-bom:1.1.4"))
api(platform("org.apache.groovy:groovy-bom:4.0.24"))
api(platform("io.projectreactor:reactor-bom:2024.0.6"))
api(platform("io.rsocket:rsocket-bom:1.1.5"))
api(platform("org.apache.groovy:groovy-bom:4.0.26"))
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
api(platform("org.assertj:assertj-bom:3.27.2"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.16"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.16"))
api(platform("org.assertj:assertj-bom:3.27.3"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.21"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.21"))
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1"))
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.3"))
api(platform("org.junit:junit-bom:5.11.4"))
api(platform("org.mockito:mockito-bom:5.15.2"))
api(platform("org.junit:junit-bom:5.12.2"))
api(platform("org.mockito:mockito-bom:5.17.0"))
constraints {
api("com.fasterxml:aalto-xml:1.3.2")
api("com.fasterxml.woodstox:woodstox-core:6.7.0")
api("com.github.ben-manes.caffeine:caffeine:3.1.8")
api("com.github.ben-manes.caffeine:caffeine:3.2.0")
api("com.github.librepdf:openpdf:1.3.43")
api("com.google.code.findbugs:findbugs:3.0.1")
api("com.google.code.findbugs:jsr305:3.0.2")
api("com.google.code.gson:gson:2.11.0")
api("com.google.protobuf:protobuf-java-util:4.29.3")
api("com.google.code.gson:gson:2.12.1")
api("com.google.protobuf:protobuf-java-util:4.30.2")
api("com.h2database:h2:2.3.232")
api("com.jayway.jsonpath:json-path:2.9.0")
api("com.oracle.database.jdbc:ojdbc11:21.9.0.0")
@@ -43,7 +43,7 @@ dependencies {
api("com.sun.xml.bind:jaxb-core:3.0.2")
api("com.sun.xml.bind:jaxb-impl:3.0.2")
api("com.sun.xml.bind:jaxb-xjc:3.0.2")
api("com.thoughtworks.qdox:qdox:2.1.0")
api("com.thoughtworks.qdox:qdox:2.2.0")
api("com.thoughtworks.xstream:xstream:1.4.21")
api("commons-io:commons-io:2.15.0")
api("de.bechte.junit:junit-hierarchicalcontextrunner:4.12.2")
@@ -100,8 +100,8 @@ 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.4.1")
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.3.1")
api("org.apache.httpcomponents.client5:httpclient5:5.4.4")
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.3.4")
api("org.apache.poi:poi-ooxml:5.2.5")
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.28")
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.28")
@@ -110,18 +110,18 @@ dependencies {
api("org.aspectj:aspectjrt:1.9.22.1")
api("org.aspectj:aspectjtools:1.9.22.1")
api("org.aspectj:aspectjweaver:1.9.22.1")
api("org.awaitility:awaitility:4.2.2")
api("org.awaitility:awaitility:4.3.0")
api("org.bouncycastle:bcpkix-jdk18on:1.72")
api("org.codehaus.jettison:jettison:1.5.4")
api("org.crac:crac:1.4.0")
api("org.dom4j:dom4j:2.1.4")
api("org.easymock:easymock:5.4.0")
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.8")
api("org.easymock:easymock:5.5.0")
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.9")
api("org.eclipse.persistence:org.eclipse.persistence.jpa:3.0.4")
api("org.eclipse:yasson:2.0.4")
api("org.ehcache:ehcache:3.10.8")
api("org.ehcache:jcache:1.0.1")
api("org.freemarker:freemarker:2.3.33")
api("org.freemarker:freemarker:2.3.34")
api("org.glassfish.external:opendmk_jmxremote_optional_jar:1.0-b01-ea")
api("org.glassfish:jakarta.el:4.0.2")
api("org.glassfish.tyrus:tyrus-container-servlet:2.1.3")
@@ -130,24 +130,24 @@ dependencies {
api("org.hibernate:hibernate-core-jakarta:5.6.15.Final")
api("org.hibernate:hibernate-validator:7.0.5.Final")
api("org.hsqldb:hsqldb:2.7.4")
api("org.htmlunit:htmlunit:4.6.0")
api("org.htmlunit:htmlunit:4.10.0")
api("org.javamoney:moneta:1.4.4")
api("org.jruby:jruby:9.4.9.0")
api("org.jruby:jruby:9.4.12.0")
api("org.junit.support:testng-engine:1.0.5")
api("org.mozilla:rhino:1.7.15")
api("org.ogce:xpp3:1.1.6")
api("org.python:jython-standalone:2.7.3")
api("org.python:jython-standalone:2.7.4")
api("org.quartz-scheduler:quartz:2.3.2")
api("org.seleniumhq.selenium:htmlunit3-driver:4.26.0")
api("org.seleniumhq.selenium:selenium-java:4.26.0")
api("org.seleniumhq.selenium:htmlunit3-driver:4.29.0")
api("org.seleniumhq.selenium:selenium-java:4.29.0")
api("org.skyscreamer:jsonassert:1.5.3")
api("org.slf4j:slf4j-api:2.0.16")
api("org.testng:testng:7.10.2")
api("org.slf4j:slf4j-api:2.0.17")
api("org.testng:testng:7.11.0")
api("org.webjars:underscorejs:1.8.3")
api("org.webjars:webjars-locator-core:0.55")
api("org.webjars:webjars-locator-lite:1.0.0")
api("org.webjars:webjars-locator-core:0.59")
api("org.webjars:webjars-locator-lite:1.1.0")
api("org.xmlunit:xmlunit-assertj:2.10.0")
api("org.xmlunit:xmlunit-matchers:2.10.0")
api("org.yaml:snakeyaml:2.3")
api("org.yaml:snakeyaml:2.4")
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
version=6.2.2
version=6.2.7
org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m
+2 -2
View File
@@ -98,7 +98,7 @@ if (project.name == "spring-oxm") {
}
// Include project specific settings
task eclipseSettings(type: Copy) {
tasks.register('eclipseSettings', Copy) {
from rootProject.files(
'src/eclipse/org.eclipse.core.resources.prefs',
'src/eclipse/org.eclipse.jdt.core.prefs',
@@ -107,7 +107,7 @@ task eclipseSettings(type: Copy) {
outputs.upToDateWhen { false }
}
task cleanEclipseSettings(type: Delete) {
tasks.register('cleanEclipseSettings', Delete) {
delete project.file('.settings/org.eclipse.core.resources.prefs')
delete project.file('.settings/org.eclipse.jdt.core.prefs')
delete project.file('.settings/org.eclipse.jdt.ui.prefs')
+26 -27
View File
@@ -69,32 +69,31 @@ normalization {
javadoc {
description = "Generates project-level javadoc for use in -javadoc jar"
options.encoding = "UTF-8"
options.memberLevel = JavadocMemberLevel.PROTECTED
options.author = true
options.header = project.name
options.use = true
options.links(project.ext.javadocLinks)
// Check for syntax during linting. 'none' doesn't seem to work in suppressing
// all linting warnings all the time (see/link references most notably).
options.addStringOption("Xdoclint:syntax", "-quiet")
// Suppress warnings due to cross-module @see and @link references.
// Note that global 'api' task does display all warnings, and
// checks for 'reference' on top of 'syntax'.
logging.captureStandardError LogLevel.INFO
logging.captureStandardOutput LogLevel.INFO // suppress "## warnings" message
failOnError = true
options {
encoding = "UTF-8"
memberLevel = JavadocMemberLevel.PROTECTED
author = true
header = project.name
use = true
links(project.ext.javadocLinks)
// Check for 'syntax' during linting. Note that the global
// 'framework-api:javadoc' task checks for 'reference' in addition
// to 'syntax'.
addBooleanOption("Xdoclint:syntax,-reference", true)
addBooleanOption('Werror', true) // fail build on Javadoc warnings
}
}
task sourcesJar(type: Jar, dependsOn: classes) {
tasks.register('sourcesJar', Jar) {
dependsOn classes
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
archiveClassifier.set("sources")
from sourceSets.main.allSource
// Don't include or exclude anything explicitly by default. See SPR-12085.
}
task javadocJar(type: Jar) {
tasks.register('javadocJar', Jar) {
archiveClassifier.set("javadoc")
from javadoc
}
@@ -114,16 +113,16 @@ components.java.withVariantsFromConfiguration(configurations.testFixturesApiElem
components.java.withVariantsFromConfiguration(configurations.testFixturesRuntimeElements) { skip() }
tasks.withType(JavaCompile).configureEach {
options.errorprone {
disableAllChecks = true
option("NullAway:CustomContractAnnotations", "org.springframework.lang.Contract")
option("NullAway:AnnotatedPackages", "org.springframework")
option("NullAway:UnannotatedSubPackages", "org.springframework.instrument,org.springframework.context.index," +
options.errorprone {
disableAllChecks = true
option("NullAway:CustomContractAnnotations", "org.springframework.lang.Contract")
option("NullAway:AnnotatedPackages", "org.springframework")
option("NullAway:UnannotatedSubPackages", "org.springframework.instrument,org.springframework.context.index," +
"org.springframework.asm,org.springframework.cglib,org.springframework.objenesis," +
"org.springframework.javapoet,org.springframework.aot.nativex.substitution,org.springframework.aot.nativex.feature")
}
}
}
tasks.compileJava {
// The check defaults to a warning, bump it up to an error for the main sources
options.errorprone.error("NullAway")
}
// The check defaults to a warning, bump it up to an error for the main sources
options.errorprone.error("NullAway")
}
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+3 -3
View File
@@ -114,7 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
@@ -205,7 +205,7 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
@@ -213,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
Vendored
+2 -2
View File
@@ -70,11 +70,11 @@ goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
+1 -1
View File
@@ -1,5 +1,5 @@
plugins {
id "com.gradle.develocity" version "3.17.2"
id "com.gradle.develocity" version "3.19"
id "io.spring.ge.conventions" version "0.0.17"
id "org.gradle.toolchains.foojay-resolver-convention" version "0.7.0"
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2025 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.
@@ -276,14 +276,18 @@ public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedence
}
if (this.aspectJAdviceMethod.getParameterCount() == this.argumentNames.length + 1) {
// May need to add implicit join point arg name...
Class<?> firstArgType = this.aspectJAdviceMethod.getParameterTypes()[0];
if (firstArgType == JoinPoint.class ||
firstArgType == ProceedingJoinPoint.class ||
firstArgType == JoinPoint.StaticPart.class) {
String[] oldNames = this.argumentNames;
this.argumentNames = new String[oldNames.length + 1];
this.argumentNames[0] = "THIS_JOIN_POINT";
System.arraycopy(oldNames, 0, this.argumentNames, 1, oldNames.length);
for (int i = 0; i < this.aspectJAdviceMethod.getParameterCount(); i++) {
Class<?> argType = this.aspectJAdviceMethod.getParameterTypes()[i];
if (argType == JoinPoint.class ||
argType == ProceedingJoinPoint.class ||
argType == JoinPoint.StaticPart.class) {
String[] oldNames = this.argumentNames;
this.argumentNames = new String[oldNames.length + 1];
System.arraycopy(oldNames, 0, this.argumentNames, 0, i);
this.argumentNames[i] = "THIS_JOIN_POINT";
System.arraycopy(oldNames, i, this.argumentNames, i + 1, oldNames.length - i);
break;
}
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2025 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.
@@ -241,7 +241,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
try {
int algorithmicStep = STEP_JOIN_POINT_BINDING;
while ((this.numberOfRemainingUnboundArguments > 0) && algorithmicStep < STEP_FINISHED) {
while (this.numberOfRemainingUnboundArguments > 0 && algorithmicStep < STEP_FINISHED) {
switch (algorithmicStep++) {
case STEP_JOIN_POINT_BINDING -> {
if (!maybeBindThisJoinPoint()) {
@@ -373,7 +373,8 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
if (this.returningName != null) {
if (this.numberOfRemainingUnboundArguments > 1) {
throw new AmbiguousBindingException("Binding of returning parameter '" + this.returningName +
"' is ambiguous: there are " + this.numberOfRemainingUnboundArguments + " candidates.");
"' is ambiguous: there are " + this.numberOfRemainingUnboundArguments + " candidates. " +
"Consider compiling with -parameters in order to make declared parameter names available.");
}
// We're all set... find the unbound parameter, and bind it.
@@ -485,8 +486,8 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
*/
private void maybeBindThisOrTargetOrArgsFromPointcutExpression() {
if (this.numberOfRemainingUnboundArguments > 1) {
throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments
+ " unbound args at this()/target()/args() binding stage, with no way to determine between them");
throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments +
" unbound args at this()/target()/args() binding stage, with no way to determine between them");
}
List<String> varNames = new ArrayList<>();
@@ -535,8 +536,8 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
private void maybeBindReferencePointcutParameter() {
if (this.numberOfRemainingUnboundArguments > 1) {
throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments
+ " unbound args at reference pointcut binding stage, with no way to determine between them");
throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments +
" unbound args at reference pointcut binding stage, with no way to determine between them");
}
List<String> varNames = new ArrayList<>();
@@ -741,7 +742,9 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
* Simple record to hold the extracted text from a pointcut body, together
* with the number of tokens consumed in extracting it.
*/
private record PointcutBody(int numTokensConsumed, @Nullable String text) {}
private record PointcutBody(int numTokensConsumed, @Nullable String text) {
}
/**
* Thrown in response to an ambiguous binding being detected when
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 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.
@@ -111,7 +111,7 @@ public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFacto
* Create a new {@code ReflectiveAspectJAdvisorFactory}, propagating the given
* {@link BeanFactory} to the created {@link AspectJExpressionPointcut} instances,
* for bean pointcut handling as well as consistent {@link ClassLoader} resolution.
* @param beanFactory the BeanFactory to propagate (may be {@code null}}
* @param beanFactory the BeanFactory to propagate (may be {@code null})
* @since 4.3.6
* @see AspectJExpressionPointcut#setBeanFactory
* @see org.springframework.beans.factory.config.ConfigurableBeanFactory#getBeanClassLoader()
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2025 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.
@@ -46,8 +46,8 @@ public class AspectEntry implements ParseState.Entry {
@Override
public String toString() {
return "Aspect: " + (StringUtils.hasLength(this.id) ? "id='" + this.id + "'"
: "ref='" + this.ref + "'");
return "Aspect: " + (StringUtils.hasLength(this.id) ? "id='" + this.id + "'" :
"ref='" + this.ref + "'");
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,6 +35,7 @@ import org.springframework.aop.AopInvocationException;
import org.springframework.aop.RawTargetAccess;
import org.springframework.aop.TargetSource;
import org.springframework.aop.support.AopUtils;
import org.springframework.aot.AotDetector;
import org.springframework.cglib.core.ClassLoaderAwareGeneratorStrategy;
import org.springframework.cglib.core.CodeGenerationException;
import org.springframework.cglib.core.GeneratorStrategy;
@@ -205,7 +206,7 @@ class CglibAopProxy implements AopProxy, Serializable {
enhancer.setSuperclass(proxySuperClass);
enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setAttemptLoad(true);
enhancer.setAttemptLoad(enhancer.getUseCache() && AotDetector.useGeneratedArtifacts());
enhancer.setStrategy(KotlinDetector.isKotlinType(proxySuperClass) ?
new ClassLoaderAwareGeneratorStrategy(classLoader) :
new ClassLoaderAwareGeneratorStrategy(classLoader, undeclaredThrowableStrategy)
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2025 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.
@@ -114,10 +114,10 @@ public class BeanNameAutoProxyCreator extends AbstractAutoProxyCreator {
boolean isFactoryBean = FactoryBean.class.isAssignableFrom(beanClass);
for (String mappedName : this.beanNames) {
if (isFactoryBean) {
if (!mappedName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {
if (mappedName.isEmpty() || mappedName.charAt(0) != BeanFactory.FACTORY_BEAN_PREFIX_CHAR) {
continue;
}
mappedName = mappedName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());
mappedName = mappedName.substring(1); // length of '&'
}
if (isMatch(beanName, mappedName)) {
return true;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -125,7 +125,7 @@ public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport imple
return null;
};
return doSubmit(task, executor, invocation.getMethod().getReturnType());
return doSubmit(task, executor, userMethod.getReturnType());
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2025 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.
@@ -198,8 +198,8 @@ public abstract class ClassFilters {
@Override
public boolean equals(Object other) {
return (this == other || (other instanceof NegateClassFilter that
&& this.original.equals(that.original)));
return (this == other || (other instanceof NegateClassFilter that &&
this.original.equals(that.original)));
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2025 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.
@@ -378,8 +378,8 @@ public abstract class MethodMatchers {
@Override
public boolean equals(Object other) {
return (this == other || (other instanceof NegateMethodMatcher that
&& this.original.equals(that.original)));
return (this == other || (other instanceof NegateMethodMatcher that &&
this.original.equals(that.original)));
}
@Override
@@ -0,0 +1,135 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.aop.aspectj;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.function.Consumer;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link AbstractAspectJAdvice}.
*
* @author Joshua Chen
* @author Stephane Nicoll
*/
class AbstractAspectJAdviceTests {
@Test
void setArgumentNamesFromStringArray_withoutJoinPointParameter() {
AbstractAspectJAdvice advice = getAspectJAdvice("methodWithNoJoinPoint");
assertThat(advice).satisfies(hasArgumentNames("arg1", "arg2"));
}
@Test
void setArgumentNamesFromStringArray_withJoinPointAsFirstParameter() {
AbstractAspectJAdvice advice = getAspectJAdvice("methodWithJoinPointAsFirstParameter");
assertThat(advice).satisfies(hasArgumentNames("THIS_JOIN_POINT", "arg1", "arg2"));
}
@Test
void setArgumentNamesFromStringArray_withJoinPointAsLastParameter() {
AbstractAspectJAdvice advice = getAspectJAdvice("methodWithJoinPointAsLastParameter");
assertThat(advice).satisfies(hasArgumentNames("arg1", "arg2", "THIS_JOIN_POINT"));
}
@Test
void setArgumentNamesFromStringArray_withJoinPointAsMiddleParameter() {
AbstractAspectJAdvice advice = getAspectJAdvice("methodWithJoinPointAsMiddleParameter");
assertThat(advice).satisfies(hasArgumentNames("arg1", "THIS_JOIN_POINT", "arg2"));
}
@Test
void setArgumentNamesFromStringArray_withProceedingJoinPoint() {
AbstractAspectJAdvice advice = getAspectJAdvice("methodWithProceedingJoinPoint");
assertThat(advice).satisfies(hasArgumentNames("THIS_JOIN_POINT", "arg1", "arg2"));
}
@Test
void setArgumentNamesFromStringArray_withStaticPart() {
AbstractAspectJAdvice advice = getAspectJAdvice("methodWithStaticPart");
assertThat(advice).satisfies(hasArgumentNames("THIS_JOIN_POINT", "arg1", "arg2"));
}
private Consumer<AbstractAspectJAdvice> hasArgumentNames(String... argumentNames) {
return advice -> assertThat(advice).extracting("argumentNames")
.asInstanceOf(InstanceOfAssertFactories.array(String[].class))
.containsExactly(argumentNames);
}
private AbstractAspectJAdvice getAspectJAdvice(final String methodName) {
AbstractAspectJAdvice advice = new TestAspectJAdvice(getMethod(methodName),
mock(AspectJExpressionPointcut.class), mock(AspectInstanceFactory.class));
advice.setArgumentNamesFromStringArray("arg1", "arg2");
return advice;
}
private Method getMethod(final String methodName) {
return Arrays.stream(Sample.class.getDeclaredMethods())
.filter(method -> method.getName().equals(methodName)).findFirst()
.orElseThrow();
}
@SuppressWarnings("serial")
public static class TestAspectJAdvice extends AbstractAspectJAdvice {
public TestAspectJAdvice(Method aspectJAdviceMethod, AspectJExpressionPointcut pointcut,
AspectInstanceFactory aspectInstanceFactory) {
super(aspectJAdviceMethod, pointcut, aspectInstanceFactory);
}
@Override
public boolean isBeforeAdvice() {
return false;
}
@Override
public boolean isAfterAdvice() {
return false;
}
}
@SuppressWarnings("unused")
static class Sample {
void methodWithNoJoinPoint(String arg1, String arg2) {
}
void methodWithJoinPointAsFirstParameter(JoinPoint joinPoint, String arg1, String arg2) {
}
void methodWithJoinPointAsLastParameter(String arg1, String arg2, JoinPoint joinPoint) {
}
void methodWithJoinPointAsMiddleParameter(String arg1, JoinPoint joinPoint, String arg2) {
}
void methodWithProceedingJoinPoint(ProceedingJoinPoint joinPoint, String arg1, String arg2) {
}
void methodWithStaticPart(JoinPoint.StaticPart staticPart, String arg1, String arg2) {
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -203,7 +203,6 @@ abstract class AbstractAspectJAdvisorFactoryTests {
itb.getSpouse();
assertThat(maaif.isMaterialized()).isTrue();
assertThat(imapa.getDeclaredPointcut().getMethodMatcher().matches(TestBean.class.getMethod("getAge"), null)).isTrue();
assertThat(itb.getAge()).as("Around advice must apply").isEqualTo(0);
@@ -301,7 +300,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
void bindingWithMultipleArgsDifferentlyOrdered() {
ManyValuedArgs target = new ManyValuedArgs();
ManyValuedArgs mva = createProxy(target, ManyValuedArgs.class,
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new ManyValuedArgs(), "someBean")));
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new ManyValuedArgs(), "someBean")));
String a = "a";
int b = 12;
@@ -320,7 +319,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
NotLockable notLockableTarget = new NotLockable();
assertThat(notLockableTarget).isNotInstanceOf(Lockable.class);
NotLockable notLockable1 = createProxy(notLockableTarget, NotLockable.class,
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")));
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")));
assertThat(notLockable1).isInstanceOf(Lockable.class);
Lockable lockable = (Lockable) notLockable1;
assertThat(lockable.locked()).isFalse();
@@ -329,7 +328,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
NotLockable notLockable2Target = new NotLockable();
NotLockable notLockable2 = createProxy(notLockable2Target, NotLockable.class,
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")));
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")));
assertThat(notLockable2).isInstanceOf(Lockable.class);
Lockable lockable2 = (Lockable) notLockable2;
assertThat(lockable2.locked()).isFalse();
@@ -343,20 +342,19 @@ abstract class AbstractAspectJAdvisorFactoryTests {
void introductionAdvisorExcludedFromTargetImplementingInterface() {
assertThat(AopUtils.findAdvisorsThatCanApply(
getAdvisorFactory().getAdvisors(
aspectInstanceFactory(new MakeLockable(), "someBean")),
aspectInstanceFactory(new MakeLockable(), "someBean")),
CannotBeUnlocked.class)).isEmpty();
assertThat(AopUtils.findAdvisorsThatCanApply(getAdvisorFactory().getAdvisors(
aspectInstanceFactory(new MakeLockable(),"someBean")), NotLockable.class)).hasSize(2);
aspectInstanceFactory(new MakeLockable(),"someBean")), NotLockable.class)).hasSize(2);
}
@Test
void introductionOnTargetImplementingInterface() {
CannotBeUnlocked target = new CannotBeUnlocked();
Lockable proxy = createProxy(target, CannotBeUnlocked.class,
// Ensure that we exclude
AopUtils.findAdvisorsThatCanApply(
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")),
CannotBeUnlocked.class));
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")),
CannotBeUnlocked.class));
assertThat(proxy).isInstanceOf(Lockable.class);
Lockable lockable = proxy;
assertThat(lockable.locked()).as("Already locked").isTrue();
@@ -370,8 +368,8 @@ abstract class AbstractAspectJAdvisorFactoryTests {
ArrayList<Object> target = new ArrayList<>();
List<?> proxy = createProxy(target, List.class,
AopUtils.findAdvisorsThatCanApply(
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")),
List.class));
getAdvisorFactory().getAdvisors(aspectInstanceFactory(new MakeLockable(), "someBean")),
List.class));
assertThat(proxy).as("Type pattern must have excluded mixin").isNotInstanceOf(Lockable.class);
}
@@ -379,7 +377,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
void introductionBasedOnAnnotationMatch() { // gh-9980
AnnotatedTarget target = new AnnotatedTargetImpl();
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
aspectInstanceFactory(new MakeAnnotatedTypeModifiable(), "someBean"));
aspectInstanceFactory(new MakeAnnotatedTypeModifiable(), "someBean"));
Object proxy = createProxy(target, AnnotatedTarget.class, advisors);
assertThat(proxy).isInstanceOf(Lockable.class);
Lockable lockable = (Lockable) proxy;
@@ -393,9 +391,9 @@ abstract class AbstractAspectJAdvisorFactoryTests {
TestBean target = new TestBean();
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
aspectInstanceFactory(new MakeITestBeanModifiable(), "someBean"));
aspectInstanceFactory(new MakeITestBeanModifiable(), "someBean"));
advisors.addAll(getAdvisorFactory().getAdvisors(
aspectInstanceFactory(new MakeLockable(), "someBean")));
aspectInstanceFactory(new MakeLockable(), "someBean")));
Modifiable modifiable = (Modifiable) createProxy(target, ITestBean.class, advisors);
assertThat(modifiable).isInstanceOf(Modifiable.class);
@@ -426,7 +424,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
TestBean target = new TestBean();
UnsupportedOperationException expectedException = new UnsupportedOperationException();
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
aspectInstanceFactory(new ExceptionThrowingAspect(expectedException), "someBean"));
aspectInstanceFactory(new ExceptionThrowingAspect(expectedException), "someBean"));
assertThat(advisors).as("One advice method was found").hasSize(1);
ITestBean itb = createProxy(target, ITestBean.class, advisors);
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(itb::getAge);
@@ -439,12 +437,12 @@ abstract class AbstractAspectJAdvisorFactoryTests {
TestBean target = new TestBean();
RemoteException expectedException = new RemoteException();
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
aspectInstanceFactory(new ExceptionThrowingAspect(expectedException), "someBean"));
aspectInstanceFactory(new ExceptionThrowingAspect(expectedException), "someBean"));
assertThat(advisors).as("One advice method was found").hasSize(1);
ITestBean itb = createProxy(target, ITestBean.class, advisors);
assertThatExceptionOfType(UndeclaredThrowableException.class)
.isThrownBy(itb::getAge)
.withCause(expectedException);
.isThrownBy(itb::getAge)
.withCause(expectedException);
}
@Test
@@ -452,7 +450,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
TestBean target = new TestBean();
TwoAdviceAspect twoAdviceAspect = new TwoAdviceAspect();
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
aspectInstanceFactory(twoAdviceAspect, "someBean"));
aspectInstanceFactory(twoAdviceAspect, "someBean"));
assertThat(advisors).as("Two advice methods found").hasSize(2);
ITestBean itb = createProxy(target, ITestBean.class, advisors);
itb.setName("");
@@ -466,7 +464,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
void afterAdviceTypes() throws Exception {
InvocationTrackingAspect aspect = new InvocationTrackingAspect();
List<Advisor> advisors = getAdvisorFactory().getAdvisors(
aspectInstanceFactory(aspect, "exceptionHandlingAspect"));
aspectInstanceFactory(aspect, "exceptionHandlingAspect"));
Echo echo = createProxy(new Echo(), Echo.class, advisors);
assertThat(aspect.invocations).isEmpty();
@@ -475,7 +473,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
aspect.invocations.clear();
assertThatExceptionOfType(FileNotFoundException.class)
.isThrownBy(() -> echo.echo(new FileNotFoundException()));
.isThrownBy(() -> echo.echo(new FileNotFoundException()));
assertThat(aspect.invocations).containsExactly("around - start", "before", "after throwing", "after", "around - end");
}
@@ -487,7 +485,6 @@ abstract class AbstractAspectJAdvisorFactoryTests {
assertThat(Modifier.isAbstract(aspect.getClass().getSuperclass().getModifiers())).isFalse();
List<Advisor> advisors = getAdvisorFactory().getAdvisors(aspectInstanceFactory(aspect, "incrementingAspect"));
ITestBean proxy = createProxy(new TestBean("Jane", 42), ITestBean.class, advisors);
assertThat(proxy.getAge()).isEqualTo(86); // (42 + 1) * 2
}
@@ -812,20 +809,20 @@ abstract class AbstractAspectJAdvisorFactoryTests {
invocations.add("before");
}
@AfterReturning("echo()")
void afterReturning() {
invocations.add("after returning");
}
@AfterThrowing("echo()")
void afterThrowing() {
invocations.add("after throwing");
}
@After("echo()")
void after() {
invocations.add("after");
}
@AfterReturning(pointcut = "this(target) && execution(* echo(*))", returning = "returnValue")
void afterReturning(JoinPoint joinPoint, Echo target, Object returnValue) {
invocations.add("after returning");
}
@AfterThrowing(pointcut = "this(target) && execution(* echo(*))", throwing = "exception")
void afterThrowing(JoinPoint joinPoint, Echo target, Throwable exception) {
invocations.add("after throwing");
}
}
@@ -967,7 +964,7 @@ abstract class AbstractMakeModifiable {
class MakeITestBeanModifiable extends AbstractMakeModifiable {
@DeclareParents(value = "org.springframework.beans.testfixture.beans.ITestBean+",
defaultImpl=ModifiableImpl.class)
defaultImpl = ModifiableImpl.class)
static MutableModifiable mixin;
}
@@ -0,0 +1,72 @@
/*
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.aop.interceptor;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.core.task.AsyncTaskExecutor;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link AsyncExecutionInterceptor}.
*
* @author Bao Ngo
* @since 7.0
*/
class AsyncExecutionInterceptorTests {
@Test
@SuppressWarnings("unchecked")
void invokeOnInterfaceWithGeneric() throws Throwable {
AsyncExecutionInterceptor interceptor = spy(new AsyncExecutionInterceptor(null));
FutureRunner impl = new FutureRunner();
MethodInvocation mi = mock();
given(mi.getThis()).willReturn(impl);
given(mi.getMethod()).willReturn(GenericRunner.class.getMethod("run"));
interceptor.invoke(mi);
ArgumentCaptor<Class<?>> classArgumentCaptor = ArgumentCaptor.forClass(Class.class);
verify(interceptor).doSubmit(any(Callable.class), any(AsyncTaskExecutor.class), classArgumentCaptor.capture());
assertThat(classArgumentCaptor.getValue()).isEqualTo(Future.class);
}
interface GenericRunner<O> {
O run();
}
static class FutureRunner implements GenericRunner<Future<Void>> {
@Override
public Future<Void> run() {
return CompletableFuture.runAsync(() -> {
});
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 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.
@@ -291,7 +291,7 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
String lastKey = tokens.keys[tokens.keys.length - 1];
if (propValue.getClass().isArray()) {
Class<?> requiredType = propValue.getClass().componentType();
Class<?> componentType = propValue.getClass().componentType();
int arrayIndex = Integer.parseInt(lastKey);
Object oldValue = null;
try {
@@ -299,10 +299,9 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
oldValue = Array.get(propValue, arrayIndex);
}
Object convertedValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(),
requiredType, ph.nested(tokens.keys.length));
componentType, ph.nested(tokens.keys.length));
int length = Array.getLength(propValue);
if (arrayIndex >= length && arrayIndex < this.autoGrowCollectionLimit) {
Class<?> componentType = propValue.getClass().componentType();
Object newArray = Array.newInstance(componentType, arrayIndex + 1);
System.arraycopy(propValue, 0, newArray, 0, length);
int lastKeyIndex = tokens.canonicalName.lastIndexOf('[');
@@ -658,6 +657,14 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
growCollectionIfNecessary(list, index, indexedPropertyName.toString(), ph, i + 1);
value = list.get(index);
}
else if (value instanceof Map map) {
Class<?> mapKeyType = ph.getResolvableType().getNested(i + 1).asMap().resolveGeneric(0);
// IMPORTANT: Do not pass full property name in here - property editors
// must not kick in for map keys but rather only for map values.
TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(mapKeyType);
Object convertedMapKey = convertIfNecessary(null, null, key, mapKeyType, typeDescriptor);
value = map.get(convertedMapKey);
}
else if (value instanceof Iterable iterable) {
// Apply index to Iterator in case of a Set/Collection/Iterable.
int index = Integer.parseInt(key);
@@ -685,14 +692,6 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
currIndex + ", accessed using property path '" + propertyName + "'");
}
}
else if (value instanceof Map map) {
Class<?> mapKeyType = ph.getResolvableType().getNested(i + 1).asMap().resolveGeneric(0);
// IMPORTANT: Do not pass full property name in here - property editors
// must not kick in for map keys but rather only for map values.
TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(mapKeyType);
Object convertedMapKey = convertIfNecessary(null, null, key, mapKeyType, typeDescriptor);
value = map.get(convertedMapKey);
}
else {
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
"Property referenced in indexed property path '" + propertyName +
@@ -904,16 +903,7 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
private Object newValue(Class<?> type, @Nullable TypeDescriptor desc, String name) {
try {
if (type.isArray()) {
Class<?> componentType = type.componentType();
// TODO - only handles 2-dimensional arrays
if (componentType.isArray()) {
Object array = Array.newInstance(componentType, 1);
Array.set(array, 0, Array.newInstance(componentType.componentType(), 0));
return array;
}
else {
return Array.newInstance(componentType, 0);
}
return createArray(type);
}
else if (Collection.class.isAssignableFrom(type)) {
TypeDescriptor elementDesc = (desc != null ? desc.getElementTypeDescriptor() : null);
@@ -937,6 +927,24 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
}
}
/**
* Create the array for the given array type.
* @param arrayType the desired type of the target array
* @return a new array instance
*/
private static Object createArray(Class<?> arrayType) {
Assert.notNull(arrayType, "Array type must not be null");
Class<?> componentType = arrayType.componentType();
if (componentType.isArray()) {
Object array = Array.newInstance(componentType, 1);
Array.set(array, 0, createArray(componentType));
return array;
}
else {
return Array.newInstance(componentType, 0);
}
}
/**
* Parse the given property name into the corresponding property name tokens.
* @param propertyName the property name to parse
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2025 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.
@@ -25,6 +25,7 @@ import org.springframework.lang.Nullable;
* analogous to an InvocationTargetException.
*
* @author Rod Johnson
* @author Juergen Hoeller
*/
@SuppressWarnings("serial")
public class MethodInvocationException extends PropertyAccessException {
@@ -41,7 +42,9 @@ public class MethodInvocationException extends PropertyAccessException {
* @param cause the Throwable raised by the invoked method
*/
public MethodInvocationException(PropertyChangeEvent propertyChangeEvent, @Nullable Throwable cause) {
super(propertyChangeEvent, "Property '" + propertyChangeEvent.getPropertyName() + "' threw exception", cause);
super(propertyChangeEvent,
"Property '" + propertyChangeEvent.getPropertyName() + "' threw exception: " + cause,
cause);
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,4 +45,18 @@ public interface PropertyEditorRegistrar {
*/
void registerCustomEditors(PropertyEditorRegistry registry);
/**
* Indicate whether this registrar exclusively overrides default editors
* rather than registering custom editors, intended to be applied lazily.
* <p>This has an impact on registrar handling in a bean factory: see
* {@link org.springframework.beans.factory.config.ConfigurableBeanFactory#addPropertyEditorRegistrar}.
* @since 6.2.3
* @see PropertyEditorRegistry#registerCustomEditor
* @see PropertyEditorRegistrySupport#overrideDefaultEditor
* @see PropertyEditorRegistrySupport#setDefaultEditorRegistrar
*/
default boolean overridesDefaultEditors() {
return false;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -99,6 +99,9 @@ public class PropertyEditorRegistrySupport implements PropertyEditorRegistry {
private boolean configValueEditorsActive = false;
@Nullable
private PropertyEditorRegistrar defaultEditorRegistrar;
@Nullable
private Map<Class<?>, PropertyEditor> defaultEditors;
@@ -155,6 +158,19 @@ public class PropertyEditorRegistrySupport implements PropertyEditorRegistry {
this.configValueEditorsActive = true;
}
/**
* Set a registrar for default editors, as a lazy way of overriding default editors.
* <p>This is expected to be a collaborator with {@link PropertyEditorRegistrySupport},
* downcasting the given {@link PropertyEditorRegistry} accordingly and calling
* {@link #overrideDefaultEditor} for registering additional default editors on it.
* @param registrar the registrar to call when default editors are actually needed
* @since 6.2.3
* @see #overrideDefaultEditor
*/
public void setDefaultEditorRegistrar(PropertyEditorRegistrar registrar) {
this.defaultEditorRegistrar = registrar;
}
/**
* Override the default editor for the specified type with the given property editor.
* <p>Note that this is different from registering a custom editor in that the editor
@@ -184,6 +200,9 @@ public class PropertyEditorRegistrySupport implements PropertyEditorRegistry {
if (!this.defaultEditorsActive) {
return null;
}
if (this.overriddenDefaultEditors == null && this.defaultEditorRegistrar != null) {
this.defaultEditorRegistrar.registerCustomEditors(this);
}
if (this.overriddenDefaultEditors != null) {
PropertyEditor editor = this.overriddenDefaultEditors.get(requiredType);
if (editor != null) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -124,9 +124,16 @@ public interface BeanFactory {
* beans <i>created</i> by the FactoryBean. For example, if the bean named
* {@code myJndiObject} is a FactoryBean, getting {@code &myJndiObject}
* will return the factory, not the instance returned by the factory.
* @see #FACTORY_BEAN_PREFIX_CHAR
*/
String FACTORY_BEAN_PREFIX = "&";
/**
* Character variant of {@link #FACTORY_BEAN_PREFIX}.
* @since 6.2.6
*/
char FACTORY_BEAN_PREFIX_CHAR = '&';
/**
* Return an instance, which may be shared or independent, of the specified bean.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,10 +39,14 @@ import org.springframework.util.StringUtils;
* (which the methods defined on the ListableBeanFactory interface don't,
* in contrast to the methods defined on the BeanFactory interface).
*
* <p><b>NOTE:</b> It is generally preferable to use {@link ObjectProvider#stream()}
* via {@link BeanFactory#getBeanProvider} instead of this utility class.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Chris Beams
* @since 04.07.2003
* @see BeanFactory#getBeanProvider
*/
public abstract class BeanFactoryUtils {
@@ -68,7 +72,7 @@ public abstract class BeanFactoryUtils {
* @see BeanFactory#FACTORY_BEAN_PREFIX
*/
public static boolean isFactoryDereference(@Nullable String name) {
return (name != null && name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));
return (name != null && !name.isEmpty() && name.charAt(0) == BeanFactory.FACTORY_BEAN_PREFIX_CHAR);
}
/**
@@ -80,14 +84,14 @@ public abstract class BeanFactoryUtils {
*/
public static String transformedBeanName(String name) {
Assert.notNull(name, "'name' must not be null");
if (!name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {
if (name.isEmpty() || name.charAt(0) != BeanFactory.FACTORY_BEAN_PREFIX_CHAR) {
return name;
}
return transformedBeanNameCache.computeIfAbsent(name, beanName -> {
do {
beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());
beanName = beanName.substring(1); // length of '&'
}
while (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));
while (beanName.charAt(0) == BeanFactory.FACTORY_BEAN_PREFIX_CHAR);
return beanName;
});
}
@@ -308,7 +312,7 @@ public abstract class BeanFactoryUtils {
* 'replacing' beans by explicitly choosing the same bean name in a child factory;
* the bean in the ancestor factory won't be visible then, not even for by-type lookups.
* @param lbf the bean factory
* @param type type of bean to match
* @param type the type of bean to match
* @return the Map of matching bean instances, or an empty Map if none
* @throws BeansException if a bean could not be created
* @see ListableBeanFactory#getBeansOfType(Class)
@@ -347,7 +351,7 @@ public abstract class BeanFactoryUtils {
* 'replacing' beans by explicitly choosing the same bean name in a child factory;
* the bean in the ancestor factory won't be visible then, not even for by-type lookups.
* @param lbf the bean factory
* @param type type of bean to match
* @param type the type of bean to match
* @param includeNonSingletons whether to include prototype or scoped beans too
* or just singletons (also applies to FactoryBeans)
* @param allowEagerInit whether to initialize <i>lazy-init singletons</i> and
@@ -395,7 +399,7 @@ public abstract class BeanFactoryUtils {
* 'replacing' beans by explicitly choosing the same bean name in a child factory;
* the bean in the ancestor factory won't be visible then, not even for by-type lookups.
* @param lbf the bean factory
* @param type type of bean to match
* @param type the type of bean to match
* @return the matching bean instance
* @throws NoSuchBeanDefinitionException if no bean of the given type was found
* @throws NoUniqueBeanDefinitionException if more than one bean of the given type was found
@@ -425,7 +429,7 @@ public abstract class BeanFactoryUtils {
* 'replacing' beans by explicitly choosing the same bean name in a child factory;
* the bean in the ancestor factory won't be visible then, not even for by-type lookups.
* @param lbf the bean factory
* @param type type of bean to match
* @param type the type of bean to match
* @param includeNonSingletons whether to include prototype or scoped beans too
* or just singletons (also applies to FactoryBeans)
* @param allowEagerInit whether to initialize <i>lazy-init singletons</i> and
@@ -457,7 +461,7 @@ public abstract class BeanFactoryUtils {
* <p>This version of {@code beanOfType} automatically includes
* prototypes and FactoryBeans.
* @param lbf the bean factory
* @param type type of bean to match
* @param type the type of bean to match
* @return the matching bean instance
* @throws NoSuchBeanDefinitionException if no bean of the given type was found
* @throws NoUniqueBeanDefinitionException if more than one bean of the given type was found
@@ -481,7 +485,7 @@ public abstract class BeanFactoryUtils {
* only raw FactoryBeans will be checked (which doesn't require initialization
* of each FactoryBean).
* @param lbf the bean factory
* @param type type of bean to match
* @param type the type of bean to match
* @param includeNonSingletons whether to include prototype or scoped beans too
* or just singletons (also applies to FactoryBeans)
* @param allowEagerInit whether to initialize <i>lazy-init singletons</i> and
@@ -529,7 +533,7 @@ public abstract class BeanFactoryUtils {
/**
* Extract a unique bean for the given type from the given Map of matching beans.
* @param type type of bean to match
* @param type the type of bean to match
* @param matchingBeans all matching beans found
* @return the unique bean instance
* @throws NoSuchBeanDefinitionException if no bean of the given type was found
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 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.
@@ -145,8 +145,6 @@ public interface ListableBeanFactory extends BeanFactory {
* <p>Does not consider any hierarchy this factory may participate in.
* Use BeanFactoryUtils' {@code beanNamesForTypeIncludingAncestors}
* to include beans in ancestor factories too.
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
* by other means than bean definitions.
* <p>This version of {@code getBeanNamesForType} matches all kinds of beans,
* be it singletons, prototypes, or FactoryBeans. In most implementations, the
* result will be the same as for {@code getBeanNamesForType(type, true, true)}.
@@ -176,8 +174,6 @@ public interface ListableBeanFactory extends BeanFactory {
* <p>Does not consider any hierarchy this factory may participate in.
* Use BeanFactoryUtils' {@code beanNamesForTypeIncludingAncestors}
* to include beans in ancestor factories too.
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
* by other means than bean definitions.
* <p>Bean names returned by this method should always return bean names <i>in the
* order of definition</i> in the backend configuration, as far as possible.
* @param type the generically typed class or interface to match
@@ -210,8 +206,6 @@ public interface ListableBeanFactory extends BeanFactory {
* <p>Does not consider any hierarchy this factory may participate in.
* Use BeanFactoryUtils' {@code beanNamesForTypeIncludingAncestors}
* to include beans in ancestor factories too.
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
* by other means than bean definitions.
* <p>This version of {@code getBeanNamesForType} matches all kinds of beans,
* be it singletons, prototypes, or FactoryBeans. In most implementations, the
* result will be the same as for {@code getBeanNamesForType(type, true, true)}.
@@ -239,8 +233,6 @@ public interface ListableBeanFactory extends BeanFactory {
* <p>Does not consider any hierarchy this factory may participate in.
* Use BeanFactoryUtils' {@code beanNamesForTypeIncludingAncestors}
* to include beans in ancestor factories too.
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
* by other means than bean definitions.
* <p>Bean names returned by this method should always return bean names <i>in the
* order of definition</i> in the backend configuration, as far as possible.
* @param type the class or interface to match, or {@code null} for all bean names
@@ -265,21 +257,24 @@ public interface ListableBeanFactory extends BeanFactory {
* subclasses), judging from either bean definitions or the value of
* {@code getObjectType} in the case of FactoryBeans.
* <p><b>NOTE: This method introspects top-level beans only.</b> It does <i>not</i>
* check nested beans which might match the specified type as well.
* check nested beans which might match the specified type as well. Also, it
* <b>suppresses exceptions for beans that are currently in creation in a circular
* reference scenario:</b> typically, references back to the caller of this method.
* <p>Does consider objects created by FactoryBeans, which means that FactoryBeans
* will get initialized. If the object created by the FactoryBean doesn't match,
* the raw FactoryBean itself will be matched against the type.
* <p>Does not consider any hierarchy this factory may participate in.
* Use BeanFactoryUtils' {@code beansOfTypeIncludingAncestors}
* to include beans in ancestor factories too.
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
* by other means than bean definitions.
* <p>This version of getBeansOfType matches all kinds of beans, be it
* singletons, prototypes, or FactoryBeans. In most implementations, the
* result will be the same as for {@code getBeansOfType(type, true, true)}.
* <p>The Map returned by this method should always return bean names and
* corresponding bean instances <i>in the order of definition</i> in the
* backend configuration, as far as possible.
* <p><b>Consider {@link #getBeanNamesForType(Class)} with selective {@link #getBean}
* calls for specific bean names in preference to this Map-based retrieval method.</b>
* Aside from lazy instantiation benefits, this also avoids any exception suppression.
* @param type the class or interface to match, or {@code null} for all concrete beans
* @return a Map with the matching beans, containing the bean names as
* keys and the corresponding bean instances as values
@@ -295,7 +290,9 @@ public interface ListableBeanFactory extends BeanFactory {
* subclasses), judging from either bean definitions or the value of
* {@code getObjectType} in the case of FactoryBeans.
* <p><b>NOTE: This method introspects top-level beans only.</b> It does <i>not</i>
* check nested beans which might match the specified type as well.
* check nested beans which might match the specified type as well. Also, it
* <b>suppresses exceptions for beans that are currently in creation in a circular
* reference scenario:</b> typically, references back to the caller of this method.
* <p>Does consider objects created by FactoryBeans if the "allowEagerInit" flag is set,
* which means that FactoryBeans will get initialized. If the object created by the
* FactoryBean doesn't match, the raw FactoryBean itself will be matched against the
@@ -304,11 +301,12 @@ public interface ListableBeanFactory extends BeanFactory {
* <p>Does not consider any hierarchy this factory may participate in.
* Use BeanFactoryUtils' {@code beansOfTypeIncludingAncestors}
* to include beans in ancestor factories too.
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
* by other means than bean definitions.
* <p>The Map returned by this method should always return bean names and
* corresponding bean instances <i>in the order of definition</i> in the
* backend configuration, as far as possible.
* <p><b>Consider {@link #getBeanNamesForType(Class)} with selective {@link #getBean}
* calls for specific bean names in preference to this Map-based retrieval method.</b>
* Aside from lazy instantiation benefits, this also avoids any exception suppression.
* @param type the class or interface to match, or {@code null} for all concrete beans
* @param includeNonSingletons whether to include prototype or scoped beans too
* or just singletons (also applies to FactoryBeans)
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.beans.factory;
import java.util.Iterator;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;
@@ -45,6 +46,21 @@ import org.springframework.lang.Nullable;
* Alternatively, you may implement the specific methods that your callers expect,
* for example, just {@link #getObject()} or {@link #getIfAvailable()}.
*
* <p>Note that {@link #getObject()} never returns {@code null} - it will throw a
* {@link NoSuchBeanDefinitionException} instead -, whereas {@link #getIfAvailable()}
* will return {@code null} if no matching bean is present at all. However, both
* methods will throw a {@link NoUniqueBeanDefinitionException} if more than one
* matching bean is found without a clear unique winner (see below). Last but not
* least, {@link #getIfUnique()} will return {@code null} both when no matching bean
* is found and when more than one matching bean is found without a unique winner.
*
* <p>Uniqueness is generally up to the container's candidate resolution algorithm
* but always honors the "primary" flag (with only one of the candidate beans marked
* as primary) and the "fallback" flag (with only one of the candidate beans not
* marked as fallback). The default-candidate flag is consistently taken into
* account as well, even for non-annotation-based injection points, with a single
* default candidate winning in case of no clear primary/fallback indication.
*
* @author Juergen Hoeller
* @since 4.3
* @param <T> the object type
@@ -53,6 +69,18 @@ import org.springframework.lang.Nullable;
*/
public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
/**
* A predicate for unfiltered type matches, including non-default candidates
* but still excluding non-autowire candidates when used on injection points.
* @since 6.2.3
* @see #stream(Predicate)
* @see #orderedStream(Predicate)
* @see org.springframework.beans.factory.config.BeanDefinition#isAutowireCandidate()
* @see org.springframework.beans.factory.support.AbstractBeanDefinition#isDefaultCandidate()
*/
Predicate<Class<?>> UNFILTERED = (clazz -> true);
@Override
default T getObject() throws BeansException {
Iterator<T> it = iterator();
@@ -175,7 +203,7 @@ public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
* if unique (not called otherwise)
* @throws BeansException in case of creation errors
* @since 5.0
* @see #getIfAvailable()
* @see #getIfUnique()
*/
default void ifUnique(Consumer<T> dependencyConsumer) throws BeansException {
T dependency = getIfUnique();
@@ -198,6 +226,10 @@ public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
/**
* Return a sequential {@link Stream} over all matching object instances,
* without specific ordering guarantees (but typically in registration order).
* <p>Note: The result may be filtered by default according to qualifiers on the
* injection point versus target beans and the general autowire candidate status
* of matching beans. For custom filtering against type-matching candidates, use
* {@link #stream(Predicate)} instead (potentially with {@link #UNFILTERED}).
* @since 5.1
* @see #iterator()
* @see #orderedStream()
@@ -219,6 +251,10 @@ public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
* {@link #stream()} method. You may override this to apply an
* {@link org.springframework.core.annotation.AnnotationAwareOrderComparator}
* if necessary.
* <p>Note: The result may be filtered by default according to qualifiers on the
* injection point versus target beans and the general autowire candidate status
* of matching beans. For custom filtering against type-matching candidates, use
* {@link #stream(Predicate)} instead (potentially with {@link #UNFILTERED}).
* @since 5.1
* @see #stream()
* @see org.springframework.core.OrderComparator
@@ -227,4 +263,70 @@ public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
return stream().sorted(OrderComparator.INSTANCE);
}
/**
* Return a custom-filtered {@link Stream} over all matching object instances,
* without specific ordering guarantees (but typically in registration order).
* @param customFilter a custom type filter for selecting beans among the raw
* bean type matches (or {@link #UNFILTERED} for all raw type matches without
* any default filtering)
* @since 6.2.3
* @see #stream()
* @see #orderedStream(Predicate)
*/
default Stream<T> stream(Predicate<Class<?>> customFilter) {
return stream(customFilter, true);
}
/**
* Return a custom-filtered {@link Stream} over all matching object instances,
* pre-ordered according to the factory's common order comparator.
* @param customFilter a custom type filter for selecting beans among the raw
* bean type matches (or {@link #UNFILTERED} for all raw type matches without
* any default filtering)
* @since 6.2.3
* @see #orderedStream()
* @see #stream(Predicate)
*/
default Stream<T> orderedStream(Predicate<Class<?>> customFilter) {
return orderedStream(customFilter, true);
}
/**
* Return a custom-filtered {@link Stream} over all matching object instances,
* without specific ordering guarantees (but typically in registration order).
* @param customFilter a custom type filter for selecting beans among the raw
* bean type matches (or {@link #UNFILTERED} for all raw type matches without
* any default filtering)
* @param includeNonSingletons whether to include prototype or scoped beans too
* or just singletons (also applies to FactoryBeans)
* @since 6.2.5
* @see #stream(Predicate)
* @see #orderedStream(Predicate, boolean)
*/
default Stream<T> stream(Predicate<Class<?>> customFilter, boolean includeNonSingletons) {
if (!includeNonSingletons) {
throw new UnsupportedOperationException("Only supports includeNonSingletons=true by default");
}
return stream().filter(obj -> customFilter.test(obj.getClass()));
}
/**
* Return a custom-filtered {@link Stream} over all matching object instances,
* pre-ordered according to the factory's common order comparator.
* @param customFilter a custom type filter for selecting beans among the raw
* bean type matches (or {@link #UNFILTERED} for all raw type matches without
* any default filtering)
* @param includeNonSingletons whether to include prototype or scoped beans too
* or just singletons (also applies to FactoryBeans)
* @since 6.2.5
* @see #orderedStream()
* @see #stream(Predicate)
*/
default Stream<T> orderedStream(Predicate<Class<?>> customFilter, boolean includeNonSingletons) {
if (!includeNonSingletons) {
throw new UnsupportedOperationException("Only supports includeNonSingletons=true by default");
}
return orderedStream().filter(obj -> customFilter.test(obj.getClass()));
}
}

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