Compare commits

...

149 Commits

Author SHA1 Message Date
Stéphane Nicoll 9b59345547 Release v6.1.11 2024-07-11 09:40:59 +02:00
rstoyanchev 611d3e5551 Correct type names in docs on Validation in WebFlux
Closes gh-33061
2024-07-10 17:55:19 +01:00
Sam Brannen fa2a58b9db Ensure varargs component type for MethodHandle is not null in SpEL
This commit ensures that the varargs component type for a MethodHandle
cannot be null in ReflectionHelper's
convertAllMethodHandleArguments(...) method in SpEL.

Closes gh-33193
2024-07-10 16:46:47 +02:00
Sam Brannen 83ca2c0cff Support MethodHandle function invocation with varargs array in SpEL
Prior to this commit, the Spring Expression Language (SpEL) could not
invoke a varargs MethodHandle function with an array containing the
variable arguments, although that is supported for a varargs Method
function. Attempting to do so resulted in the array being supplied as a
single argument to the MethodHandle.

This commit addresses this by updating the
executeFunctionViaMethodHandle(...) method in FunctionReference as
follows when the user supplies the varargs already packaged in an array.

- Creates a new array large enough to hold the non-varargs arguments
  and the unpackaged varargs arguments.

- Adds the non-varargs arguments to the beginning of that array and
  adds the unpackaged varargs arguments to the end of that array.

- Invokes the MethodHandle with the new arguments array.

Closes gh-33191
2024-07-10 16:16:41 +02:00
Sam Brannen dc16f3cffb Polish SpEL function invocation support 2024-07-10 16:16:41 +02:00
Juergen Hoeller f2b3263fff Polishing 2024-07-10 15:56:56 +02:00
Simon Baslé 1d890a8952 Make coroutines with custom AOP aspects work with @Transactional
Previous to this change, the transactional aspect would supersed the
user-defined AspectJ aspect, shortcircuiting to calling the original
Kotlin suspending function.

This change simplifies the TransactionAspectSupport way of dealing with
transactional coroutines, thanks to the fact that lower level support
for AOP has been introduced in c8169e5c.

Closes gh-33095
2024-07-10 15:35:43 +02:00
Juergen Hoeller 3ccaefe38f Polishing 2024-07-10 15:15:32 +02:00
Juergen Hoeller 300f4585be Update spring.jdbc.getParameterType.ignore note for 6.1.2
See gh-25679
2024-07-10 15:15:26 +02:00
Sam Brannen a0f5c16627 Support MethodHandle function invocation with zero varargs in SpEL
Prior to this commit, the Spring Expression Language (SpEL) could not
invoke a varargs MethodHandle function with zero variable arguments,
even though the variable arguments are not required. Attempting to do
so resulted in a SpelEvaluationException with an
INCORRECT_NUMBER_OF_ARGUMENTS_TO_FUNCTION message.

This commit addresses this by updating the
executeFunctionViaMethodHandle(...) method in FunctionReference so that
it properly checks the required number of arguments for both varargs
and non-varargs MethodHandle invocations.

This commit also improves the error message for varargs invocations
with too few arguments. For example, if the MethodHandle requires at
least 1 argument plus a variable number of additional arguments and 0
arguments were supplied, the error message now states:

"Incorrect number of arguments for function 'myFunc': 0 supplied but function takes 1 or more"

Instead of:

"Incorrect number of arguments for function 'myFunc': 0 supplied but function takes 2"

Closes gh-33190
2024-07-10 13:39:48 +02:00
Stéphane Nicoll b5a86dec92 Retain previous factory method in case of nested invocation with AOT
This commit harmonizes the invocation of a bean supplier with what
SimpleInstantiationStrategy does. Previously, the current factory method
was set to `null` once the invocation completes. This did not take
into account recursive scenarios where an instance supplier triggers
another instance supplier.

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

Closes gh-33180
2024-07-10 12:38:56 +02:00
Sam Brannen c55c64427c Support single String argument for varargs invocations in SpEL
Prior to this commit, the Spring Expression Language (SpEL) incorrectly
split single String arguments by comma for Object... varargs method and
constructor invocations.

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

Closes gh-33013
2024-07-10 11:51:55 +02:00
Sam Brannen a4fcd301f2 Polish SpEL's ReflectionHelper 2024-07-10 11:19:36 +02:00
Stéphane Nicoll ec4558dc14 Polish 2024-07-10 10:19:50 +02:00
Stéphane Nicoll d6c623be40 Release from GHA
This commit adds a workflow to release the Spring Framework from GitHub
Actions.

Closes gh-33179
2024-07-10 10:11:32 +02:00
Stéphane Nicoll 86da45ba15 Upgrade to Reactor 2023.0.8
Closes gh-33169
2024-07-09 15:52:09 +02:00
Stéphane Nicoll 6a60298024 Upgrade to Micrometer 1.12.8
Closes gh-33168
2024-07-09 14:39:10 +02:00
Sébastien Deleuze f8b448e949 Upgrade to Kotlin Serialization 1.6.3
Closes gh-33175
2024-07-09 14:31:56 +02:00
Sébastien Deleuze 52a0e6f900 Upgrade to Kotlin 1.9.24
Closes gh-33177
2024-07-09 14:31:47 +02:00
Stéphane Nicoll 3d2fb3af3d Add verification tests
In preparation for releasing Spring Framework from GitHub actions, this
commit adds a `verify` workflow that validates that the released bits
are correct by running sample projects against them.

Closes gh-33178
2024-07-09 14:23:44 +02:00
Sébastien Deleuze 4f38079042 Throw proper DecodingException in Kotlin Serialization decoders
Closes gh-33138
2024-07-09 12:05:24 +02:00
rstoyanchev 3008d97f93 Polishing contribution
Closes gh-33150
2024-07-09 06:53:14 +01:00
Sébastien NUSSBAUMER f0c758498b Allow validation of Set method parameters
See gh-33150
2024-07-09 06:53:14 +01:00
Juergen Hoeller bc2deef951 Upgrade to JRuby 9.4.8, Rhino 1.7.15, Moneta 1.4.4, JSONassert 1.5.3 2024-07-08 22:55:54 +02:00
Juergen Hoeller 7322a0d3c5 Upgrade to Groovy 4.0.22, Tomcat 10.1.25, Jetty 12.0.11, Undertow 2.3.14 2024-07-08 19:43:19 +02:00
Juergen Hoeller 7227c30917 Allow tests to pass on Windows JVM (non-UTF-8 default encoding)
See gh-33071
2024-07-08 19:42:28 +02:00
Stéphane Nicoll 7e7f55cb23 Merge pull request #33166 from hunhee98
* pr/33166:
  Polish contribution
  Harmonize phrasing in URI Encoding section

Closes gh-33166
2024-07-08 16:51:51 +02:00
Stéphane Nicoll 84f20cd0a7 Polish contribution
See gh-33166
2024-07-08 16:51:23 +02:00
Hunhee Lee 3d8f1fb00f Harmonize phrasing in URI Encoding section
See gh-33166
2024-07-08 16:51:09 +02:00
Stéphane Nicoll e06115c06f Merge pull request #33156 from
* pr/33156:
  Polish "Implement DefaultErrorResponseBuilder#headers(Consumer)"
  Implement DefaultErrorResponseBuilder#headers(Consumer)

Closes gh-33156
2024-07-08 13:15:29 +02:00
Stéphane Nicoll 69850cad27 Polish "Implement DefaultErrorResponseBuilder#headers(Consumer)"
See gh-33156
2024-07-08 13:11:31 +02:00
Harry Yang bbbc95f773 Implement DefaultErrorResponseBuilder#headers(Consumer)
See gh-33156
2024-07-08 13:09:26 +02:00
Sam Brannen 83f7996c79 Polishing 2024-07-05 19:00:39 +02:00
Stéphane Nicoll 0544dfe090 Enable building against Java 23-ea
This commit updates the CI workflow to build against Java 23-ea as
well, using the temurin distribution. For consistency, we're also
building against Java 22.

Closes gh-32090
2024-07-05 10:00:17 +02:00
Stéphane Nicoll eacfd77d6a Harmonize CI configuration
This commit harmonizes our CI configuration with Spring Boot, in
particular the clever use of reusable custom actions that simplify
the workflow definition quite a bit.

One main difference compared to Spring Boot is that we can now
specify a different distribution for a Java version to test, in
preparation for the support of building against 23-ea

See gh-32090
2024-07-05 09:46:45 +02:00
Sam Brannen 89338c91a9 Stop using using legacy locale data for Date/Time formatting tests
Commit 84714fbae9 introduced usage of the
-Djava.locale.providers=COMPAT command-line argument for javac in order
to allow our JDK 20 builds to pass by using legacy locale data.

That was done to ensure that Date/Time formats using AM/PM produced a
standard space (" ") before the "AM" or "PM" instead of a narrow
non-breaking space (NNBSP "\u202F"), which was introduced in Java 20
due to adoption of Unicode Common Locale Data Repository (CLDR-14032).

This commit removes usage of the -Djava.locale.providers=COMPAT
command-line argument and updates all affected tests to:

- Use an NNBSP before "AM" or "PM" in input text when running on Java 20
  or higher.

- Leniently match against any Unicode space character in formatted
  values containing "AM" or "PM".

See https://jdk.java.net/20/release-notes#JDK-8284840
See https://unicode-org.atlassian.net/browse/CLDR-14032
See gh-30185
Closes gh-33144
2024-07-04 17:03:15 +02:00
Stéphane Nicoll 51641ece72 Polish
See gh-33127
2024-07-04 16:51:10 +02:00
Simon Baslé 06d267f04e Improve WebClientResponseException message in case of 1xx/2xx/3xx status
When a response fails to be completely emitted by the remote (connection
termination during the transmission of the response for example), a
WebClientResponseException can be propagated with a confusing message
which mainly reflects the status code and reason phrase, leading to
messages like "200 OK" in such an exception.

This change improves the situation by appending a hint at the underlying
cause whenever getMessage() is called on a WebClientResponseException
which was created with a non-error status code.

Closes gh-33127
2024-07-04 15:20:46 +02:00
Stéphane Nicoll dfa6b4bd42 Upgrade to Gradle 8.8
Closes gh-33146
2024-07-04 10:23:15 +02:00
Stéphane Nicoll 099a97740e Downgrade Awaitility to 4.2.0
This is required to build against ea builds

Closes gh-33143
2024-07-04 04:20:43 +02:00
Juergen Hoeller daea3f0eae Apply fallback resolution for non-hierarchical URIs such as "file:."
Includes meaningful exception message for file system resolution.

Closes gh-33124
2024-07-03 16:36:18 +02:00
zizare 8974da2a5a Use error handler for reactive cache aspect
This change ensures that the cache error handler is used in case of
future-based or publisher-based asynchronous caching completing with an
exception.

Closes gh-33073
2024-07-02 11:29:53 +02:00
Brian Clozel ab236c7741 Re-enable async dispatches in Observation Filter
Prior to this commit, the fix for gh-32730 disabled the involvment of
the osbervation filter for async dispatches. Instead of relying on ASYNC
dispatches to close the observation for async requests, this is now
using an async listener instead: async dispatches are not guaranteed to
happen once the async request is handled.

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

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

Fixes gh-33091
2024-07-01 11:42:48 +02:00
Juergen Hoeller 61adf2dd25 Formal null safety for exception message through String.valueOf
See gh-33117
2024-06-28 18:09:39 +02:00
Juergen Hoeller 61894af0bd Expose FactoryBean attribute exception as BeanDefinitionStoreException
Closes gh-33117
2024-06-28 17:55:45 +02:00
rstoyanchev c74666a883 Polishing contribution
Closes gh-33105
2024-06-28 15:32:43 +01:00
vatsal 976b4f3533 Fix return value validation
Fix argument in call to applyReturnValueValidation()
method in MethodValidationInterceptor.java. Method
argument was passed instead of the return value of the
method that was being validated.

See gh-33105
2024-06-28 15:00:11 +01:00
Juergen Hoeller 100da83913 Detect ajc markers in superclasses as well (for weaving check)
Closes gh-33113
2024-06-28 11:36:17 +02:00
Sam Brannen e881c70a93 Upgrade to JUnit 5.10.3 2024-06-27 17:00:43 +02:00
Juergen Hoeller fea237c065 Lazily start and retain HttpClient once resource factory is running
Closes gh-33093
2024-06-27 12:03:10 +02:00
Sam Brannen 8b11ee9ee2 Document that ModelMap is not a supported argument type in WebFlux
Prior to this commit, the "Method Arguments" documentation for WebFlux
in the reference manual stated that WebFlux controller methods can
accept arguments of type Map, Model, or ModelMap to access the model.
However, ModelMap is actually not supported and results in exception
due to a type mismatch.

This commit updates the documentation to reflect this.

In addition, this commit updates related Javadoc and tests to avoid
mentioning or using ModelMap in WebFlux.

Closes gh-33107
2024-06-27 11:33:50 +02:00
Sam Brannen 1cf5264163 Polishing 2024-06-27 10:46:00 +02:00
Sam Brannen c68c6faa03 Fix comment in Method Injection example in reference manual
Prior to this commit, the comment in the XML configuration example in
the Method Injection section of the reference manual referred to the
wrong bean names.

Closes gh-33096
2024-06-26 16:45:06 +02:00
Brian Clozel 6dd5c85ed0 Support byte array payloads in ProtobufMessageConverter
Prior to this commit, the `ProtobufMessageConverter` used in messaging
would try and serialize the message payload by calling "toString()" on
it in order to pass it to the Protobuf JSON encoder.
While this works for `String` payloads, this fails for `byte[]` types.

This commit ensures that such `byte[]` are first converted to `String`
instances using the given charset first.

Fixes gh-27408
2024-06-25 21:16:50 +02:00
Sam Brannen d133ab60ee Improve documentation regarding encoding in FreeMarker support
This commit also introduces integration tests to test the status quo
regarding encoding.

Closes gh-33071
2024-06-25 16:56:48 +02:00
Sam Brannen 5d6e143ff4 Remove invalid configuration in RequestMappingViewResolutionIntegrationTests
Prior to this commit, RequestMappingViewResolutionIntegrationTests
invoked the following:

configurer.setTemplateLoaderPath(
	"classpath*:org/springframework/web/reactive/view/freemarker/");

However, that configuration is invalid since `classpath*:` is not
supported for a `templateLoaderPath`.

Despite that, the tests still passed since FreeMarkerConfigurer already
registers a new ClassTemplateLoader(FreeMarkerConfigurer.class, ""),
which automatically finds template files in the same package as
FreeMarkerConfigurer (for the "spring.ftl" macro library support) and
coincidentally RequestMappingViewResolutionIntegrationTests as well
(which resides in the same package).

This commit therefore removes the invalid configuration and adds a
comment to explain what's going on.
2024-06-25 16:56:21 +02:00
Juergen Hoeller 4e2fb308f6 Document contentLength() behavior for InputStreamResource and custom subclasses
Closes gh-33089
2024-06-24 12:10:35 +02:00
Sam Brannen 5f765fc8ce Polishing 2024-06-23 13:22:08 +02:00
Sam Brannen 6b456b6157 Upgrade to FreeMarker 2.3.33 2024-06-22 16:55:56 +02:00
Sam Brannen 000b563e83 Use consistent formatting for view name in AbstractView 2024-06-22 16:51:37 +02:00
Sam Brannen c571ee1f95 Fix typo in comment 2024-06-22 16:51:28 +02:00
Stéphane Nicoll 899f6308d9 Fix name of GitHub actions bot
See gh-gh-33076
2024-06-21 14:14:05 +02:00
Juergen Hoeller a580d6d6fc Leniently ignore type mismatch for LoadTimeWeaverAware beans
Closes gh-33082
2024-06-21 11:22:48 +02:00
Juergen Hoeller 66eddf99af Include original exception if cause is null
Closes gh-33080
See gh-32952
2024-06-20 18:56:43 +02:00
Stéphane Nicoll e94ec80df4 Exclude GitHub Actions bot from changelog
Closes gh-33076
2024-06-20 14:15:54 +02:00
Juergen Hoeller 2861e570fd Catch and log LinkageError in getTypeForFactoryMethod
Closes gh-33075
2024-06-20 13:47:43 +02:00
Spring Builds ee7a1e8b7e Next development version (v6.1.11-SNAPSHOT) 2024-06-19 15:37:10 +00:00
Brian Clozel f7307c9e07 Avoid recording RestClient observations twice
Prior to this commit, the fix for gh-32575 introduced cases where the
client observation would be stopped twice.

This commit ensures that `RestClient` observations are stopped only once
when the response is closed, or before throwing an unhanlded exception.

Fixes gh-33068
2024-06-19 12:46:21 +02:00
Brian Clozel 098c4b1dd7 Use Sonatype S01 token in release pipeline 2024-06-18 19:25:20 +02:00
Juergen Hoeller 65dbfd09b4 Defensive PersistenceExceptionTranslator bean retrieval on shutdown
Closes gh-33067
2024-06-18 18:31:15 +02:00
Sam Brannen 203fa75196 Support all "connection reset" phrases in DisconnectedClientHelper
Prior to this commit, the isClientDisconnectedException() method in
DisconnectedClientHelper checked whether the message of the ultimate
exception in an exception chain contained one of the phrases "broken
pipe" or "connection reset by peer". However, that failed to match if
the exception message contained "Connection reset", which is the case
for the SocketException thrown by throwConnectionReset() in
sun.nio.ch.SocketChannelImpl.

This commit therefore replaces the "connection reset by peer" phrase
with "connection reset" in order to support all exception messages
containing "connection reset".

Closes gh-33064
2024-06-18 16:43:06 +02:00
Juergen Hoeller 9a56a8877f Polishing 2024-06-17 18:42:37 +02:00
Juergen Hoeller e79a9a5bff Correct and consistent event class names in constructor javadoc
Closes gh-33032
2024-06-17 18:42:30 +02:00
Juergen Hoeller 2c3c3831c1 Consistently ignore bridge method on generated subclass for visibility purposes
Closes gh-33030
2024-06-17 18:42:20 +02:00
Arjen Poutsma c38e9896c7 Remove use of ServletException in ModelFactory
This commit changes the use of HttpSessionRequiredException in
ModelFactory::initModel to an IllegalStateException, because the former
extends ServletException and cannot be used in WebFlux.

Closes gh-33043
2024-06-17 14:19:19 +02:00
Stéphane Nicoll 3e0849a566 Fix typo
Closes gh-33050
2024-06-17 13:39:18 +02:00
github-actions[bot] f3a605b92c Update Antora Spring UI to v0.4.16 2024-06-17 10:06:47 +00:00
Stéphane Nicoll 6fdff201e9 Fix property name in Container Extension Points section
Closes gh-33037
2024-06-16 16:47:36 +02:00
Stéphane Nicoll c42778a05b Merge pull request #33036 from tafjwr
* pr/33036:
  Polish "Fix typo in comment"
  Fix typo in comment

Closes gh-33036
2024-06-16 09:59:15 +02:00
Stéphane Nicoll f140df881d Polish "Fix typo in comment"
See gh-33036
2024-06-16 09:57:24 +02:00
tafjwr 12cf654c98 Fix typo in comment
See gh-33036
2024-06-16 09:56:58 +02:00
Juergen Hoeller 089e4e69f1 Do not attempt to load pre-enhanced class for reloadable classes
Closes gh-33024
2024-06-14 22:07:46 +02:00
Stéphane Nicoll 77755ff2fa Remove repo-specific code of conduct
This will allow us to inherit the org's CoC instead.

Closes gh-33031
2024-06-14 18:58:49 +02:00
Stéphane Nicoll d0aa7ad524 Fix invalid character in Javadoc of BeanFactory 2024-06-13 14:06:19 +02:00
Spring Builds 072fc80cdb Next development version (v6.1.10-SNAPSHOT) 2024-06-13 10:27:57 +00:00
Arjen Poutsma 6f32ff489a Use null stream in ReactorNettyClientResponse if no body is available
Closes gh-32805
2024-06-13 11:10:50 +02:00
Juergen Hoeller 24c8dfea1f Remove duplicated javadoc paragraph 2024-06-12 14:23:26 +02:00
Juergen Hoeller cdfe5816c8 Upgrade to Netty 4.1.111 and Awaitility 4.2.1 2024-06-12 14:23:14 +02:00
Juergen Hoeller 0ff200b2f1 Trigger cancellation on context close for non-managed objects only
Specifically for prototype/scoped beans and FactoryBean-exposed objects.

Closes gh-33009
2024-06-12 13:31:00 +02:00
Stéphane Nicoll 261dac87cc Upgrade to Reactor 2023.0.7
Closes gh-33007
2024-06-11 14:42:08 +02:00
Juergen Hoeller fce2f49e46 Polishing (aligned with main) 2024-06-11 09:07:09 +02:00
Juergen Hoeller eca2b9657e Documentation notes for @Bean overriding and bean name matching
Closes gh-32825
2024-06-11 09:06:33 +02:00
Juergen Hoeller e48f37d956 Upgrade to Micrometer 1.12.7
Closes gh-33001
2024-06-11 09:05:48 +02:00
Juergen Hoeller ddc397dd05 Upgrade to spring-javaformat-checkstyle 0.0.42 2024-06-11 09:04:30 +02:00
Stéphane Nicoll 6b7f0bd4b6 Fix typo 2024-06-11 06:47:42 +02:00
Sébastien Deleuze c97a895f09 Add support for double backslashes to StringUtils#cleanPath
Closes gh-32962
2024-06-10 22:25:31 +02:00
rstoyanchev 3b13f2ed38 Update code-of-conduct email 2024-06-10 10:48:06 +01:00
Stéphane Nicoll a0eebca0cf Merge pull request #32993 from hlmg
* pr/32993:
  Fix typo

Closes gh-32993
2024-06-10 10:02:36 +02:00
hlmg c6c64e6fe7 Fix typo
See gh-32993
2024-06-10 10:00:37 +02:00
Brian Clozel 0ca393c0dc Restrict memory allocation in ContentCachingRequestWrapper
Prior to this commit, the `ContentCachingRequestWrapper` could allocate
a `FastByteArrayOutputStream` block that was larger than the content
cache limit given as a consturctor argument. This was due to an
optimization applied in gh-31834 for allocating the right content cache
size when the request size is known.

Fixes gh-32987
2024-06-10 09:46:01 +02:00
Brian Clozel 6681394886 Stop observations for async requests in Servlet filter
Prior to this commit, the `ServerHttpObservationFilter` would support
async dispatches and would do the following:

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

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

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

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

Fixes gh-32730
2024-06-07 19:01:57 +02:00
Sébastien Deleuze 172987c874 Ignore checkpointOnRefresh after restore
Closes gh-32978
2024-06-07 18:41:37 +02:00
Thomas Deblock 47a5ebfde6 Support canEncode() for JAXBElement in Jaxb2XmlEncoder
Commit d7970e4ab8 introduced support for JAXBElement in
Jaxb2XmlEncoder's encodeValue() method; however, canEncode() still
returned false for a JAXBElement element type.

This commit revises canEncode() so that it returns true for an element
type that is assignable from JAXBElement.

See gh-30552
See gh-32972
Closes gh-32977
2024-06-07 14:13:59 +02:00
Sébastien Deleuze 43a113f067 Polishing
See gh-32983
2024-06-07 13:45:09 +02:00
Sébastien Deleuze dc250e1cc1 Remove outdated copyright from index.adoc
Closes gh-32983
2024-06-07 13:35:21 +02:00
Sébastien Deleuze fe74fcfded Exclude node_modules from NoHttp checks
Closes gh-32980
2024-06-07 11:43:45 +02:00
Juergen Hoeller 2451bd62b0 Polishing 2024-06-06 20:43:31 +02:00
Juergen Hoeller 624d6dd167 Expose actual result value for @CacheEvict condition
Closes gh-32960
2024-06-06 20:43:04 +02:00
Juergen Hoeller 0ea96b4806 Skip ajc-compiled aspects for ajc-compiled target classes
Includes defensive ignoring of incompatible aspect types.

Closes gh-32970
2024-06-06 20:42:07 +02:00
Sébastien Deleuze c28a0d5627 Add missing hints for Hibernate @TenantId
Closes gh-32967
2024-06-06 10:04:35 +02:00
Juergen Hoeller 61d045ce52 Polishing 2024-06-06 08:54:37 +02:00
Juergen Hoeller c0bef2c693 Lazily start resources on demand (if necessary outside of lifecycle)
See gh-32945
2024-06-06 08:54:32 +02:00
Stéphane Nicoll c3a0eaa95e Merge pull request #32966 from ypyf
* pr/32966:
  Use HttpStatusCode consistently in reference guide

Closes gh-32966
2024-06-06 08:08:45 +02:00
ypyf e12d1259d1 Use HttpStatusCode consistently in reference guide
See gh-32966
2024-06-06 08:06:33 +02:00
Brian Clozel 404c4d9d92 Support @Valid on container elements for handler arguments
Prior to this commit, #31870 added support for constraint annotations on
container elements for handler method argument validation. Supporting
this use case:

```
public void addNames(List<@NotEmpty String> names)
```

This commit does the same for `@Valid` annotation:

```
public void addPeople(List<@Valid Person> people)
```

Fixes gh-32964
2024-06-05 20:02:46 +02:00
Juergen Hoeller 7785f94c4c Revise and align Reactor client lifecycle management
Closes gh-32945
2024-06-05 16:32:40 +02:00
Juergen Hoeller 4f6f2c0d41 Revert to separate get/put steps against method cache for concurrency
Closes gh-32958
2024-06-05 16:32:27 +02:00
Stéphane Nicoll e6da2a86fc Merge pull request #32957 from soglad
* pr/32957:
  Fix entity name in MappingSqlQuery example of reference guide

Closes gh-32957
2024-06-05 10:45:01 +02:00
Tony Wen 301087e510 Fix entity name in MappingSqlQuery example of reference guide
See gh-32957
2024-06-05 10:43:14 +02:00
Juergen Hoeller 6c054f88ea Defensively handle UncheckedIOException cause (for NullAway compliance) 2024-06-05 00:01:52 +02:00
Juergen Hoeller f58c7d80cc Upgrade to SLF4J 2.0.13, Jetty 12.0.10, Netty 4.1.110, JRuby 9.4.7 2024-06-04 23:44:54 +02:00
Juergen Hoeller e5be10d53d Consistently throw IOException from ReactorNettyClientResponse
Aligned with ReactorNettyClientRequest.

See gh-32952
2024-06-04 23:43:10 +02:00
Juergen Hoeller 524da905db Consistently throw IOException from ReactorNettyClientRequest
This commit renames ReactorNettyClientRequestFactoryTests.

Closes gh-32952
2024-06-04 22:59:29 +02:00
Juergen Hoeller 4323c60513 Common context lifecycle management for ReactorResourceFactory
This commit moves ReactorResourceFactoryTests to same package.

Closes gh-32945
2024-06-04 22:59:18 +02:00
Sébastien Deleuze f6b608eecb Consistently support Hibernate annotation hint inference on methods
See gh-32842
2024-06-04 16:46:25 +02:00
Sébastien Deleuze 4da1511ed3 Infer hints for Hibernate generators
This commit updates
PersistenceManagedTypesBeanRegistrationAotProcessor
in order to infer hints for Hibernate annotations meta
annotated with `@ValueGenerationType` (like `@CreationTimestamp`)
and `@IdGeneratorType`.

`@GenericGenerator` is not supported as it is deprecated as of
Hibernate 6.5.

Closes gh-32842
2024-06-04 16:46:25 +02:00
Stéphane Nicoll 7102c33661 Add section about using complex data structures with AOT
Closes gh-32273
2024-06-04 15:51:40 +02:00
Sébastien Deleuze 43409b00d0 Refine KotlinDetector.isKotlinType documentation
This commit documents changes in lambda detection
as of Kotlin 2.0.

Closes gh-32905
2024-06-03 18:58:13 +02:00
Sébastien Deleuze d55abc6cf9 Fix RegisterReflectionForBinding Javadoc
Closes gh-32947
2024-06-03 18:26:24 +02:00
Sébastien Deleuze 31806f3a6b Fix MethodValidationPostProcessor refdoc
Closes gh-32929
2024-06-03 18:20:52 +02:00
Juergen Hoeller 8a84241c1e Polishing 2024-06-03 12:46:31 +02:00
Juergen Hoeller 624be6d4e6 Report bean creation failure in sortAdvisors as AopConfigException
Closes gh-32230
2024-06-03 12:46:14 +02:00
Juergen Hoeller b08883b65c Avoid NoSuchMethodException for annotation attribute checks
Closes gh-32921
2024-06-03 12:45:11 +02:00
Cong-Xin Cynthia Qiu 542ba3517f Fix typo in Jakarta validation documentation
Closes gh-32928
2024-05-31 12:22:02 +02:00
Juergen Hoeller 8c6a7799be Upgrade to Checkstyle 10.17 2024-05-28 18:46:22 +02:00
Juergen Hoeller 557dbba585 Remove superfluous addToClassHierarchy call for Enum types
Closes gh-32906
2024-05-28 18:45:05 +02:00
Sam Brannen e9de426eb5 Support compilation of map indexing with primitive in SpEL
Prior to this commit, the Spring Expression Language (SpEL) failed to
compile an expression that indexed into a Map using a primitive literal
(boolean, int, long, float, or double).

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

Closes gh-32903
2024-05-28 10:19:15 +02:00
Sam Brannen cda577d1aa Support compilation of array and list indexing with Integer in SpEL
Prior to this commit, the Spring Expression Language (SpEL) failed to
compile an expression that indexed into any array or list using an
Integer.

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

See gh-32694
Closes gh-32908
2024-05-27 17:06:48 +02:00
Sam Brannen 8feb842df5 Upgrade to AssertJ 3.26.0
See https://github.com/assertj/assertj/issues/3322
2024-05-27 16:43:22 +02:00
Sam Brannen ea2931f24a Use Develocity Gradle plugin API to avoid deprecation warning
Prior to this commit, the Gradle build issued the following warning.

- The deprecated "gradleEnterprise.buildScan.value" API has been
  replaced by "develocity.buildScan.value"
2024-05-24 15:41:58 +02:00
Juergen Hoeller 26d1c38d84 Polishing 2024-05-24 13:05:49 +02:00
Juergen Hoeller 345daaabbc Detect original generic method for CGLIB bridge method
Closes gh-32888
2024-05-24 11:49:10 +02:00
Juergen Hoeller 6c08d93992 Polishing 2024-05-23 17:07:55 +02:00
Juergen Hoeller 6d7cd9c7dc Defensive handling of incompatible advice methods
This covers AspectJ transaction and caching aspects when encountered by Spring AOP.

Closes gh-32882
See gh-32793
2024-05-23 17:07:51 +02:00
Attacktive 73eb6f0660 Complete a Kotlin code snippet in the refdoc
Closes gh-32877
2024-05-23 16:03:34 +02:00
Stéphane Nicoll 31f298b929 Merge pull request #32874 from Seungpang
* pr/32874:
  Polish
  Document using ThreadLocal#remove instead of ThreadLocal#set(null)

Closes gh-32874
2024-05-23 08:14:00 +02:00
Stéphane Nicoll c01aab5850 Polish
See gh-32874
2024-05-23 08:12:30 +02:00
Seungrae 61ef5a8930 Document using ThreadLocal#remove instead of ThreadLocal#set(null)
See gh-32874
2024-05-23 08:10:54 +02:00
Spring Builds 3b53ee7038 Next development version (v6.1.9-SNAPSHOT) 2024-05-22 16:45:49 +00:00
191 changed files with 3776 additions and 1356 deletions
+56
View File
@@ -0,0 +1,56 @@
name: 'Build'
description: 'Builds the project, optionally publishing it to a local deployment repository'
inputs:
java-version:
required: false
default: '17'
description: 'The Java version to compile and test with'
java-distribution:
required: false
default: 'liberica'
description: 'The Java distribution to use for the build'
java-toolchain:
required: false
default: 'false'
description: 'Whether a Java toolchain should be used'
publish:
required: false
default: 'false'
description: 'Whether to publish artifacts ready for deployment to Artifactory'
develocity-access-key:
required: false
description: 'The access key for authentication with ge.spring.io'
outputs:
build-scan-url:
description: 'The URL, if any, of the build scan produced by the build'
value: ${{ (inputs.publish == 'true' && steps.publish.outputs.build-scan-url) || steps.build.outputs.build-scan-url }}
version:
description: 'The version that was built'
value: ${{ steps.read-version.outputs.version }}
runs:
using: composite
steps:
- name: Prepare Gradle Build
uses: ./.github/actions/prepare-gradle-build
with:
develocity-access-key: ${{ inputs.develocity-access-key }}
java-version: ${{ inputs.java-version }}
java-distribution: ${{ inputs.java-distribution }}
java-toolchain: ${{ inputs.java-toolchain }}
- name: Build
id: build
if: ${{ inputs.publish == 'false' }}
shell: bash
run: ./gradlew check antora
- name: Publish
id: publish
if: ${{ inputs.publish == 'true' }}
shell: bash
run: ./gradlew -PdeploymentRepository=$(pwd)/deployment-repository build publishAllPublicationsToDeploymentRepository
- name: Read Version From gradle.properties
id: read-version
shell: bash
run: |
version=$(sed -n 's/version=\(.*\)/\1/p' gradle.properties)
echo "Version is $version"
echo "version=$version" >> $GITHUB_OUTPUT
@@ -0,0 +1,23 @@
name: Create GitHub Release
description: Create the release on GitHub with a changelog
inputs:
milestone:
description: 'Name of the GitHub milestone for which a release will be created'
required: true
token:
description: 'Token to use for authentication with GitHub'
required: true
runs:
using: composite
steps:
- name: Generate Changelog
uses: spring-io/github-changelog-generator@185319ad7eaa75b0e8e72e4b6db19c8b2cb8c4c1 #v0.0.11
with:
milestone: ${{ inputs.milestone }}
token: ${{ inputs.token }}
config-file: .github/actions/create-github-release/changelog-generator.yml
- name: Create GitHub Release
env:
GITHUB_TOKEN: ${{ inputs.token }}
shell: bash
run: gh release create ${{ format('v{0}', inputs.milestone) }} --notes-file changelog.md
@@ -0,0 +1,28 @@
changelog:
repository: spring-projects/spring-framework
sections:
- title: ":star: New Features"
labels:
- "type: enhancement"
- title: ":lady_beetle: Bug Fixes"
labels:
- "type: bug"
- "type: regression"
- title: ":notebook_with_decorative_cover: Documentation"
labels:
- "type: documentation"
- title: ":hammer: Dependency Upgrades"
sort: "title"
labels:
- "type: dependency-upgrade"
contributors:
exclude:
names:
- "bclozel"
- "jhoeller"
- "poutsma"
- "rstoyanchev"
- "sbrannen"
- "sdeleuze"
- "simonbasle"
- "snicoll"
@@ -0,0 +1,49 @@
name: 'Prepare Gradle Build'
description: 'Prepares a Gradle build. Sets up Java and Gradle and configures Gradle properties'
inputs:
java-version:
required: false
default: '17'
description: 'The Java version to use for the build'
java-distribution:
required: false
default: 'liberica'
description: 'The Java distribution to use for the build'
java-toolchain:
required: false
default: 'false'
description: 'Whether a Java toolchain should be used'
develocity-access-key:
required: false
description: 'The access key for authentication with ge.spring.io'
runs:
using: composite
steps:
- name: Set Up Java
uses: actions/setup-java@v4
with:
distribution: ${{ inputs.java-distribution }}
java-version: |
${{ inputs.java-version }}
${{ inputs.java-toolchain == 'true' && '17' || '' }}
- name: Set Up Gradle
uses: gradle/actions/setup-gradle@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2
with:
cache-read-only: false
develocity-access-key: ${{ inputs.develocity-access-key }}
- name: Configure Gradle Properties
shell: bash
run: |
mkdir -p $HOME/.gradle
echo 'systemProp.user.name=spring-builds+github' >> $HOME/.gradle/gradle.properties
echo 'systemProp.org.gradle.internal.launcher.welcomeMessageEnabled=false' >> $HOME/.gradle/gradle.properties
echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties
echo 'org.gradle.daemon=4' >> $HOME/.gradle/gradle.properties
- name: Configure Toolchain Properties
if: ${{ inputs.java-toolchain == 'true' }}
shell: bash
run: |
echo toolchainVersion=${{ inputs.java-version }} >> $HOME/.gradle/gradle.properties
echo systemProp.org.gradle.java.installations.auto-detect=false >> $HOME/.gradle/gradle.properties
echo systemProp.org.gradle.java.installations.auto-download=false >> $HOME/.gradle/gradle.properties
echo systemProp.org.gradle.java.installations.paths=${{ format('$JAVA_HOME_{0}_X64', inputs.java-version) }} >> $HOME/.gradle/gradle.properties
+1 -1
View File
@@ -1,4 +1,4 @@
name: Send notification
name: Send Notification
description: Sends a Google Chat message as a notification of the job's outcome
inputs:
webhook-url:
@@ -0,0 +1,50 @@
name: Sync to Maven Central
description: Syncs a release to Maven Central and waits for it to be available for use
inputs:
jfrog-cli-config-token:
description: 'Config token for the JFrog CLI'
required: true
spring-framework-version:
description: 'The version of Spring Framework that is being synced to Central'
required: true
ossrh-s01-token-username:
description: 'Username for authentication with s01.oss.sonatype.org'
required: true
ossrh-s01-token-password:
description: 'Password for authentication with s01.oss.sonatype.org'
required: true
ossrh-s01-staging-profile:
description: 'Staging profile to use when syncing to Central'
required: true
runs:
using: composite
steps:
- name: Set Up JFrog CLI
uses: jfrog/setup-jfrog-cli@7c95feb32008765e1b4e626b078dfd897c4340ad # v4.1.2
env:
JF_ENV_SPRING: ${{ inputs.jfrog-cli-config-token }}
- name: Download Release Artifacts
shell: bash
run: jf rt download --spec ${{ format('{0}/artifacts.spec', github.action_path) }} --spec-vars 'buildName=${{ format('spring-framework-{0}', inputs.spring-framework-version) }};buildNumber=${{ github.run_number }}'
- name: Sync
uses: spring-io/nexus-sync-action@42477a2230a2f694f9eaa4643fa9e76b99b7ab84 # v0.0.1
with:
username: ${{ inputs.ossrh-s01-token-username }}
password: ${{ inputs.ossrh-s01-token-password }}
staging-profile-name: ${{ inputs.ossrh-s01-staging-profile }}
create: true
upload: true
close: true
release: true
generate-checksums: true
- name: Await
shell: bash
run: |
url=${{ format('https://repo.maven.apache.org/maven2/org/springframework/spring-context/{0}/spring-context-{0}.jar', inputs.spring-framework-version) }}
echo "Waiting for $url"
until curl --fail --head --silent $url > /dev/null
do
echo "."
sleep 60
done
echo "$url is available"
@@ -0,0 +1,20 @@
{
"files": [
{
"aql": {
"items.find": {
"$and": [
{
"@build.name": "${buildName}",
"@build.number": "${buildNumber}",
"path": {
"$nmatch": "org/springframework/framework-api/*"
}
}
]
}
},
"target": "nexus/"
}
]
}
+29 -32
View File
@@ -1,49 +1,33 @@
name: Build and deploy snapshot
name: Build and Deploy Snapshot
on:
push:
branches:
- 6.1.x
permissions:
actions: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
build-and-deploy-snapshot:
if: ${{ github.repository == 'spring-projects/spring-framework' }}
name: Build and deploy snapshot
name: Build and Deploy Snapshot
runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-framework' }}
steps:
- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: 'liberica'
java-version: 17
- name: Check out code
- name: Check Out Code
uses: actions/checkout@v4
- name: Set up Gradle
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5
- name: Build and Publish
id: build-and-publish
uses: ./.github/actions/build
with:
cache-read-only: false
- name: Configure Gradle properties
shell: bash
run: |
mkdir -p $HOME/.gradle
echo 'systemProp.user.name=spring-builds+github' >> $HOME/.gradle/gradle.properties
echo 'systemProp.org.gradle.internal.launcher.welcomeMessageEnabled=false' >> $HOME/.gradle/gradle.properties
echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties
echo 'org.gradle.daemon=4' >> $HOME/.gradle/gradle.properties
- name: Build and publish
id: build
env:
CI: 'true'
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
run: ./gradlew -PdeploymentRepository=$(pwd)/deployment-repository build publishAllPublicationsToDeploymentRepository
develocity-access-key: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
publish: true
- name: Deploy
uses: spring-io/artifactory-deploy-action@v0.0.1
uses: spring-io/artifactory-deploy-action@26bbe925a75f4f863e1e529e85be2d0093cac116 # v0.0.1
with:
uri: 'https://repo.spring.io'
username: ${{ secrets.ARTIFACTORY_USERNAME }}
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
build-name: ${{ format('spring-framework-{0}', github.ref_name)}}
build-name: 'spring-framework-6.1.x'
repository: 'libs-snapshot-local'
folder: 'deployment-repository'
signing-key: ${{ secrets.GPG_PRIVATE_KEY }}
@@ -52,11 +36,24 @@ jobs:
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
/**/framework-api-*-docs.zip::zip.type=docs
/**/framework-api-*-schema.zip::zip.type=schema
- name: Send notification
- name: Send Notification
uses: ./.github/actions/send-notification
if: always()
with:
webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
status: ${{ job.status }}
build-scan-url: ${{ steps.build.outputs.build-scan-url }}
run-name: ${{ format('{0} | Linux | Java 17', github.ref_name) }}
build-scan-url: ${{ steps.build-and-publish.outputs.build-scan-url }}
run-name: ${{ format('{0} | Linux | Java 17', github.ref_name) }}
outputs:
version: ${{ steps.build-and-publish.outputs.version }}
verify:
name: Verify
needs: build-and-deploy-snapshot
uses: ./.github/workflows/verify.yml
secrets:
google-chat-webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
repository-password: ${{ secrets.ARTIFACTORY_PASSWORD }}
repository-username: ${{ secrets.ARTIFACTORY_USERNAME }}
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
with:
version: ${{ needs.build-and-deploy-snapshot.outputs.version }}
+16 -37
View File
@@ -7,6 +7,8 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
ci:
name: '${{ matrix.os.name}} | Java ${{ matrix.java.version}}'
runs-on: ${{ matrix.os.id }}
if: ${{ github.repository == 'spring-projects/spring-framework' }}
strategy:
matrix:
@@ -18,61 +20,38 @@ jobs:
toolchain: false
- version: 21
toolchain: true
- version: 22
toolchain: true
- version: 23-ea
distribution: temurin
toolchain: true
exclude:
- os:
name: Linux
java:
version: 17
name: '${{ matrix.os.name}} | Java ${{ matrix.java.version}}'
runs-on: ${{ matrix.os.id }}
steps:
- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: 'liberica'
java-version: |
${{ matrix.java.version }}
${{ matrix.java.toolchain && '17' || '' }}
- name: Prepare Windows runner
if: ${{ runner.os == 'Windows' }}
run: |
git config --global core.autocrlf true
git config --global core.longPaths true
Stop-Service -name Docker
- name: Check out code
- name: Check Out Code
uses: actions/checkout@v4
- name: Set up Gradle
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5
with:
cache-read-only: false
- name: Configure Gradle properties
shell: bash
run: |
mkdir -p $HOME/.gradle
echo 'systemProp.user.name=spring-builds+github' >> $HOME/.gradle/gradle.properties
echo 'systemProp.org.gradle.internal.launcher.welcomeMessageEnabled=false' >> $HOME/.gradle/gradle.properties
echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties
echo 'org.gradle.daemon=4' >> $HOME/.gradle/gradle.properties
- name: Configure toolchain properties
if: ${{ matrix.java.toolchain }}
shell: bash
run: |
echo toolchainVersion=${{ matrix.java.version }} >> $HOME/.gradle/gradle.properties
echo systemProp.org.gradle.java.installations.auto-detect=false >> $HOME/.gradle/gradle.properties
echo systemProp.org.gradle.java.installations.auto-download=false >> $HOME/.gradle/gradle.properties
echo systemProp.org.gradle.java.installations.paths=${{ format('$JAVA_HOME_{0}_X64', matrix.java.version) }} >> $HOME/.gradle/gradle.properties
- name: Build
id: build
env:
CI: 'true'
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
run: ./gradlew check antora
- name: Send notification
uses: ./.github/actions/build
with:
java-version: ${{ matrix.java.version }}
java-distribution: ${{ matrix.java.distribution || 'liberica' }}
java-toolchain: ${{ matrix.java.toolchain }}
develocity-access-key: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
- name: Send Notification
uses: ./.github/actions/send-notification
if: always()
with:
webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
status: ${{ job.status }}
build-scan-url: ${{ steps.build.outputs.build-scan-url }}
run-name: ${{ format('{0} | {1} | Java {2}', github.ref_name, matrix.os.name, matrix.java.version) }}
run-name: ${{ format('{0} | {1} | Java {2}', github.ref_name, matrix.os.name, matrix.java.version) }}
+90
View File
@@ -0,0 +1,90 @@
name: Release
on:
push:
tags:
- v6.1.[0-9]+
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
build-and-stage-release:
if: ${{ github.repository == 'spring-projects/spring-framework' }}
name: Build and Stage Release
runs-on: ubuntu-latest
steps:
- name: Check Out Code
uses: actions/checkout@v4
- name: Build and Publish
id: build-and-publish
uses: ./.github/actions/build
with:
develocity-access-key: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
publish: true
- name: Stage Release
uses: spring-io/artifactory-deploy-action@26bbe925a75f4f863e1e529e85be2d0093cac116 # v0.0.1
with:
build-name: ${{ format('spring-framework-{0}', steps.build-and-publish.outputs.version)}}
folder: 'deployment-repository'
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
repository: 'libs-staging-local'
signing-key: ${{ secrets.GPG_PRIVATE_KEY }}
signing-passphrase: ${{ secrets.GPG_PASSPHRASE }}
uri: 'https://repo.spring.io'
username: ${{ secrets.ARTIFACTORY_USERNAME }}
outputs:
version: ${{ steps.build-and-publish.outputs.version }}
verify:
name: Verify
needs: build-and-stage-release
uses: ./.github/workflows/verify.yml
with:
staging: true
version: ${{ needs.build-and-stage-release.outputs.version }}
secrets:
google-chat-webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
repository-password: ${{ secrets.ARTIFACTORY_PASSWORD }}
repository-username: ${{ secrets.ARTIFACTORY_USERNAME }}
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
sync-to-maven-central:
name: Sync to Maven Central
needs:
- build-and-stage-release
- verify
runs-on: ubuntu-latest
steps:
- name: Check Out Code
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Sync to Maven Central
uses: ./.github/actions/sync-to-maven-central
with:
jfrog-cli-config-token: ${{ secrets.JF_ARTIFACTORY_SPRING }}
ossrh-s01-staging-profile: ${{ secrets.OSSRH_S01_STAGING_PROFILE }}
ossrh-s01-token-password: ${{ secrets.OSSRH_S01_TOKEN_PASSWORD }}
ossrh-s01-token-username: ${{ secrets.OSSRH_S01_TOKEN_USERNAME }}
spring-framework-version: ${{ needs.build-and-stage-release.outputs.version }}
promote-release:
name: Promote Release
needs:
- build-and-stage-release
- sync-to-maven-central
runs-on: ubuntu-latest
steps:
- name: Set up JFrog CLI
uses: jfrog/setup-jfrog-cli@7c95feb32008765e1b4e626b078dfd897c4340ad # v4.1.2
env:
JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }}
- name: Promote build
run: jfrog rt build-promote ${{ format('spring-framework-{0}', needs.build-and-stage-release.outputs.version)}} ${{ github.run_number }} libs-release-local
create-github-release:
name: Create GitHub Release
needs:
- build-and-stage-release
- promote-release
runs-on: ubuntu-latest
steps:
- name: Check Out Code
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Create GitHub Release
uses: ./.github/actions/create-github-release
with:
milestone: ${{ needs.build-and-stage-release.outputs.version }}
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
@@ -1,13 +1,11 @@
name: "Validate Gradle Wrapper"
on: [push, pull_request]
permissions:
contents: read
jobs:
validation:
name: "Validation"
name: "Validate Gradle Wrapper"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: gradle/wrapper-validation-action@v2
- uses: gradle/actions/wrapper-validation@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2
+71
View File
@@ -0,0 +1,71 @@
name: Verify
on:
workflow_call:
inputs:
version:
required: true
type: string
staging:
required: false
default: false
type: boolean
secrets:
repository-username:
required: false
repository-password:
required: false
google-chat-webhook-url:
required: true
token:
required: true
jobs:
verify:
name: Verify
runs-on: ubuntu-latest
steps:
- name: Check Out Release Verification Tests
uses: actions/checkout@v4
with:
repository: spring-projects/spring-framework-release-verification
ref: 'v0.0.2'
token: ${{ secrets.token }}
- name: Check Out Send Notification Action
uses: actions/checkout@v4
with:
path: spring-framework
sparse-checkout: .github/actions/send-notification
- name: Set Up Java
uses: actions/setup-java@v4
with:
distribution: 'liberica'
java-version: 17
- name: Set Up Gradle
uses: gradle/actions/setup-gradle@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2
with:
cache-read-only: false
- name: Configure Gradle Properties
shell: bash
run: |
mkdir -p $HOME/.gradle
echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties
- name: Run Release Verification Tests
env:
RVT_VERSION: ${{ inputs.version }}
RVT_RELEASE_TYPE: oss
RVT_STAGING: ${{ inputs.staging }}
RVT_OSS_REPOSITORY_USERNAME: ${{ secrets.repository-username }}
RVT_OSS_REPOSITORY_PASSWORD: ${{ secrets.repository-password }}
run: ./gradlew spring-framework-release-verification-tests:test
- name: Upload Build Reports on Failure
uses: actions/upload-artifact@v4
if: failure()
with:
name: build-reports
path: '**/build/reports/'
- name: Send Notification
uses: ./spring-framework/.github/actions/send-notification
if: failure()
with:
webhook-url: ${{ secrets.google-chat-webhook-url }}
status: ${{ job.status }}
run-name: ${{ format('{0} | Verification | {1}', github.ref_name, inputs.version) }}
-44
View File
@@ -1,44 +0,0 @@
= Contributor Code of Conduct
As contributors and maintainers of this project, and in the interest of fostering an open
and welcoming community, we pledge to respect all people who contribute through reporting
issues, posting feature requests, updating documentation, submitting pull requests or
patches, and other activities.
We are committed to making participation in this project a harassment-free experience for
everyone, regardless of level of experience, gender, gender identity and expression,
sexual orientation, disability, personal appearance, body size, race, ethnicity, age,
religion, or nationality.
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery
* Personal attacks
* Trolling or insulting/derogatory comments
* Public or private harassment
* Publishing other's private information, such as physical or electronic addresses,
without explicit permission
* Other unethical or unprofessional conduct
Project maintainers have the right and responsibility to remove, edit, or reject comments,
commits, code, wiki edits, issues, and other contributions that are not aligned to this
Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors
that they deem inappropriate, threatening, offensive, or harmful.
By adopting this Code of Conduct, project maintainers commit themselves to fairly and
consistently applying these principles to every aspect of managing this project. Project
maintainers who do not follow or enforce the Code of Conduct may be permanently removed
from the project team.
This Code of Conduct applies both within project spaces and in public spaces when an
individual is representing the project or its community.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by
contacting a project maintainer at spring-code-of-conduct@pivotal.io . All complaints will
be reviewed and investigated and will result in a response that is deemed necessary and
appropriate to the circumstances. Maintainers are obligated to maintain confidentiality
with regard to the reporter of an incident.
This Code of Conduct is adapted from the
https://contributor-covenant.org[Contributor Covenant], version 1.3.0, available at
https://contributor-covenant.org/version/1/3/0/[contributor-covenant.org/version/1/3/0/]
+1 -1
View File
@@ -18,7 +18,7 @@ First off, thank you for taking the time to contribute! :+1: :tada:
This project is governed by the [Spring Code of Conduct](CODE_OF_CONDUCT.adoc).
By participating you are expected to uphold this code.
Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
Please report unacceptable behavior to spring-code-of-conduct@spring.io.
### How to Contribute
+1 -1
View File
@@ -6,7 +6,7 @@ Spring provides everything required beyond the Java programming language for cre
## Code of Conduct
This project is governed by the [Spring Code of Conduct](CODE_OF_CONDUCT.adoc). By participating, you are expected to uphold this code of conduct. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
This project is governed by the [Spring Code of Conduct](CODE_OF_CONDUCT.adoc). By participating, you are expected to uphold this code of conduct. Please report unacceptable behavior to spring-code-of-conduct@spring.io.
## Access to Binaries
+1 -1
View File
@@ -1,2 +1,2 @@
org.gradle.caching=true
javaFormatVersion=0.0.41
javaFormatVersion=0.0.42
@@ -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.16.0");
checkstyle.setToolVersion("10.17.0");
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/**");
"**/.classpath", "**/.project", "**/.gradle/**", "**/node_modules/**");
List<String> buildFolders = List.of("bin", "build", "out");
project.allprojects(subproject -> {
Path rootPath = project.getRootDir().toPath();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -63,8 +63,7 @@ class TestConventions {
test.systemProperty("testGroups", project.getProperties().get("testGroups"));
}
test.jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED",
"--add-opens=java.base/java.util=ALL-UNNAMED",
"-Djava.locale.providers=COMPAT");
"--add-opens=java.base/java.util=ALL-UNNAMED");
}
private void configureTestRetryPlugin(Project project, Test test) {
+10 -1
View File
@@ -17,4 +17,13 @@ changelog:
- "type: dependency-upgrade"
contributors:
exclude:
names: ["bclozel", "jhoeller", "poutsma", "rstoyanchev", "sbrannen", "sdeleuze", "snicoll", "simonbasle"]
names:
- "bclozel"
- "github-actions[bot]"
- "jhoeller"
- "poutsma"
- "rstoyanchev"
- "sbrannen"
- "sdeleuze"
- "simonbasle"
- "snicoll"
+2 -2
View File
@@ -7,8 +7,8 @@ anchors:
gradle-enterprise-task-params: &gradle-enterprise-task-params
DEVELOCITY_ACCESS_KEY: ((gradle_enterprise_secret_access_key))
sonatype-task-params: &sonatype-task-params
SONATYPE_USERNAME: ((sonatype-username))
SONATYPE_PASSWORD: ((sonatype-password))
SONATYPE_USERNAME: ((s01-user-token))
SONATYPE_PASSWORD: ((s01-user-token-password))
SONATYPE_URL: ((sonatype-url))
SONATYPE_STAGING_PROFILE: ((sonatype-staging-profile))
artifactory-task-params: &artifactory-task-params
+1 -1
View File
@@ -36,4 +36,4 @@ runtime:
failure_level: warn
ui:
bundle:
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.15/ui-bundle.zip
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.16/ui-bundle.zip
@@ -221,8 +221,8 @@ NOTE: `ThreadLocal` instances come with serious issues (potentially resulting in
incorrectly using them in multi-threaded and multi-classloader environments. You
should always consider wrapping a `ThreadLocal` in some other class and never directly use
the `ThreadLocal` itself (except in the wrapper class). Also, you should
always remember to correctly set and unset (where the latter simply involves a call to
`ThreadLocal.set(null)`) the resource local to the thread. Unsetting should be done in
always remember to correctly set and unset (where the latter involves a call to
`ThreadLocal.remove()`) the resource local to the thread. Unsetting should be done in
any case, since not unsetting it might result in problematic behavior. Spring's
`ThreadLocal` support does this for you and should always be considered in favor of using
`ThreadLocal` instances without other proper handling code.
@@ -326,6 +326,19 @@ However, this is not a best practice and flagging the preferred constructor with
In case you are working on a code base that you cannot modify, you can set the {spring-framework-api}/beans/factory/support/AbstractBeanDefinition.html#PREFERRED_CONSTRUCTORS_ATTRIBUTE[`preferredConstructors` attribute] on the related bean definition to indicate which constructor should be used.
[[aot.bestpractices.comlext-data-structure]]
=== Avoid Complex Data Structure for Constructor Parameters and Properties
When crafting a `RootBeanDefinition` programmatically, you are not constrained in terms of types that you can use.
For instance, you may have a custom `record` with several properties that your bean takes as a constructor argument.
While this works fine with the regular runtime, AOT does not know how to generate the code of your custom data structure.
A good rule of thumb is to keep in mind that bean definitions are an abstraction on top of several models.
Rather than using such structure, decomposing to simple types or referring to a bean that is built as such is recommended.
As a last resort, you can implement your own `org.springframework.aot.generate.ValueCodeGenerator$Delegate`.
To use it, register its fully qualified name in `META-INF/spring/aot.factories` using the `Delegate` as the key.
[[aot.bestpractices.custom-arguments]]
=== Avoid Creating Bean with Custom Arguments
@@ -153,17 +153,15 @@ Letting qualifier values select against target bean names, within the type-match
candidates, does not require a `@Qualifier` annotation at the injection point.
If there is no other resolution indicator (such as a qualifier or a primary marker),
for a non-unique dependency situation, Spring matches the injection point name
(that is, the field name or parameter name) against the target bean names and chooses the
same-named candidate, if any.
(that is, the field name or parameter name) against the target bean names and chooses
the same-named candidate, if any (either by bean name or by associated alias).
Since version 6.1, this requires the `-parameters` Java compiler flag to be present.
====
That said, if you intend to express annotation-driven injection by name, do not
primarily use `@Autowired`, even if it is capable of selecting by bean name among
type-matching candidates. Instead, use the JSR-250 `@Resource` annotation, which is
semantically defined to identify a specific target component by its unique name, with
the declared type being irrelevant for the matching process. `@Autowired` has rather
As an alternative for injection by name, consider the JSR-250 `@Resource` annotation
which is semantically defined to identify a specific target component by its unique name,
with the declared type being irrelevant for the matching process. `@Autowired` has rather
different semantics: After selecting candidate beans by type, the specified `String`
qualifier value is considered within those type-selected candidates only (for example,
matching an `account` qualifier against beans marked with the same qualifier label).
@@ -75,6 +75,31 @@ lead to concurrent access exceptions, inconsistent state in the bean container,
[[beans-definition-overriding]]
== Overriding Beans
Bean overriding is happening when a bean is registered using an identifier that is
already allocated. While bean overriding is possible, it makes the configuration harder
to read and this feature will be deprecated in a future release.
To disable bean overriding altogether, you can set the `allowBeanDefinitionOverriding`
flag to `false` on the `ApplicationContext` before it is refreshed. In such setup, an
exception is thrown if bean overriding is used.
By default, the container logs every bean overriding at `INFO` level so that you can
adapt your configuration accordingly. While not recommended, you can silence those logs
by setting the `allowBeanDefinitionOverriding` flag to `true`.
.Java-configuration
****
If you use Java Configuration, a corresponding `@Bean` method always silently overrides
a scanned bean class with the same component name as long as the return type of the
`@Bean` method matches that bean class. This simply means that the container will call
the `@Bean` factory method in favor of any pre-declared constructor on the bean class.
****
[[beans-beanname]]
== Naming Beans
@@ -201,7 +201,7 @@ the original class. Consider the following example:
<!-- inject dependencies here as required -->
</bean>
<!-- commandProcessor uses statefulCommandHelper -->
<!-- commandManager uses myCommand prototype bean -->
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="myCommand"/>
</bean>
@@ -439,7 +439,7 @@ dataSource.url=jdbc:mysql:mydb
----
This example file can be used with a container definition that contains a bean called
`dataSource` that has `driver` and `url` properties.
`dataSource` that has `driverClassName` and `url` properties.
Compound property names are also supported, as long as every component of the path
except the final property being overridden is already non-null (presumably initialized
@@ -299,7 +299,7 @@ Java::
public class AppConfig {
@Bean
public MethodValidationPostProcessor validationPostProcessor() {
public static MethodValidationPostProcessor validationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
@@ -341,7 +341,7 @@ xref:web/webflux/ann-rest-exceptions.adoc[Error Responses] sections.
=== Method Validation Exceptions
By default, `jakarta.validation.ConstraintViolationException` is raised with the set of
``ConstraintViolation``s returned by `jakarata.validation.Validator`. As an alternative,
``ConstraintViolation``s returned by `jakarta.validation.Validator`. As an alternative,
you can have `MethodValidationException` raised instead with ``ConstraintViolation``s
adapted to `MessageSourceResolvable` errors. To enable set the following flag:
@@ -357,7 +357,7 @@ Java::
public class AppConfig {
@Bean
public MethodValidationPostProcessor validationPostProcessor() {
public static MethodValidationPostProcessor validationPostProcessor() {
MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
processor.setAdaptConstraintViolations(true);
return processor;
@@ -211,19 +211,28 @@ the JDBC driver. If the count is not available, the JDBC driver returns a value
====
In such a scenario, with automatic setting of values on an underlying `PreparedStatement`,
the corresponding JDBC type for each value needs to be derived from the given Java type.
While this usually works well, there is a potential for issues (for example, with Map-contained
`null` values). Spring, by default, calls `ParameterMetaData.getParameterType` in such a
case, which can be expensive with your JDBC driver. You should use a recent driver
While this usually works well, there is a potential for issues (for example, with
Map-contained `null` values). Spring, by default, calls `ParameterMetaData.getParameterType`
in such a case, which can be expensive with your JDBC driver. You should use a recent driver
version and consider setting the `spring.jdbc.getParameterType.ignore` property to `true`
(as a JVM system property or via the
xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism) if you encounter
a performance issue (as reported on Oracle 12c, JBoss, and PostgreSQL).
xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism)
if you encounter a specific performance issue for your application.
Alternatively, you might consider specifying the corresponding JDBC types explicitly,
either through a `BatchPreparedStatementSetter` (as shown earlier), through an explicit type
array given to a `List<Object[]>` based call, through `registerSqlType` calls on a
custom `MapSqlParameterSource` instance, or through a `BeanPropertySqlParameterSource`
that derives the SQL type from the Java-declared property type even for a null value.
As of 6.1.2, Spring bypasses the default `getParameterType` resolution on PostgreSQL and
MS SQL Server. This is a common optimization to avoid further roundtrips to the DBMS just
for parameter type resolution which is known to make a very significant difference on
PostgreSQL and MS SQL Server specifically, in particular for batch operations. If you
happen to see a side effect e.g. when setting a byte array to null without specific type
indication, you may explicitly set the `spring.jdbc.getParameterType.ignore=false` flag
as a system property (see above) to restore full `getParameterType` resolution.
Alternatively, you could consider specifying the corresponding JDBC types explicitly,
either through a `BatchPreparedStatementSetter` (as shown earlier), through an explicit
type array given to a `List<Object[]>` based call, through `registerSqlType` calls on a
custom `MapSqlParameterSource` instance, through a `BeanPropertySqlParameterSource`
that derives the SQL type from the Java-declared property type even for a null value, or
through providing individual `SqlParameterValue` instances instead of plain null values.
====
@@ -112,7 +112,7 @@ Java::
this.actorMappingQuery = new ActorMappingQuery(dataSource);
}
public Customer getCustomer(Long id) {
public Actor getActor(Long id) {
return actorMappingQuery.findObject(id);
}
----
@@ -123,11 +123,11 @@ Kotlin::
----
private val actorMappingQuery = ActorMappingQuery(dataSource)
fun getCustomer(id: Long) = actorMappingQuery.findObject(id)
fun getActor(id: Long) = actorMappingQuery.findObject(id)
----
======
The method in the preceding example retrieves the customer with the `id` that is passed in as the
The method in the preceding example retrieves the actor with the `id` that is passed in as the
only parameter. Since we want only one object to be returned, we call the `findObject` convenience
method with the `id` as the parameter. If we had instead a query that returned a
list of objects and took additional parameters, we would use one of the `execute`
+1 -3
View File
@@ -29,8 +29,6 @@ Brannen, Ramnivas Laddad, Arjen Poutsma, Chris Beams, Tareq Abedrabbo, Andy Clem
Syer, Oliver Gierke, Rossen Stoyanchev, Phillip Webb, Rob Winch, Brian Clozel, Stephane
Nicoll, Sebastien Deleuze, Jay Bryant, Mark Paluch
Copyright © 2002 - 2024 VMware, Inc. All Rights Reserved.
Copies of this document may be made for your own use and for distribution to others,
provided that you do not charge any fee for such copies and further provided that each
copy contains this Copyright Notice, whether distributed in print or electronically.
copy contains the Copyright Notice, whether distributed in print or electronically.
@@ -58,7 +58,7 @@ your `@Configuration` classes, as the following example shows:
By default, the infrastructure looks for a bean named `jmsListenerContainerFactory`
as the source for the factory to use to create message listener containers. In this
case (and ignoring the JMS infrastructure setup), you can invoke the `processOrder`
method with a core poll size of three threads and a maximum pool size of ten threads.
method with a core pool size of three threads and a maximum pool size of ten threads.
You can customize the listener container factory to use for each annotation or you can
configure an explicit default by implementing the `JmsListenerConfigurer` interface.
@@ -97,8 +97,8 @@ Java::
Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.onStatus(HttpStatus::is4xxClientError, response -> ...)
.onStatus(HttpStatus::is5xxServerError, response -> ...)
.onStatus(HttpStatusCode::is4xxClientError, response -> ...)
.onStatus(HttpStatusCode::is5xxServerError, response -> ...)
.bodyToMono(Person.class);
----
@@ -109,8 +109,8 @@ Kotlin::
val result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.onStatus(HttpStatus::is4xxClientError) { ... }
.onStatus(HttpStatus::is5xxServerError) { ... }
.onStatus(HttpStatusCode::is4xxClientError) { ... }
.onStatus(HttpStatusCode::is5xxServerError) { ... }
.awaitBody<Person>()
----
======
@@ -338,7 +338,7 @@ Kotlin::
class WebConfig : WebFluxConfigurer {
override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) {
// ...
configurer.defaultCodecs().maxInMemorySize(512 * 1024)
}
}
----
@@ -77,7 +77,7 @@ and others) and is equivalent to `required=false`.
| For access to a part in a `multipart/form-data` request. Supports reactive types.
See xref:web/webflux/controller/ann-methods/multipart-forms.adoc[Multipart Content] and xref:web/webflux/reactive-spring.adoc#webflux-multipart[Multipart Data].
| `java.util.Map`, `org.springframework.ui.Model`, and `org.springframework.ui.ModelMap`.
| `java.util.Map` or `org.springframework.ui.Model`
| For access to the model that is used in HTML controllers and is exposed to templates as
part of view rendering.
@@ -89,9 +89,9 @@ and others) and is equivalent to `required=false`.
Note that use of `@ModelAttribute` is optional -- for example, to set its attributes.
See "`Any other argument`" later in this table.
| `Errors`, `BindingResult`
| `Errors` or `BindingResult`
| For access to errors from validation and data binding for a command object, i.e. a
`@ModelAttribute` argument. An `Errors`, or `BindingResult` argument must be declared
`@ModelAttribute` argument. An `Errors` or `BindingResult` argument must be declared
immediately after the validated method argument.
| `SessionStatus` + class-level `@SessionAttributes`
@@ -13,7 +13,7 @@ xref:web/webflux/controller/ann-methods/multipart-forms.adoc[@RequestPart] argum
resolvers validate a method argument individually if the method parameter is annotated
with Jakarta `@Valid` or Spring's `@Validated`, _AND_ there is no `Errors` or
`BindingResult` parameter immediately after, _AND_ method validation is not needed (to be
discussed next). The exception raised in this case is `MethodArgumentNotValidException`.
discussed next). The exception raised in this case is `WebExchangeBindException`.
2. When `@Constraint` annotations such as `@Min`, `@NotBlank` and others are declared
directly on method parameters, or on the method (for the return value), then method
@@ -21,7 +21,7 @@ validation must be applied, and that supersedes validation at the method argumen
because method validation covers both method parameter constraints and nested constraints
via `@Valid`. The exception raised in this case is `HandlerMethodValidationException`.
Applications must handle both `MethodArgumentNotValidException` and
Applications must handle both `WebExchangeBindException` and
`HandlerMethodValidationException` as either may be raised depending on the controller
method signature. The two exceptions, however are designed to be very similar, and can be
handled with almost identical code. The main difference is that the former is for a single
@@ -39,7 +39,7 @@ method parameters with an `Errors` immediately after. If there are validation er
any other method parameter then `HandlerMethodValidationException` is raised.
You can configure a `Validator` globally through the
xref:web/webflux/config.adoc#webflux-config-validation[WebMvc config], or locally
xref:web/webflux/config.adoc#webflux-config-validation[WebFlux config], or locally
through an xref:web/webflux/controller/ann-initbinder.adoc[@InitBinder] method in an
`@Controller` or `@ControllerAdvice`. You can also use multiple validators.
@@ -49,8 +49,8 @@ through an AOP proxy. In order to take advantage of the Spring MVC built-in supp
method validation added in Spring Framework 6.1, you need to remove the class level
`@Validated` annotation from the controller.
The xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses] section provides further
details on how `MethodArgumentNotValidException` and `HandlerMethodValidationException`
The xref:web/webflux/ann-rest-exceptions.adoc[Error Responses] section provides further
details on how `WebExchangeBindException` and `HandlerMethodValidationException`
are handled, and also how their rendering can be customized through a `MessageSource` and
locale and language specific resource bundles.
@@ -40,7 +40,9 @@ content of the provided resource to the response `OutputStream`. Note that the
`InputStream` should be lazily retrieved by the `Resource` handle in order to reliably
close it after it has been copied to the response. If you are using `InputStreamResource`
for such a purpose, make sure to construct it with an on-demand `InputStreamSource`
(e.g. through a lambda expression that retrieves the actual `InputStream`).
(e.g. through a lambda expression that retrieves the actual `InputStream`). Also, custom
subclasses of `InputStreamResource` are only supported in combination with a custom
`contentLength()` implementation which avoids consuming the stream for that purpose.
Spring MVC supports using a single value xref:web/webmvc/mvc-ann-async.adoc#mvc-ann-async-reactive-types[reactive type]
to produce the `ResponseEntity` asynchronously, and/or single and multi-value reactive
@@ -13,7 +13,7 @@ If configured with a task scheduler, the simple broker supports
https://stomp.github.io/stomp-specification-1.2.html#Heart-beating[STOMP heartbeats].
To configure a scheduler, you can declare your own `TaskScheduler` bean and set it through
the `MessageBrokerRegistry`. Alternatively, you can use the one that is automatically
declared in the built-in WebSocket configuration, however, you'll' need `@Lazy` to avoid
declared in the built-in WebSocket configuration, however, you'll need `@Lazy` to avoid
a cycle between the built-in WebSocket configuration and your
`WebSocketMessageBrokerConfigurer`. For example:
@@ -389,7 +389,7 @@ template.
encode URI component value _after_ URI variables are expanded.
* `NONE`: No encoding is applied.
The `RestTemplate` is set to `EncodingMode.URI_COMPONENT` for historic
The `RestTemplate` is set to `EncodingMode.URI_COMPONENT` for historical
reasons and for backwards compatibility. The `WebClient` relies on the default value
in `DefaultUriBuilderFactory`, which was changed from `EncodingMode.URI_COMPONENT` in
5.0.x to `EncodingMode.TEMPLATE_AND_VALUES` in 5.1.
+24 -24
View File
@@ -8,19 +8,19 @@ javaPlatform {
dependencies {
api(platform("com.fasterxml.jackson:jackson-bom:2.15.4"))
api(platform("io.micrometer:micrometer-bom:1.12.6"))
api(platform("io.netty:netty-bom:4.1.109.Final"))
api(platform("io.micrometer:micrometer-bom:1.12.8"))
api(platform("io.netty:netty-bom:4.1.111.Final"))
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
api(platform("io.projectreactor:reactor-bom:2023.0.6"))
api(platform("io.projectreactor:reactor-bom:2023.0.8"))
api(platform("io.rsocket:rsocket-bom:1.1.3"))
api(platform("org.apache.groovy:groovy-bom:4.0.21"))
api(platform("org.apache.groovy:groovy-bom:4.0.22"))
api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))
api(platform("org.assertj:assertj-bom:3.25.3"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.9"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.9"))
api(platform("org.assertj:assertj-bom:3.26.0"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.11"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.11"))
api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3"))
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.0"))
api(platform("org.junit:junit-bom:5.10.2"))
api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.3"))
api(platform("org.junit:junit-bom:5.10.3"))
api(platform("org.mockito:mockito-bom:5.12.0"))
constraints {
@@ -55,9 +55,9 @@ dependencies {
api("io.r2dbc:r2dbc-spi:1.0.0.RELEASE")
api("io.reactivex.rxjava3:rxjava:3.1.8")
api("io.smallrye.reactive:mutiny:1.10.0")
api("io.undertow:undertow-core:2.3.13.Final")
api("io.undertow:undertow-servlet:2.3.13.Final")
api("io.undertow:undertow-websockets-jsr:2.3.13.Final")
api("io.undertow:undertow-core:2.3.14.Final")
api("io.undertow:undertow-servlet:2.3.14.Final")
api("io.undertow:undertow-websockets-jsr:2.3.14.Final")
api("io.vavr:vavr:0.10.4")
api("jakarta.activation:jakarta.activation-api:2.0.1")
api("jakarta.annotation:jakarta.annotation-api:2.0.0")
@@ -101,12 +101,12 @@ dependencies {
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.3.1")
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.2.4")
api("org.apache.httpcomponents.core5:httpcore5-reactive:5.2.5")
api("org.apache.poi:poi-ooxml:5.2.5")
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.24")
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.24")
api("org.apache.tomcat:tomcat-util:10.1.24")
api("org.apache.tomcat:tomcat-websocket:10.1.24")
api("org.apache.tomcat.embed:tomcat-embed-core:10.1.25")
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.25")
api("org.apache.tomcat:tomcat-util:10.1.25")
api("org.apache.tomcat:tomcat-websocket:10.1.25")
api("org.aspectj:aspectjrt:1.9.22.1")
api("org.aspectj:aspectjtools:1.9.22.1")
api("org.aspectj:aspectjweaver:1.9.22.1")
@@ -115,12 +115,12 @@ dependencies {
api("org.codehaus.jettison:jettison:1.5.4")
api("org.crac:crac:1.4.0")
api("org.dom4j:dom4j:2.1.4")
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.4")
api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.5")
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.32")
api("org.freemarker:freemarker:2.3.33")
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")
@@ -129,17 +129,17 @@ 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.2")
api("org.javamoney:moneta:1.4.2")
api("org.jruby:jruby:9.4.6.0")
api("org.javamoney:moneta:1.4.4")
api("org.jruby:jruby:9.4.8.0")
api("org.junit.support:testng-engine:1.0.5")
api("org.mozilla:rhino:1.7.14")
api("org.mozilla:rhino:1.7.15")
api("org.ogce:xpp3:1.1.6")
api("org.python:jython-standalone:2.7.3")
api("org.quartz-scheduler:quartz:2.3.2")
api("org.seleniumhq.selenium:htmlunit-driver:2.70.0")
api("org.seleniumhq.selenium:selenium-java:3.141.59")
api("org.skyscreamer:jsonassert:1.5.1")
api("org.slf4j:slf4j-api:2.0.12")
api("org.skyscreamer:jsonassert:1.5.3")
api("org.slf4j:slf4j-api:2.0.13")
api("org.testng:testng:7.9.0")
api("org.webjars:underscorejs:1.8.3")
api("org.webjars:webjars-locator-core:0.55")
+2 -2
View File
@@ -1,10 +1,10 @@
version=6.1.8-SNAPSHOT
version=6.1.11
org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m
org.gradle.parallel=true
kotlinVersion=1.9.22
kotlinVersion=1.9.24
kotlin.jvm.target.validation.mode=ignore
kotlin.stdlib.default.dependency=false
+1 -1
View File
@@ -29,7 +29,7 @@ publishing {
developer {
id = "jhoeller"
name = "Juergen Hoeller"
email = "jhoeller@pivotal.io"
email = "juergen.hoeller@broadcom.com"
}
}
issueManagement {
+2 -2
View File
@@ -87,12 +87,12 @@ rootProject.ext {
gradle.taskGraph.afterTask { Task task, TaskState state ->
if (!resolvedMainToolchain && task instanceof JavaCompile && task.javaCompiler.isPresent()) {
def metadata = task.javaCompiler.get().metadata
task.project.buildScan.value('Main toolchain', "$metadata.vendor $metadata.languageVersion ($metadata.installationPath)")
task.project.develocity.buildScan.value('Main toolchain', "$metadata.vendor $metadata.languageVersion ($metadata.installationPath)")
resolvedMainToolchain = true
}
if (testToolchainConfigured() && !resolvedTestToolchain && task instanceof Test && task.javaLauncher.isPresent()) {
def metadata = task.javaLauncher.get().metadata
task.project.buildScan.value('Test toolchain', "$metadata.vendor $metadata.languageVersion ($metadata.installationPath)")
task.project.develocity.buildScan.value('Test toolchain', "$metadata.vendor $metadata.languageVersion ($metadata.installationPath)")
resolvedTestToolchain = true
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+1 -1
View File
@@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -62,7 +62,7 @@ class EnableCachingIntegrationTests {
// attempt was made to look up the AJ aspect. It's due to classpath issues
// in integration-tests that it's not found.
assertThatException().isThrownBy(ctx::refresh)
.withMessageContaining("AspectJCachingConfiguration");
.withMessageContaining("AspectJCachingConfiguration");
}
@@ -541,8 +541,7 @@ public class EnvironmentSystemIntegrationTests {
{
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setRequiredProperties("foo", "bar");
assertThatExceptionOfType(MissingRequiredPropertiesException.class).isThrownBy(
ctx::refresh);
assertThatExceptionOfType(MissingRequiredPropertiesException.class).isThrownBy(ctx::refresh);
}
{
@@ -97,9 +97,8 @@ class EnableTransactionManagementIntegrationTests {
// this test is a bit fragile, but gets the job done, proving that an
// attempt was made to look up the AJ aspect. It's due to classpath issues
// in integration-tests that it's not found.
assertThatException()
.isThrownBy(ctx::refresh)
.withMessageContaining("AspectJJtaTransactionManagementConfiguration");
assertThatException().isThrownBy(ctx::refresh)
.withMessageContaining("AspectJJtaTransactionManagementConfiguration");
}
@Test
@@ -20,6 +20,9 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import org.aopalliance.intercept.MethodInterceptor
import org.aopalliance.intercept.MethodInvocation
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Aspect
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.aop.framework.autoproxy.AspectJAutoProxyInterceptorKotlinIntegrationTests.InterceptorConfig
@@ -28,10 +31,18 @@ import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.EnableAspectJAutoProxy
import org.springframework.stereotype.Component
import org.springframework.test.annotation.DirtiesContext
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig
import org.springframework.transaction.annotation.EnableTransactionManagement
import org.springframework.transaction.annotation.Transactional
import org.springframework.transaction.testfixture.ReactiveCallCountingTransactionManager
import reactor.core.publisher.Mono
import java.lang.reflect.Method
import kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS
import kotlin.annotation.AnnotationTarget.CLASS
import kotlin.annotation.AnnotationTarget.FUNCTION
import kotlin.annotation.AnnotationTarget.TYPE
/**
@@ -43,7 +54,9 @@ import java.lang.reflect.Method
class AspectJAutoProxyInterceptorKotlinIntegrationTests(
@Autowired val echo: Echo,
@Autowired val firstAdvisor: TestPointcutAdvisor,
@Autowired val secondAdvisor: TestPointcutAdvisor) {
@Autowired val secondAdvisor: TestPointcutAdvisor,
@Autowired val countingAspect: CountingAspect,
@Autowired val reactiveTransactionManager: ReactiveCallCountingTransactionManager) {
@Test
fun `Multiple interceptors with regular function`() {
@@ -67,8 +80,22 @@ class AspectJAutoProxyInterceptorKotlinIntegrationTests(
assertThat(secondAdvisor.interceptor.invocations).singleElement().matches { Mono::class.java.isAssignableFrom(it) }
}
@Test // gh-33095
fun `Aspect and reactive transactional with suspending function`() {
assertThat(countingAspect.counter).isZero()
assertThat(reactiveTransactionManager.commits).isZero()
val value = "Hello!"
runBlocking {
assertThat(echo.suspendingTransactionalEcho(value)).isEqualTo(value)
}
assertThat(countingAspect.counter).`as`("aspect applied").isOne()
assertThat(reactiveTransactionManager.begun).isOne()
assertThat(reactiveTransactionManager.commits).`as`("transactional applied").isOne()
}
@Configuration
@EnableAspectJAutoProxy
@EnableTransactionManagement
open class InterceptorConfig {
@Bean
@@ -77,6 +104,13 @@ class AspectJAutoProxyInterceptorKotlinIntegrationTests(
@Bean
open fun secondAdvisor() = TestPointcutAdvisor().apply { order = 1 }
@Bean
open fun countingAspect() = CountingAspect()
@Bean
open fun transactionManager(): ReactiveCallCountingTransactionManager {
return ReactiveCallCountingTransactionManager()
}
@Bean
open fun echo(): Echo {
@@ -107,6 +141,24 @@ class AspectJAutoProxyInterceptorKotlinIntegrationTests(
}
}
@Target(CLASS, FUNCTION, ANNOTATION_CLASS, TYPE)
@Retention(AnnotationRetention.RUNTIME)
annotation class Counting()
@Aspect
@Component
class CountingAspect {
var counter: Long = 0
@Around("@annotation(org.springframework.aop.framework.autoproxy.AspectJAutoProxyInterceptorKotlinIntegrationTests.Counting)")
fun logging(joinPoint: ProceedingJoinPoint): Any {
return (joinPoint.proceed(joinPoint.args) as Mono<*>).doOnTerminate {
counter++
}
}
}
open class Echo {
open fun echo(value: String): String {
@@ -118,6 +170,13 @@ class AspectJAutoProxyInterceptorKotlinIntegrationTests(
return value
}
@Transactional
@Counting
open suspend fun suspendingTransactionalEcho(value: String): String {
delay(1)
return value
}
}
}
@@ -18,6 +18,7 @@ package org.springframework.aop.aspectj;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
@@ -86,6 +87,8 @@ import org.springframework.util.StringUtils;
public class AspectJExpressionPointcut extends AbstractExpressionPointcut
implements ClassFilter, IntroductionAwareMethodMatcher, BeanFactoryAware {
private static final String AJC_MAGIC = "ajc$";
private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = Set.of(
PointcutPrimitive.EXECUTION,
PointcutPrimitive.ARGS,
@@ -103,6 +106,8 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
@Nullable
private Class<?> pointcutDeclarationScope;
private boolean aspectCompiledByAjc;
private String[] pointcutParameterNames = new String[0];
private Class<?>[] pointcutParameterTypes = new Class<?>[0];
@@ -134,7 +139,7 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
* @param paramTypes the parameter types for the pointcut
*/
public AspectJExpressionPointcut(Class<?> declarationScope, String[] paramNames, Class<?>[] paramTypes) {
this.pointcutDeclarationScope = declarationScope;
setPointcutDeclarationScope(declarationScope);
if (paramNames.length != paramTypes.length) {
throw new IllegalStateException(
"Number of pointcut parameter names must match number of pointcut parameter types");
@@ -149,6 +154,7 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
*/
public void setPointcutDeclarationScope(Class<?> pointcutDeclarationScope) {
this.pointcutDeclarationScope = pointcutDeclarationScope;
this.aspectCompiledByAjc = compiledByAjc(pointcutDeclarationScope);
}
/**
@@ -274,6 +280,11 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
@Override
public boolean matches(Class<?> targetClass) {
if (this.pointcutParsingFailed) {
// Pointcut parsing failed before below -> avoid trying again.
return false;
}
if (this.aspectCompiledByAjc && compiledByAjc(targetClass)) {
// ajc-compiled aspect class for ajc-compiled target class -> already weaved.
return false;
}
@@ -536,6 +547,16 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
return resolveExpression().contains("@annotation");
}
private static boolean compiledByAjc(Class<?> clazz) {
for (Field field : clazz.getDeclaredFields()) {
if (field.getName().startsWith(AJC_MAGIC)) {
return true;
}
}
Class<?> superclass = clazz.getSuperclass();
return (superclass != null && compiledByAjc(superclass));
}
@Override
public boolean equals(@Nullable Object other) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,9 +22,12 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.reflect.PerClauseKind;
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.AopConfigException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.lang.Nullable;
@@ -40,6 +43,8 @@ import org.springframework.util.Assert;
*/
public class BeanFactoryAspectJAdvisorsBuilder {
private static final Log logger = LogFactory.getLog(BeanFactoryAspectJAdvisorsBuilder.class);
private final ListableBeanFactory beanFactory;
private final AspectJAdvisorFactory advisorFactory;
@@ -102,30 +107,37 @@ public class BeanFactoryAspectJAdvisorsBuilder {
continue;
}
if (this.advisorFactory.isAspect(beanType)) {
aspectNames.add(beanName);
AspectMetadata amd = new AspectMetadata(beanType, beanName);
if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
MetadataAwareAspectInstanceFactory factory =
new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
if (this.beanFactory.isSingleton(beanName)) {
this.advisorsCache.put(beanName, classAdvisors);
try {
AspectMetadata amd = new AspectMetadata(beanType, beanName);
if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
MetadataAwareAspectInstanceFactory factory =
new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
if (this.beanFactory.isSingleton(beanName)) {
this.advisorsCache.put(beanName, classAdvisors);
}
else {
this.aspectFactoryCache.put(beanName, factory);
}
advisors.addAll(classAdvisors);
}
else {
// Per target or per this.
if (this.beanFactory.isSingleton(beanName)) {
throw new IllegalArgumentException("Bean with name '" + beanName +
"' is a singleton, but aspect instantiation model is not singleton");
}
MetadataAwareAspectInstanceFactory factory =
new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
this.aspectFactoryCache.put(beanName, factory);
advisors.addAll(this.advisorFactory.getAdvisors(factory));
}
advisors.addAll(classAdvisors);
aspectNames.add(beanName);
}
else {
// Per target or per this.
if (this.beanFactory.isSingleton(beanName)) {
throw new IllegalArgumentException("Bean with name '" + beanName +
"' is a singleton, but aspect instantiation model is not singleton");
catch (IllegalArgumentException | IllegalStateException | AopConfigException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Ignoring incompatible aspect [" + beanType.getName() + "]: " + ex);
}
MetadataAwareAspectInstanceFactory factory =
new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
this.aspectFactoryCache.put(beanName, factory);
advisors.addAll(this.advisorFactory.getAdvisors(factory));
}
}
}
@@ -213,8 +213,16 @@ public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFacto
return null;
}
return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
try {
return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}
catch (IllegalArgumentException | IllegalStateException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Ignoring incompatible advice method: " + candidateAdviceMethod, ex);
}
return null;
}
}
@Nullable
@@ -488,20 +488,27 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
* @return a List of MethodInterceptors (may also include InterceptorAndDynamicMethodMatchers)
*/
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) {
if (this.methodCache == null) {
List<Object> cachedInterceptors;
if (this.methodCache != null) {
// Method-specific cache for method-specific pointcuts
MethodCacheKey cacheKey = new MethodCacheKey(method);
cachedInterceptors = this.methodCache.get(cacheKey);
if (cachedInterceptors == null) {
cachedInterceptors = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
this, method, targetClass);
this.methodCache.put(cacheKey, cachedInterceptors);
}
}
else {
// Shared cache since there are no method-specific advisors (see below).
List<Object> cachedInterceptors = this.cachedInterceptors;
cachedInterceptors = this.cachedInterceptors;
if (cachedInterceptors == null) {
cachedInterceptors = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
this, method, targetClass);
this.cachedInterceptors = cachedInterceptors;
}
return cachedInterceptors;
}
// Method-specific cache for method-specific pointcuts
return this.methodCache.computeIfAbsent(new MethodCacheKey(method), k ->
this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(this, method, targetClass));
return cachedInterceptors;
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,7 +20,9 @@ import java.util.List;
import org.springframework.aop.Advisor;
import org.springframework.aop.TargetSource;
import org.springframework.aop.framework.AopConfigException;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
@@ -97,7 +99,13 @@ public abstract class AbstractAdvisorAutoProxyCreator extends AbstractAutoProxyC
List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
extendAdvisors(eligibleAdvisors);
if (!eligibleAdvisors.isEmpty()) {
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
try {
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
}
catch (BeanCreationException ex) {
throw new AopConfigException("Advisor sorting failed with unexpected bean creation, probably due " +
"to custom use of the Ordered interface. Consider using the @Order annotation instead.", ex);
}
}
return eligibleAdvisors;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -79,8 +79,8 @@ class ScopedProxyBeanRegistrationAotProcessor implements BeanRegistrationAotProc
}
@Nullable
private BeanDefinition getTargetBeanDefinition(ConfigurableBeanFactory beanFactory,
@Nullable String targetBeanName) {
private BeanDefinition getTargetBeanDefinition(
ConfigurableBeanFactory beanFactory, @Nullable String targetBeanName) {
if (targetBeanName != null && beanFactory.containsBean(targetBeanName)) {
return beanFactory.getMergedBeanDefinition(targetBeanName);
@@ -123,40 +123,32 @@ class ScopedProxyBeanRegistrationAotProcessor implements BeanRegistrationAotProc
@Override
public CodeBlock generateSetBeanDefinitionPropertiesCode(
GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode,
GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode,
RootBeanDefinition beanDefinition, Predicate<String> attributeFilter) {
RootBeanDefinition processedBeanDefinition = new RootBeanDefinition(
beanDefinition);
processedBeanDefinition
.setTargetType(this.targetBeanDefinition.getResolvableType());
processedBeanDefinition.getPropertyValues()
.removePropertyValue("targetBeanName");
RootBeanDefinition processedBeanDefinition = new RootBeanDefinition(beanDefinition);
processedBeanDefinition.setTargetType(this.targetBeanDefinition.getResolvableType());
processedBeanDefinition.getPropertyValues().removePropertyValue("targetBeanName");
return super.generateSetBeanDefinitionPropertiesCode(generationContext,
beanRegistrationCode, processedBeanDefinition, attributeFilter);
}
@Override
public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode, boolean allowDirectSupplierShortcut) {
public CodeBlock generateInstanceSupplierCode(
GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode,
boolean allowDirectSupplierShortcut) {
GeneratedMethod generatedMethod = beanRegistrationCode.getMethods()
.add("getScopedProxyInstance", method -> {
method.addJavadoc(
"Create the scoped proxy bean instance for '$L'.",
method.addJavadoc("Create the scoped proxy bean instance for '$L'.",
this.registeredBean.getBeanName());
method.addModifiers(Modifier.PRIVATE, Modifier.STATIC);
method.returns(ScopedProxyFactoryBean.class);
method.addParameter(RegisteredBean.class,
REGISTERED_BEAN_PARAMETER_NAME);
method.addParameter(RegisteredBean.class, REGISTERED_BEAN_PARAMETER_NAME);
method.addStatement("$T factory = new $T()",
ScopedProxyFactoryBean.class,
ScopedProxyFactoryBean.class);
method.addStatement("factory.setTargetBeanName($S)",
this.targetBeanName);
method.addStatement(
"factory.setBeanFactory($L.getBeanFactory())",
ScopedProxyFactoryBean.class, ScopedProxyFactoryBean.class);
method.addStatement("factory.setTargetBeanName($S)", this.targetBeanName);
method.addStatement("factory.setBeanFactory($L.getBeanFactory())",
REGISTERED_BEAN_PARAMETER_NAME);
method.addStatement("return factory");
});
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,6 +36,7 @@ import static org.springframework.aot.hint.predicate.RuntimeHintsPredicates.refl
* Tests for {@link AspectJAdvisorBeanRegistrationAotProcessor}.
*
* @author Sebastien Deleuze
* @since 6.1
*/
class AspectJAdvisorBeanRegistrationAotProcessorTests {
@@ -43,8 +44,9 @@ class AspectJAdvisorBeanRegistrationAotProcessorTests {
private final RuntimeHints runtimeHints = this.generationContext.getRuntimeHints();
@Test
void shouldProcessesAspectJClass() {
void shouldProcessAspectJClass() {
process(AspectJClass.class);
assertThat(reflection().onType(AspectJClass.class).withMemberCategory(MemberCategory.DECLARED_FIELDS))
.accepts(this.runtimeHints);
@@ -2,21 +2,29 @@
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:cache="http://www.springframework.org/schema/cache"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/cache https://www.springframework.org/schema/cache/spring-cache-3.1.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context-2.5.xsd">
<aop:aspectj-autoproxy/>
<context:spring-configured/>
<cache:annotation-driven mode="aspectj"/>
<bean id="cacheManager" class="org.springframework.cache.support.NoOpCacheManager"/>
<bean id="myAspect" class="org.springframework.aop.aspectj.autoproxy.CodeStyleAspect" factory-method="aspectOf">
<property name="foo" value="bar"/>
</bean>
<bean id="otherBean" class="java.lang.Object"/>
<bean id="otherBean" class="org.springframework.beans.factory.aspectj.ShouldBeConfiguredBySpring"/>
<bean id="yetAnotherBean" class="java.lang.Object"/>
<bean id="yetAnotherBean" class="org.springframework.beans.factory.aspectj.ShouldBeConfiguredBySpring"/>
<bean id="configuredBean" class="org.springframework.beans.factory.aspectj.ShouldBeConfiguredBySpring" lazy-init="true"/>
</beans>
@@ -24,8 +24,7 @@
</property>
</bean>
<bean id="defaultCache"
class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean">
<bean id="defaultCache" class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean">
<property name="name" value="default"/>
</bean>
@@ -7,12 +7,10 @@
http://www.springframework.org/schema/task
https://www.springframework.org/schema/task/spring-task.xsd">
<task:annotation-driven mode="aspectj" executor="testExecutor"
exception-handler="testExceptionHandler"/>
<task:annotation-driven mode="aspectj" executor="testExecutor" exception-handler="testExceptionHandler"/>
<task:executor id="testExecutor"/>
<bean id="testExceptionHandler"
class="org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler"/>
<bean id="testExceptionHandler" class="org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler"/>
</beans>
@@ -243,7 +243,7 @@ public interface BeanFactory {
* specific type, specify the actual bean type as an argument here and subsequently
* use {@link ObjectProvider#orderedStream()} or its lazy streaming/iteration options.
* <p>Also, generics matching is strict here, as per the Java assignment rules.
* For lenient fallback matching with unchecked semantics (similar to the ´unchecked´
* For lenient fallback matching with unchecked semantics (similar to the 'unchecked'
* Java compiler warning), consider calling {@link #getBeanProvider(Class)} with the
* raw type as a second step if no full generic match is
* {@link ObjectProvider#getIfAvailable() available} with this variant.
@@ -1098,7 +1098,6 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
}
}
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -213,12 +213,13 @@ public final class BeanInstanceSupplier<T> extends AutowiredElementResolver impl
if (!(executable instanceof Method method)) {
return beanSupplier.get();
}
Method priorInvokedFactoryMethod = SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod();
try {
SimpleInstantiationStrategy.setCurrentlyInvokedFactoryMethod(method);
return beanSupplier.get();
}
finally {
SimpleInstantiationStrategy.setCurrentlyInvokedFactoryMethod(null);
SimpleInstantiationStrategy.setCurrentlyInvokedFactoryMethod(priorInvokedFactoryMethod);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -58,39 +58,39 @@ public class BeanRegistrationCodeFragmentsDecorator implements BeanRegistrationC
public CodeBlock generateNewBeanDefinitionCode(GenerationContext generationContext,
ResolvableType beanType, BeanRegistrationCode beanRegistrationCode) {
return this.delegate.generateNewBeanDefinitionCode(generationContext,
beanType, beanRegistrationCode);
return this.delegate.generateNewBeanDefinitionCode(generationContext, beanType, beanRegistrationCode);
}
@Override
public CodeBlock generateSetBeanDefinitionPropertiesCode(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode, RootBeanDefinition beanDefinition,
Predicate<String> attributeFilter) {
public CodeBlock generateSetBeanDefinitionPropertiesCode(
GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode,
RootBeanDefinition beanDefinition, Predicate<String> attributeFilter) {
return this.delegate.generateSetBeanDefinitionPropertiesCode(
generationContext, beanRegistrationCode, beanDefinition, attributeFilter);
}
@Override
public CodeBlock generateSetBeanInstanceSupplierCode(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode, CodeBlock instanceSupplierCode,
List<MethodReference> postProcessors) {
public CodeBlock generateSetBeanInstanceSupplierCode(
GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode,
CodeBlock instanceSupplierCode, List<MethodReference> postProcessors) {
return this.delegate.generateSetBeanInstanceSupplierCode(generationContext,
beanRegistrationCode, instanceSupplierCode, postProcessors);
}
@Override
public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode, boolean allowDirectSupplierShortcut) {
public CodeBlock generateInstanceSupplierCode(
GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode,
boolean allowDirectSupplierShortcut) {
return this.delegate.generateInstanceSupplierCode(generationContext,
beanRegistrationCode, allowDirectSupplierShortcut);
}
@Override
public CodeBlock generateReturnCode(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode) {
public CodeBlock generateReturnCode(
GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) {
return this.delegate.generateReturnCode(generationContext, beanRegistrationCode);
}
@@ -46,8 +46,7 @@ import org.springframework.util.ClassUtils;
import org.springframework.util.function.SingletonSupplier;
/**
* Internal {@link BeanRegistrationCodeFragments} implementation used by
* default.
* Internal {@link BeanRegistrationCodeFragments} implementation used by default.
*
* @author Phillip Webb
* @author Stephane Nicoll
@@ -81,7 +80,8 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
if (hasInstanceSupplier()) {
String resourceDescription = registeredBean.getMergedBeanDefinition().getResourceDescription();
throw new IllegalStateException("Error processing bean with name '" + registeredBean.getBeanName() + "'" +
(resourceDescription != null ? " defined in " + resourceDescription : "") + ": instance supplier is not supported");
(resourceDescription != null ? " defined in " + resourceDescription : "") +
": instance supplier is not supported");
}
Class<?> target = extractDeclaringClass(registeredBean, this.instantiationDescriptor.get());
while (target.getName().startsWith("java.") && registeredBean.isInnerBean()) {
@@ -94,9 +94,8 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
private Class<?> extractDeclaringClass(RegisteredBean registeredBean, InstantiationDescriptor instantiationDescriptor) {
Class<?> declaringClass = ClassUtils.getUserClass(instantiationDescriptor.targetClass());
if (instantiationDescriptor.executable() instanceof Constructor<?>
&& AccessControl.forMember(instantiationDescriptor.executable()).isPublic()
&& FactoryBean.class.isAssignableFrom(declaringClass)) {
if (instantiationDescriptor.executable() instanceof Constructor<?> ctor &&
AccessControl.forMember(ctor).isPublic() && FactoryBean.class.isAssignableFrom(declaringClass)) {
return extractTargetClassFromFactoryBean(declaringClass, registeredBean.getBeanType());
}
return declaringClass;
@@ -105,8 +104,7 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
/**
* Extract the target class of a public {@link FactoryBean} based on its
* constructor. If the implementation does not resolve the target class
* because it itself uses a generic, attempt to extract it from the
* bean type.
* because it itself uses a generic, attempt to extract it from the bean type.
* @param factoryBeanType the factory bean type
* @param beanType the bean type
* @return the target class to use
@@ -127,17 +125,15 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
ResolvableType beanType, BeanRegistrationCode beanRegistrationCode) {
CodeBlock.Builder code = CodeBlock.builder();
RootBeanDefinition mergedBeanDefinition = this.registeredBean.getMergedBeanDefinition();
Class<?> beanClass = (mergedBeanDefinition.hasBeanClass()
? ClassUtils.getUserClass(mergedBeanDefinition.getBeanClass()) : null);
RootBeanDefinition mbd = this.registeredBean.getMergedBeanDefinition();
Class<?> beanClass = (mbd.hasBeanClass() ? ClassUtils.getUserClass(mbd.getBeanClass()) : null);
CodeBlock beanClassCode = generateBeanClassCode(
beanRegistrationCode.getClassName().packageName(),
(beanClass != null ? beanClass : beanType.toClass()));
code.addStatement("$T $L = new $T($L)", RootBeanDefinition.class,
BEAN_DEFINITION_VARIABLE, RootBeanDefinition.class, beanClassCode);
if (targetTypeNecessary(beanType, beanClass)) {
code.addStatement("$L.setTargetType($L)", BEAN_DEFINITION_VARIABLE,
generateBeanTypeCode(beanType));
code.addStatement("$L.setTargetType($L)", BEAN_DEFINITION_VARIABLE, generateBeanTypeCode(beanType));
}
return code.build();
}
@@ -162,8 +158,7 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
if (beanType.hasGenerics()) {
return true;
}
if (beanClass != null
&& this.registeredBean.getMergedBeanDefinition().getFactoryMethodName() != null) {
if (beanClass != null && this.registeredBean.getMergedBeanDefinition().getFactoryMethodName() != null) {
return true;
}
return (beanClass != null && !beanType.toClass().equals(beanClass));
@@ -171,21 +166,21 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
@Override
public CodeBlock generateSetBeanDefinitionPropertiesCode(
GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode, RootBeanDefinition beanDefinition,
Predicate<String> attributeFilter) {
GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode,
RootBeanDefinition beanDefinition, Predicate<String> attributeFilter) {
Loader loader = AotServices.factories(this.registeredBean.getBeanFactory().getBeanClassLoader());
List<Delegate> additionalDelegates = loader.load(Delegate.class).asList();
return new BeanDefinitionPropertiesCodeGenerator(generationContext.getRuntimeHints(),
attributeFilter, beanRegistrationCode.getMethods(),
additionalDelegates, (name, value) -> generateValueCode(generationContext, name, value)
).generateCode(beanDefinition);
return new BeanDefinitionPropertiesCodeGenerator(
generationContext.getRuntimeHints(), attributeFilter,
beanRegistrationCode.getMethods(), additionalDelegates,
(name, value) -> generateValueCode(generationContext, name, value))
.generateCode(beanDefinition);
}
@Nullable
protected CodeBlock generateValueCode(GenerationContext generationContext,
String name, Object value) {
protected CodeBlock generateValueCode(GenerationContext generationContext, String name, Object value) {
RegisteredBean innerRegisteredBean = getInnerRegisteredBean(value);
if (innerRegisteredBean != null) {
BeanDefinitionMethodGenerator methodGenerator = this.beanDefinitionMethodGeneratorFactory
@@ -211,9 +206,8 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
@Override
public CodeBlock generateSetBeanInstanceSupplierCode(
GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode, CodeBlock instanceSupplierCode,
List<MethodReference> postProcessors) {
GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode,
CodeBlock instanceSupplierCode, List<MethodReference> postProcessors) {
CodeBlock.Builder code = CodeBlock.builder();
if (postProcessors.isEmpty()) {
@@ -233,20 +227,22 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme
}
@Override
public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode, boolean allowDirectSupplierShortcut) {
public CodeBlock generateInstanceSupplierCode(
GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode,
boolean allowDirectSupplierShortcut) {
if (hasInstanceSupplier()) {
throw new IllegalStateException("Default code generation is not supported for bean definitions declaring "
+ "an instance supplier callback: " + this.registeredBean.getMergedBeanDefinition());
throw new IllegalStateException("Default code generation is not supported for bean definitions " +
"declaring an instance supplier callback: " + this.registeredBean.getMergedBeanDefinition());
}
return new InstanceSupplierCodeGenerator(generationContext, beanRegistrationCode.getClassName(),
beanRegistrationCode.getMethods(), allowDirectSupplierShortcut).generateCode(
this.registeredBean, this.instantiationDescriptor.get());
return new InstanceSupplierCodeGenerator(generationContext,
beanRegistrationCode.getClassName(), beanRegistrationCode.getMethods(), allowDirectSupplierShortcut)
.generateCode(this.registeredBean, this.instantiationDescriptor.get());
}
@Override
public CodeBlock generateReturnCode(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode) {
public CodeBlock generateReturnCode(
GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) {
CodeBlock.Builder code = CodeBlock.builder();
code.addStatement("return $L", BEAN_DEFINITION_VARIABLE);
@@ -813,10 +813,20 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
// Common return type found: all factory methods return same type. For a non-parameterized
// unique candidate, cache the full type declaration context of the target factory method.
cachedReturnType = (uniqueCandidate != null ?
ResolvableType.forMethodReturnType(uniqueCandidate) : ResolvableType.forClass(commonType));
mbd.factoryMethodReturnType = cachedReturnType;
return cachedReturnType.resolve();
try {
cachedReturnType = (uniqueCandidate != null ?
ResolvableType.forMethodReturnType(uniqueCandidate) : ResolvableType.forClass(commonType));
mbd.factoryMethodReturnType = cachedReturnType;
return cachedReturnType.resolve();
}
catch (LinkageError err) {
// E.g. a NoClassDefFoundError for a generic method return type
if (logger.isDebugEnabled()) {
logger.debug("Failed to resolve type for factory method of bean '" + beanName + "': " +
(uniqueCandidate != null ? uniqueCandidate : commonType), err);
}
return null;
}
}
/**
@@ -833,10 +843,18 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
*/
@Override
protected ResolvableType getTypeForFactoryBean(String beanName, RootBeanDefinition mbd, boolean allowInit) {
ResolvableType result;
// Check if the bean definition itself has defined the type with an attribute
ResolvableType result = getTypeForFactoryBeanFromAttributes(mbd);
if (result != ResolvableType.NONE) {
return result;
try {
result = getTypeForFactoryBeanFromAttributes(mbd);
if (result != ResolvableType.NONE) {
return result;
}
}
catch (IllegalArgumentException ex) {
throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName,
String.valueOf(ex.getMessage()));
}
// For instance supplied beans, try the target type and bean class immediately
@@ -1716,9 +1716,15 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
* @see #getBean(String)
*/
protected ResolvableType getTypeForFactoryBean(String beanName, RootBeanDefinition mbd, boolean allowInit) {
ResolvableType result = getTypeForFactoryBeanFromAttributes(mbd);
if (result != ResolvableType.NONE) {
return result;
try {
ResolvableType result = getTypeForFactoryBeanFromAttributes(mbd);
if (result != ResolvableType.NONE) {
return result;
}
}
catch (IllegalArgumentException ex) {
throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName,
String.valueOf(ex.getMessage()));
}
if (allowInit && mbd.isSingleton()) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -55,12 +55,18 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy {
}
/**
* Set the factory method currently being invoked or {@code null} to reset.
* Set the factory method currently being invoked or {@code null} to remove
* the current value, if any.
* @param method the factory method currently being invoked or {@code null}
* @since 6.0
*/
public static void setCurrentlyInvokedFactoryMethod(@Nullable Method method) {
currentlyInvokedFactoryMethod.set(method);
if (method != null) {
currentlyInvokedFactoryMethod.set(method);
}
else {
currentlyInvokedFactoryMethod.remove();
}
}
@@ -134,9 +140,9 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy {
try {
ReflectionUtils.makeAccessible(factoryMethod);
Method priorInvokedFactoryMethod = currentlyInvokedFactoryMethod.get();
Method priorInvokedFactoryMethod = getCurrentlyInvokedFactoryMethod();
try {
currentlyInvokedFactoryMethod.set(factoryMethod);
setCurrentlyInvokedFactoryMethod(factoryMethod);
Object result = factoryMethod.invoke(factoryBean, args);
if (result == null) {
result = new NullBean();
@@ -144,12 +150,7 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy {
return result;
}
finally {
if (priorInvokedFactoryMethod != null) {
currentlyInvokedFactoryMethod.set(priorInvokedFactoryMethod);
}
else {
currentlyInvokedFactoryMethod.remove();
}
setCurrentlyInvokedFactoryMethod(priorInvokedFactoryMethod);
}
}
catch (IllegalArgumentException ex) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -92,9 +92,9 @@ public class PathEditor extends PropertyEditorSupport {
// a file prefix (let's try as Spring resource location)
nioPathCandidate = !text.startsWith(ResourceUtils.FILE_URL_PREFIX);
}
catch (FileSystemNotFoundException ex) {
// URI scheme not registered for NIO (let's try URL
// protocol handlers via Spring's resource mechanism).
catch (FileSystemNotFoundException | IllegalArgumentException ex) {
// URI scheme not registered for NIO or not meeting Paths requirements:
// let's try URL protocol handlers via Spring's resource mechanism.
}
}
@@ -111,8 +111,13 @@ public class PathEditor extends PropertyEditorSupport {
setValue(resource.getFile().toPath());
}
catch (IOException ex) {
throw new IllegalArgumentException(
"Could not retrieve file for " + resource + ": " + ex.getMessage());
String msg = "Could not resolve \"" + text + "\" to 'java.nio.file.Path' for " + resource + ": " +
ex.getMessage();
if (nioPathCandidate) {
msg += " - In case of ambiguity, consider adding the 'file:' prefix for an explicit reference " +
"to a file system resource of the same name: \"file:" + text + "\"";
}
throw new IllegalArgumentException(msg);
}
}
}
@@ -42,6 +42,8 @@ import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.beans.testfixture.beans.DerivedTestBean;
import org.springframework.beans.testfixture.beans.ITestBean;
import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceEditor;
import org.springframework.lang.Nullable;
@@ -322,12 +324,13 @@ class BeanUtilsTests {
Order original = new Order("test", List.of("foo", "bar"));
// Create a Proxy that loses the generic type information for the getLineItems() method.
OrderSummary proxy = proxyOrder(original);
OrderSummary proxy = (OrderSummary) Proxy.newProxyInstance(getClass().getClassLoader(),
new Class<?>[] {OrderSummary.class}, new OrderInvocationHandler(original));
assertThat(OrderSummary.class.getDeclaredMethod("getLineItems").toGenericString())
.contains("java.util.List<java.lang.String>");
.contains("java.util.List<java.lang.String>");
assertThat(proxy.getClass().getDeclaredMethod("getLineItems").toGenericString())
.contains("java.util.List")
.doesNotContain("<java.lang.String>");
.contains("java.util.List")
.doesNotContain("<java.lang.String>");
// Ensure that our custom Proxy works as expected.
assertThat(proxy.getId()).isEqualTo("test");
@@ -340,6 +343,23 @@ class BeanUtilsTests {
assertThat(target.getLineItems()).containsExactly("foo", "bar");
}
@Test // gh-32888
public void copyPropertiesWithGenericCglibClass() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(User.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> proxy.invokeSuper(obj, args));
User user = (User) enhancer.create();
user.setId(1);
user.setName("proxy");
user.setAddress("addr");
User target = new User();
BeanUtils.copyProperties(user, target);
assertThat(target.getId()).isEqualTo(user.getId());
assertThat(target.getName()).isEqualTo(user.getName());
assertThat(target.getAddress()).isEqualTo(user.getAddress());
}
@Test
void copyPropertiesWithEditable() throws Exception {
TestBean tb = new TestBean();
@@ -520,6 +540,7 @@ class BeanUtilsTests {
}
}
@SuppressWarnings("unused")
private static class IntegerHolder {
@@ -534,6 +555,7 @@ class BeanUtilsTests {
}
}
@SuppressWarnings("unused")
private static class WildcardListHolder1 {
@@ -548,6 +570,7 @@ class BeanUtilsTests {
}
}
@SuppressWarnings("unused")
private static class WildcardListHolder2 {
@@ -562,6 +585,7 @@ class BeanUtilsTests {
}
}
@SuppressWarnings("unused")
private static class NumberUpperBoundedWildcardListHolder {
@@ -576,6 +600,7 @@ class BeanUtilsTests {
}
}
@SuppressWarnings("unused")
private static class NumberListHolder {
@@ -590,6 +615,7 @@ class BeanUtilsTests {
}
}
@SuppressWarnings("unused")
private static class IntegerListHolder1 {
@@ -604,6 +630,7 @@ class BeanUtilsTests {
}
}
@SuppressWarnings("unused")
private static class IntegerListHolder2 {
@@ -618,6 +645,7 @@ class BeanUtilsTests {
}
}
@SuppressWarnings("unused")
private static class LongListHolder {
@@ -798,6 +826,7 @@ class BeanUtilsTests {
}
}
private static class BeanWithNullableTypes {
private Integer counter;
@@ -828,6 +857,7 @@ class BeanUtilsTests {
}
}
private static class BeanWithPrimitiveTypes {
private boolean flag;
@@ -840,7 +870,6 @@ class BeanUtilsTests {
private char character;
private String text;
@SuppressWarnings("unused")
public BeanWithPrimitiveTypes(boolean flag, byte byteCount, short shortCount, int intCount, long longCount,
float floatCount, double doubleCount, char character, String text) {
@@ -891,21 +920,22 @@ class BeanUtilsTests {
public String getText() {
return text;
}
}
private static class PrivateBeanWithPrivateConstructor {
private PrivateBeanWithPrivateConstructor() {
}
}
@SuppressWarnings("unused")
private static class Order {
private String id;
private List<String> lineItems;
private List<String> lineItems;
Order() {
}
@@ -937,6 +967,7 @@ class BeanUtilsTests {
}
}
private interface OrderSummary {
String getId();
@@ -945,17 +976,10 @@ class BeanUtilsTests {
}
private OrderSummary proxyOrder(Order order) {
return (OrderSummary) Proxy.newProxyInstance(getClass().getClassLoader(),
new Class<?>[] { OrderSummary.class }, new OrderInvocationHandler(order));
}
private static class OrderInvocationHandler implements InvocationHandler {
private final Order order;
OrderInvocationHandler(Order order) {
this.order = order;
}
@@ -973,4 +997,46 @@ class BeanUtilsTests {
}
}
private static class GenericBaseModel<T> {
private T id;
private String name;
public T getId() {
return id;
}
public void setId(T id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
private static class User extends GenericBaseModel<Integer> {
private String address;
public User() {
super();
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.stream.Stream;
@@ -51,6 +52,7 @@ import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.InstanceSupplier;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.support.SimpleInstantiationStrategy;
import org.springframework.core.env.Environment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
@@ -292,6 +294,33 @@ class BeanInstanceSupplierTests {
assertThat(instance).isEqualTo("1");
}
@Test // gh-33180
void getWithNestedInvocationRetainsFactoryMethod() throws Exception {
AtomicReference<Method> testMethodReference = new AtomicReference<>();
AtomicReference<Method> anotherMethodReference = new AtomicReference<>();
BeanInstanceSupplier<Object> nestedInstanceSupplier = BeanInstanceSupplier
.forFactoryMethod(AnotherTestStringFactory.class, "another")
.withGenerator(registeredBean -> {
anotherMethodReference.set(SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod());
return "Another";
});
RegisteredBean nestedRegisteredBean = new Source(String.class, nestedInstanceSupplier).registerBean(this.beanFactory);
BeanInstanceSupplier<Object> instanceSupplier = BeanInstanceSupplier
.forFactoryMethod(TestStringFactory.class, "test")
.withGenerator(registeredBean -> {
Object nested = nestedInstanceSupplier.get(nestedRegisteredBean);
testMethodReference.set(SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod());
return "custom" + nested;
});
RegisteredBean registeredBean = new Source(String.class, instanceSupplier).registerBean(this.beanFactory);
Object value = instanceSupplier.get(registeredBean);
assertThat(value).isEqualTo("customAnother");
assertThat(testMethodReference.get()).isEqualTo(instanceSupplier.getFactoryMethod());
assertThat(anotherMethodReference.get()).isEqualTo(nestedInstanceSupplier.getFactoryMethod());
}
@Test
void resolveArgumentsWithNoArgConstructor() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(
@@ -1030,4 +1059,18 @@ class BeanInstanceSupplierTests {
}
static class TestStringFactory {
String test() {
return "test";
}
}
static class AnotherTestStringFactory {
String another() {
return "another";
}
}
}
@@ -21,9 +21,7 @@ import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
import org.yaml.snakeyaml.composer.ComposerException;
import org.yaml.snakeyaml.parser.ParserException;
@@ -34,6 +32,7 @@ import org.springframework.core.io.ByteArrayResource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.entry;
import static org.assertj.core.api.InstanceOfAssertFactories.set;
/**
* Tests for {@link YamlProcessor}.
@@ -142,13 +141,11 @@ class YamlProcessorTests {
}
@Test
@SuppressWarnings("unchecked")
void standardTypesSupportedByDefault() {
setYaml("value: !!set\n ? first\n ? second");
this.processor.process((properties, map) -> {
assertThat(properties).containsExactly(entry("value[0]", "first"), entry("value[1]", "second"));
assertThat(map.get("value")).asInstanceOf(InstanceOfAssertFactories.type(Set.class))
.satisfies(set -> assertThat(set).containsExactly("first", "second"));
assertThat(map.get("value")).asInstanceOf(set(String.class)).containsExactly("first", "second");
});
}
@@ -46,9 +46,9 @@ class FileEditorTests {
@Test
void testWithNonExistentResource() {
PropertyEditor propertyEditor = new FileEditor();
PropertyEditor fileEditor = new FileEditor();
assertThatIllegalArgumentException().isThrownBy(() ->
propertyEditor.setAsText("classpath:no_way_this_file_is_found.doc"));
fileEditor.setAsText("classpath:no_way_this_file_is_found.doc"));
}
@Test
@@ -71,6 +71,16 @@ class FileEditorTests {
assertThat(file).doesNotExist();
}
@Test
void testCurrentDirectory() {
PropertyEditor fileEditor = new FileEditor();
fileEditor.setAsText("file:.");
Object value = fileEditor.getValue();
assertThat(value).isInstanceOf(File.class);
File file = (File) value;
assertThat(file).isEqualTo(new File("."));
}
@Test
void testUnqualifiedFileNameFound() {
PropertyEditor fileEditor = new FileEditor();
@@ -19,6 +19,7 @@ package org.springframework.beans.propertyeditors;
import java.beans.PropertyEditor;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.junit.jupiter.api.Test;
@@ -46,9 +47,9 @@ class PathEditorTests {
@Test
void testWithNonExistentResource() {
PropertyEditor propertyEditor = new PathEditor();
PropertyEditor pathEditor = new PathEditor();
assertThatIllegalArgumentException().isThrownBy(() ->
propertyEditor.setAsText("classpath:/no_way_this_file_is_found.doc"));
pathEditor.setAsText("classpath:/no_way_this_file_is_found.doc"));
}
@Test
@@ -98,6 +99,16 @@ class PathEditorTests {
}
}
@Test
void testCurrentDirectory() {
PropertyEditor pathEditor = new PathEditor();
pathEditor.setAsText("file:.");
Object value = pathEditor.getValue();
assertThat(value).isInstanceOf(Path.class);
Path path = (Path) value;
assertThat(path).isEqualTo(Paths.get("."));
}
@Test
void testUnqualifiedPathNameFound() {
PropertyEditor pathEditor = new PathEditor();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -41,9 +41,11 @@ import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
/**
* Factory that configures a FreeMarker Configuration. Can be used standalone, but
* typically you will either use FreeMarkerConfigurationFactoryBean for preparing a
* Configuration as bean reference, or FreeMarkerConfigurer for web views.
* Factory that configures a FreeMarker {@link Configuration}.
*
* <p>Can be used standalone, but typically you will either use
* {@link FreeMarkerConfigurationFactoryBean} for preparing a {@code Configuration}
* as a bean reference, or {@code FreeMarkerConfigurer} for web views.
*
* <p>The optional "configLocation" property sets the location of a FreeMarker
* properties file, within the current application. FreeMarker properties can be
@@ -52,17 +54,18 @@ import org.springframework.util.CollectionUtils;
* subject to constraints set by FreeMarker.
*
* <p>The "freemarkerVariables" property can be used to specify a Map of
* shared variables that will be applied to the Configuration via the
* shared variables that will be applied to the {@code Configuration} via the
* {@code setAllSharedVariables()} method. Like {@code setSettings()},
* these entries are subject to FreeMarker constraints.
*
* <p>The simplest way to use this class is to specify a "templateLoaderPath";
* FreeMarker does not need any further configuration then.
*
* <p>Note: Spring's FreeMarker support requires FreeMarker 2.3 or higher.
* <p>Note: Spring's FreeMarker support requires FreeMarker 2.3.21 or higher.
*
* @author Darren Davison
* @author Juergen Hoeller
* @author Sam Brannen
* @since 03.03.2004
* @see #setConfigLocation
* @see #setFreemarkerSettings
@@ -107,7 +110,7 @@ public class FreeMarkerConfigurationFactory {
/**
* Set the location of the FreeMarker config file.
* Alternatively, you can specify all setting locally.
* <p>Alternatively, you can specify all settings locally.
* @see #setFreemarkerSettings
* @see #setTemplateLoaderPath
*/
@@ -134,25 +137,33 @@ public class FreeMarkerConfigurationFactory {
}
/**
* Set the default encoding for the FreeMarker configuration.
* If not specified, FreeMarker will use the platform file encoding.
* <p>Used for template rendering unless there is an explicit encoding specified
* for the rendering process (for example, on Spring's FreeMarkerView).
* Set the default encoding for the FreeMarker {@link Configuration}, which
* is used to decode byte sequences to character sequences when reading template
* files.
* <p>If not specified, FreeMarker will read template files using the platform
* file encoding (defined by the JVM system property {@code file.encoding})
* or {@code "utf-8"} if the platform file encoding is undefined.
* <p>Note that the encoding is not used for template rendering. Instead, an
* explicit encoding must be specified for the rendering process &mdash; for
* example, via Spring's {@code FreeMarkerView} or {@code FreeMarkerViewResolver}.
* @see freemarker.template.Configuration#setDefaultEncoding
* @see org.springframework.web.servlet.view.freemarker.FreeMarkerView#setEncoding
* @see org.springframework.web.servlet.view.freemarker.FreeMarkerView#setContentType
* @see org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver#setContentType
*/
public void setDefaultEncoding(String defaultEncoding) {
this.defaultEncoding = defaultEncoding;
}
/**
* Set a List of {@code TemplateLoader}s that will be used to search
* for templates. For example, one or more custom loaders such as database
* loaders could be configured and injected here.
* <p>The {@link TemplateLoader TemplateLoaders} specified here will be
* registered <i>before</i> the default template loaders that this factory
* registers (such as loaders for specified "templateLoaderPaths" or any
* loaders registered in {@link #postProcessTemplateLoaders}).
* Set a List of {@link TemplateLoader TemplateLoaders} that will be used to
* search for templates.
* <p>For example, one or more custom loaders such as database loaders could
* be configured and injected here.
* <p>The {@code TemplateLoaders} specified here will be registered <i>before</i>
* the default template loaders that this factory registers (such as loaders
* for specified "templateLoaderPaths" or any loaders registered in
* {@link #postProcessTemplateLoaders}).
* @see #setTemplateLoaderPaths
* @see #postProcessTemplateLoaders
*/
@@ -161,13 +172,14 @@ public class FreeMarkerConfigurationFactory {
}
/**
* Set a List of {@code TemplateLoader}s that will be used to search
* for templates. For example, one or more custom loaders such as database
* loaders can be configured.
* <p>The {@link TemplateLoader TemplateLoaders} specified here will be
* registered <i>after</i> the default template loaders that this factory
* registers (such as loaders for specified "templateLoaderPaths" or any
* loaders registered in {@link #postProcessTemplateLoaders}).
* Set a List of {@link TemplateLoader TemplateLoaders} that will be used to
* search for templates.
* <p>For example, one or more custom loaders such as database loaders could
* be configured and injected here.
* <p>The {@code TemplateLoaders} specified here will be registered <i>after</i>
* the default template loaders that this factory registers (such as loaders
* for specified "templateLoaderPaths" or any loaders registered in
* {@link #postProcessTemplateLoaders}).
* @see #setTemplateLoaderPaths
* @see #postProcessTemplateLoaders
*/
@@ -177,7 +189,7 @@ public class FreeMarkerConfigurationFactory {
/**
* Set the Freemarker template loader path via a Spring resource location.
* See the "templateLoaderPaths" property for details on path handling.
* <p>See the "templateLoaderPaths" property for details on path handling.
* @see #setTemplateLoaderPaths
*/
public void setTemplateLoaderPath(String templateLoaderPath) {
@@ -188,28 +200,29 @@ public class FreeMarkerConfigurationFactory {
* Set multiple Freemarker template loader paths via Spring resource locations.
* <p>When populated via a String, standard URLs like "file:" and "classpath:"
* pseudo URLs are supported, as understood by ResourceEditor. Allows for
* relative paths when running in an ApplicationContext.
* <p>Will define a path for the default FreeMarker template loader.
* If a specified resource cannot be resolved to a {@code java.io.File},
* a generic SpringTemplateLoader will be used, without modification detection.
* <p>To enforce the use of SpringTemplateLoader, i.e. to not resolve a path
* as file system resource in any case, turn off the "preferFileSystemAccess"
* relative paths when running in an {@code ApplicationContext}.
* <p>Will define a path for the default FreeMarker template loader. If a
* specified resource cannot be resolved to a {@code java.io.File}, a generic
* {@link SpringTemplateLoader} will be used, without modification detection.
* <p>To enforce the use of {@code SpringTemplateLoader}, i.e. to not resolve
* a path as file system resource in any case, turn off the "preferFileSystemAccess"
* flag. See the latter's javadoc for details.
* <p>If you wish to specify your own list of TemplateLoaders, do not set this
* property and instead use {@code setTemplateLoaders(List templateLoaders)}
* property and instead use {@link #setPostTemplateLoaders(TemplateLoader...)}.
* @see org.springframework.core.io.ResourceEditor
* @see org.springframework.context.ApplicationContext#getResource
* @see freemarker.template.Configuration#setDirectoryForTemplateLoading
* @see SpringTemplateLoader
* @see #setPreferFileSystemAccess(boolean)
*/
public void setTemplateLoaderPaths(String... templateLoaderPaths) {
this.templateLoaderPaths = templateLoaderPaths;
}
/**
* Set the Spring ResourceLoader to use for loading FreeMarker template files.
* The default is DefaultResourceLoader. Will get overridden by the
* ApplicationContext if running in a context.
* Set the {@link ResourceLoader} to use for loading FreeMarker template files.
* <p>The default is {@link DefaultResourceLoader}. Will get overridden by the
* {@code ApplicationContext} if running in a context.
* @see org.springframework.core.io.DefaultResourceLoader
*/
public void setResourceLoader(ResourceLoader resourceLoader) {
@@ -217,7 +230,7 @@ public class FreeMarkerConfigurationFactory {
}
/**
* Return the Spring ResourceLoader to use for loading FreeMarker template files.
* Return the {@link ResourceLoader} to use for loading FreeMarker template files.
*/
protected ResourceLoader getResourceLoader() {
return this.resourceLoader;
@@ -225,11 +238,11 @@ public class FreeMarkerConfigurationFactory {
/**
* Set whether to prefer file system access for template loading.
* File system access enables hot detection of template changes.
* <p>File system access enables hot detection of template changes.
* <p>If this is enabled, FreeMarkerConfigurationFactory will try to resolve
* the specified "templateLoaderPath" as file system resource (which will work
* for expanded class path resources and ServletContext resources too).
* <p>Default is "true". Turn this off to always load via SpringTemplateLoader
* <p>Default is "true". Turn this off to always load via {@link SpringTemplateLoader}
* (i.e. as stream, without hot detection of template changes), which might
* be necessary if some of your templates reside in an expanded classes
* directory while others reside in jar files.
@@ -248,8 +261,8 @@ public class FreeMarkerConfigurationFactory {
/**
* Prepare the FreeMarker Configuration and return it.
* @return the FreeMarker Configuration object
* Prepare the FreeMarker {@link Configuration} and return it.
* @return the FreeMarker {@code Configuration} object
* @throws IOException if the config file wasn't found
* @throws TemplateException on FreeMarker initialization failure
*/
@@ -314,11 +327,12 @@ public class FreeMarkerConfigurationFactory {
}
/**
* Return a new Configuration object. Subclasses can override this for custom
* initialization (e.g. specifying a FreeMarker compatibility level which is a
* new feature in FreeMarker 2.3.21), or for using a mock object for testing.
* <p>Called by {@code createConfiguration()}.
* @return the Configuration object
* Return a new {@link Configuration} object.
* <p>Subclasses can override this for custom initialization &mdash; for example,
* to specify a FreeMarker compatibility level (which is a new feature in
* FreeMarker 2.3.21), or to use a mock object for testing.
* <p>Called by {@link #createConfiguration()}.
* @return the {@code Configuration} object
* @throws IOException if a config file wasn't found
* @throws TemplateException on FreeMarker initialization failure
* @see #createConfiguration()
@@ -328,11 +342,11 @@ public class FreeMarkerConfigurationFactory {
}
/**
* Determine a FreeMarker TemplateLoader for the given path.
* <p>Default implementation creates either a FileTemplateLoader or
* a SpringTemplateLoader.
* Determine a FreeMarker {@link TemplateLoader} for the given path.
* <p>Default implementation creates either a {@link FileTemplateLoader} or
* a {@link SpringTemplateLoader}.
* @param templateLoaderPath the path to load templates from
* @return an appropriate TemplateLoader
* @return an appropriate {@code TemplateLoader}
* @see freemarker.cache.FileTemplateLoader
* @see SpringTemplateLoader
*/
@@ -366,9 +380,9 @@ public class FreeMarkerConfigurationFactory {
/**
* To be overridden by subclasses that want to register custom
* TemplateLoader instances after this factory created its default
* {@link TemplateLoader} instances after this factory created its default
* template loaders.
* <p>Called by {@code createConfiguration()}. Note that specified
* <p>Called by {@link #createConfiguration()}. Note that specified
* "postTemplateLoaders" will be registered <i>after</i> any loaders
* registered by this callback; as a consequence, they are <i>not</i>
* included in the given List.
@@ -381,10 +395,10 @@ public class FreeMarkerConfigurationFactory {
}
/**
* Return a TemplateLoader based on the given TemplateLoader list.
* If more than one TemplateLoader has been registered, a FreeMarker
* MultiTemplateLoader needs to be created.
* @param templateLoaders the final List of TemplateLoader instances
* Return a {@link TemplateLoader} based on the given {@code TemplateLoader} list.
* <p>If more than one TemplateLoader has been registered, a FreeMarker
* {@link MultiTemplateLoader} will be created.
* @param templateLoaders the final List of {@code TemplateLoader} instances
* @return the aggregate TemplateLoader
*/
@Nullable
@@ -404,10 +418,10 @@ public class FreeMarkerConfigurationFactory {
/**
* To be overridden by subclasses that want to perform custom
* post-processing of the Configuration object after this factory
* post-processing of the {@link Configuration} object after this factory
* performed its default initialization.
* <p>Called by {@code createConfiguration()}.
* @param config the current Configuration object
* <p>Called by {@link #createConfiguration()}.
* @param config the current {@code Configuration} object
* @throws IOException if a config file wasn't found
* @throws TemplateException on FreeMarker initialization failure
* @see #createConfiguration()
@@ -27,22 +27,25 @@ import org.springframework.context.ResourceLoaderAware;
import org.springframework.lang.Nullable;
/**
* Factory bean that creates a FreeMarker Configuration and provides it as
* bean reference. This bean is intended for any kind of usage of FreeMarker
* in application code, e.g. for generating email content. For web views,
* FreeMarkerConfigurer is used to set up a FreeMarkerConfigurationFactory.
* <p>
* The simplest way to use this class is to specify just a "templateLoaderPath";
* Factory bean that creates a FreeMarker {@link Configuration} and provides it
* as a bean reference.
*
* <p>This bean is intended for any kind of usage of FreeMarker in application
* code &mdash; for example, for generating email content. For web views,
* {@code FreeMarkerConfigurer} is used to set up a {@link FreeMarkerConfigurationFactory}.
*
* <p>The simplest way to use this class is to specify just a "templateLoaderPath";
* you do not need any further configuration then. For example, in a web
* application context:
*
* <pre class="code"> &lt;bean id="freemarkerConfiguration" class="org.springframework.ui.freemarker.FreeMarkerConfigurationFactoryBean"&gt;
* &lt;property name="templateLoaderPath" value="/WEB-INF/freemarker/"/&gt;
* &lt;/bean&gt;</pre>
* See the base class FreeMarkerConfigurationFactory for configuration details.
*
* <p>Note: Spring's FreeMarker support requires FreeMarker 2.3 or higher.
* <p>See the {@link FreeMarkerConfigurationFactory} base class for configuration
* details.
*
* <p>Note: Spring's FreeMarker support requires FreeMarker 2.3.21 or higher.
*
* @author Darren Davison
* @since 03.03.2004
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,7 +24,8 @@ import freemarker.template.TemplateException;
/**
* Utility class for working with FreeMarker.
* Provides convenience methods to process a FreeMarker template with a model.
*
* <p>Provides convenience methods to process a FreeMarker template with a model.
*
* @author Juergen Hoeller
* @since 14.03.2004
@@ -33,12 +34,12 @@ public abstract class FreeMarkerTemplateUtils {
/**
* Process the specified FreeMarker template with the given model and write
* the result to the given Writer.
* <p>When using this method to prepare a text for a mail to be sent with Spring's
* the result to a String.
* <p>When using this method to prepare text for a mail to be sent with Spring's
* mail support, consider wrapping IO/TemplateException in MailPreparationException.
* @param model the model object, typically a Map that contains model names
* as keys and model objects as values
* @return the result as String
* @return the result as a String
* @throws IOException if the template wasn't found or couldn't be read
* @throws freemarker.template.TemplateException if rendering failed
* @see org.springframework.mail.MailPreparationException
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,9 +29,11 @@ import org.springframework.core.io.ResourceLoader;
import org.springframework.lang.Nullable;
/**
* FreeMarker {@link TemplateLoader} adapter that loads via a Spring {@link ResourceLoader}.
* Used by {@link FreeMarkerConfigurationFactory} for any resource loader path that cannot
* be resolved to a {@link java.io.File}.
* FreeMarker {@link TemplateLoader} adapter that loads template files via a
* Spring {@link ResourceLoader}.
*
* <p>Used by {@link FreeMarkerConfigurationFactory} for any resource loader path
* that cannot be resolved to a {@link java.io.File}.
*
* @author Juergen Hoeller
* @since 14.03.2004
@@ -48,7 +50,7 @@ public class SpringTemplateLoader implements TemplateLoader {
/**
* Create a new SpringTemplateLoader.
* Create a new {@code SpringTemplateLoader}.
* @param resourceLoader the Spring ResourceLoader to use
* @param templateLoaderPath the template loader path to use
*/
@@ -503,7 +503,10 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
if (CompletableFuture.class.isAssignableFrom(context.getMethod().getReturnType())) {
CompletableFuture<?> result = cache.retrieve(key);
if (result != null) {
return result.thenCompose(value -> (CompletableFuture<?>) evaluate(
return result.exceptionally(ex -> {
getErrorHandler().handleCacheGetError((RuntimeException) ex, cache, key);
return null;
}).thenCompose(value -> (CompletableFuture<?>) evaluate(
(value != null ? CompletableFuture.completedFuture(unwrapCacheValue(value)) : null),
invoker, method, contexts));
}
@@ -630,7 +633,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
if (result instanceof CompletableFuture<?> future) {
return future.whenComplete((value, ex) -> {
if (ex == null) {
performCacheEvicts(applicable, result);
performCacheEvicts(applicable, value);
}
});
}
@@ -1112,7 +1115,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
ReactiveAdapter adapter = (result != null ? this.registry.getAdapter(result.getClass()) : null);
if (adapter != null) {
return adapter.fromPublisher(Mono.from(adapter.toPublisher(result))
.doOnSuccess(value -> performCacheEvicts(contexts, result)));
.doOnSuccess(value -> performCacheEvicts(contexts, value)));
}
return NOT_HANDLED;
}
@@ -1131,12 +1134,30 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
if (adapter.isMultiValue()) {
return adapter.fromPublisher(Flux.from(Mono.fromFuture(cachedFuture))
.switchIfEmpty(Flux.defer(() -> (Flux) evaluate(null, invoker, method, contexts)))
.flatMap(v -> evaluate(valueToFlux(v, contexts), invoker, method, contexts)));
.flatMap(v -> evaluate(valueToFlux(v, contexts), invoker, method, contexts))
.onErrorResume(RuntimeException.class, ex -> {
try {
getErrorHandler().handleCacheGetError((RuntimeException) ex, cache, key);
return evaluate(null, invoker, method, contexts);
}
catch (RuntimeException exception) {
return Flux.error(exception);
}
}));
}
else {
return adapter.fromPublisher(Mono.fromFuture(cachedFuture)
.switchIfEmpty(Mono.defer(() -> (Mono) evaluate(null, invoker, method, contexts)))
.flatMap(v -> evaluate(Mono.justOrEmpty(unwrapCacheValue(v)), invoker, method, contexts)));
.flatMap(v -> evaluate(Mono.justOrEmpty(unwrapCacheValue(v)), invoker, method, contexts))
.onErrorResume(RuntimeException.class, ex -> {
try {
getErrorHandler().handleCacheGetError((RuntimeException) ex, cache, key);
return evaluate(null, invoker, method, contexts);
}
catch (RuntimeException exception) {
return Mono.error(exception);
}
}));
}
}
return NOT_HANDLED;
@@ -49,6 +49,7 @@ import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.cglib.proxy.NoOp;
import org.springframework.cglib.transform.ClassEmitterTransformer;
import org.springframework.cglib.transform.TransformingClassGenerator;
import org.springframework.core.SmartClassLoader;
import org.springframework.lang.Nullable;
import org.springframework.objenesis.ObjenesisException;
import org.springframework.objenesis.SpringObjenesis;
@@ -132,13 +133,21 @@ class ConfigurationClassEnhancer {
enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
enhancer.setUseFactory(false);
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setAttemptLoad(true);
enhancer.setAttemptLoad(!isClassReloadable(configSuperClass, classLoader));
enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
enhancer.setCallbackFilter(CALLBACK_FILTER);
enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
return enhancer;
}
/**
* Checks whether the given configuration class is reloadable.
*/
private boolean isClassReloadable(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
return (classLoader instanceof SmartClassLoader smartClassLoader &&
smartClassLoader.isClassReloadable(configSuperClass));
}
/**
* Uses enhancer to generate a subclass of superclass,
* ensuring that callbacks are registered for the new subclass.
@@ -756,12 +756,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
}
private CodeBlock handleNull(@Nullable Object value, Supplier<CodeBlock> nonNull) {
if (value == null) {
return CodeBlock.of("null");
}
else {
return nonNull.get();
}
return (value == null ? CodeBlock.of("null") : nonNull.get());
}
}
@@ -781,8 +776,9 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
}
@Override
public CodeBlock generateSetBeanDefinitionPropertiesCode(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode, RootBeanDefinition beanDefinition, Predicate<String> attributeFilter) {
public CodeBlock generateSetBeanDefinitionPropertiesCode(
GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode,
RootBeanDefinition beanDefinition, Predicate<String> attributeFilter) {
CodeBlock.Builder code = CodeBlock.builder();
code.add(super.generateSetBeanDefinitionPropertiesCode(generationContext,
@@ -793,17 +789,21 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
}
@Override
public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode, boolean allowDirectSupplierShortcut) {
public CodeBlock generateInstanceSupplierCode(
GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode,
boolean allowDirectSupplierShortcut) {
InstantiationDescriptor instantiationDescriptor = proxyInstantiationDescriptor(
generationContext.getRuntimeHints(), this.registeredBean.resolveInstantiationDescriptor());
return new InstanceSupplierCodeGenerator(generationContext,
beanRegistrationCode.getClassName(), beanRegistrationCode.getMethods(), allowDirectSupplierShortcut)
.generateCode(this.registeredBean, instantiationDescriptor);
}
private InstantiationDescriptor proxyInstantiationDescriptor(RuntimeHints runtimeHints, InstantiationDescriptor instantiationDescriptor) {
private InstantiationDescriptor proxyInstantiationDescriptor(
RuntimeHints runtimeHints, InstantiationDescriptor instantiationDescriptor) {
Executable userExecutable = instantiationDescriptor.executable();
if (userExecutable instanceof Constructor<?> userConstructor) {
try {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@ import org.springframework.context.ApplicationEvent;
public abstract class ApplicationContextEvent extends ApplicationEvent {
/**
* Create a new ContextStartedEvent.
* Create a new {@code ApplicationContextEvent}.
* @param source the {@code ApplicationContext} that the event is raised for
* (must not be {@code null})
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@ import org.springframework.context.ApplicationContext;
public class ContextClosedEvent extends ApplicationContextEvent {
/**
* Creates a new ContextClosedEvent.
* Create a new {@code ContextClosedEvent}.
* @param source the {@code ApplicationContext} that has been closed
* (must not be {@code null})
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@ import org.springframework.context.ApplicationContext;
public class ContextRefreshedEvent extends ApplicationContextEvent {
/**
* Create a new ContextRefreshedEvent.
* Create a new {@code ContextRefreshedEvent}.
* @param source the {@code ApplicationContext} that has been initialized
* or refreshed (must not be {@code null})
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,7 +30,7 @@ import org.springframework.context.ApplicationContext;
public class ContextStartedEvent extends ApplicationContextEvent {
/**
* Create a new ContextStartedEvent.
* Create a new {@code ContextStartedEvent}.
* @param source the {@code ApplicationContext} that has been started
* (must not be {@code null})
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,7 +30,7 @@ import org.springframework.context.ApplicationContext;
public class ContextStoppedEvent extends ApplicationContextEvent {
/**
* Create a new ContextStoppedEvent.
* Create a new {@code ContextStoppedEvent}.
* @param source the {@code ApplicationContext} that has been stopped
* (must not be {@code null})
*/
@@ -36,6 +36,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.CachedIntrospectionResults;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanNotOfRequiredTypeException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
@@ -949,7 +950,15 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
// Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
for (String weaverAwareName : weaverAwareNames) {
beanFactory.getBean(weaverAwareName, LoadTimeWeaverAware.class);
try {
beanFactory.getBean(weaverAwareName, LoadTimeWeaverAware.class);
}
catch (BeanNotOfRequiredTypeException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to initialize LoadTimeWeaverAware bean '" + weaverAwareName +
"' due to unexpected type mismatch: " + ex.getMessage());
}
}
}
// Stop using the temporary ClassLoader for type matching.
@@ -60,10 +60,6 @@ import org.springframework.util.ClassUtils;
* groups for specific phases, on startup/shutdown as well as for explicit start/stop
* interactions on a {@link org.springframework.context.ConfigurableApplicationContext}.
*
* <p>Provides interaction with {@link Lifecycle} and {@link SmartLifecycle} beans in
* groups for specific phases, on startup/shutdown as well as for explicit start/stop
* interactions on a {@link org.springframework.context.ConfigurableApplicationContext}.
*
* <p>As of 6.1, this also includes support for JVM checkpoint/restore (Project CRaC)
* when the {@code org.crac:crac} dependency on the classpath.
*
@@ -98,7 +94,7 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor
public static final String ON_REFRESH_VALUE = "onRefresh";
private static final boolean checkpointOnRefresh =
private static boolean checkpointOnRefresh =
ON_REFRESH_VALUE.equalsIgnoreCase(SpringProperties.getProperty(CHECKPOINT_PROPERTY_NAME));
private static final boolean exitOnRefresh =
@@ -194,6 +190,7 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor
@Override
public void onRefresh() {
if (checkpointOnRefresh) {
checkpointOnRefresh = false;
new CracDelegate().checkpointRestore();
}
if (exitOnRefresh) {
@@ -249,13 +246,13 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor
lifecycleBeans.forEach((beanName, bean) -> {
if (!autoStartupOnly || isAutoStartupCandidate(beanName, bean)) {
int phase = getPhase(bean);
phases.computeIfAbsent(
phase,
p -> new LifecycleGroup(phase, this.timeoutPerShutdownPhase, lifecycleBeans, autoStartupOnly)
int startupPhase = getPhase(bean);
phases.computeIfAbsent(startupPhase,
phase -> new LifecycleGroup(phase, this.timeoutPerShutdownPhase, lifecycleBeans, autoStartupOnly)
).add(beanName, bean);
}
});
if (!phases.isEmpty()) {
phases.values().forEach(LifecycleGroup::start);
}
@@ -306,13 +303,14 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor
private void stopBeans() {
Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans();
Map<Integer, LifecycleGroup> phases = new TreeMap<>(Comparator.reverseOrder());
lifecycleBeans.forEach((beanName, bean) -> {
int shutdownPhase = getPhase(bean);
phases.computeIfAbsent(
shutdownPhase,
p -> new LifecycleGroup(shutdownPhase, this.timeoutPerShutdownPhase, lifecycleBeans, false)
phases.computeIfAbsent(shutdownPhase,
phase -> new LifecycleGroup(phase, this.timeoutPerShutdownPhase, lifecycleBeans, false)
).add(beanName, bean);
});
if (!phases.isEmpty()) {
phases.values().forEach(LifecycleGroup::stop);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@ import org.springframework.util.Assert;
/**
* Configures basic date formatting for use with Spring, primarily for
* {@link org.springframework.format.annotation.DateTimeFormat} declarations.
* Applies to fields of type {@link Date}, {@link Calendar} and {@code long}.
* Applies to fields of type {@link Date}, {@link Calendar}, and {@code long}.
*
* <p>Designed for direct instantiation but also exposes the static
* {@link #addDateConverters(ConverterRegistry)} utility method for
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -76,8 +76,8 @@ public class DateTimeFormatterRegistrar implements FormatterRegistrar {
/**
* Set whether standard ISO formatting should be applied to all date/time types.
* Default is "false" (no).
* <p>If set to "true", the "dateStyle", "timeStyle" and "dateTimeStyle"
* <p>Default is "false" (no).
* <p>If set to "true", the "dateStyle", "timeStyle", and "dateTimeStyle"
* properties are effectively ignored.
*/
public void setUseIsoFormat(boolean useIsoFormat) {
@@ -88,7 +88,7 @@ public class DateTimeFormatterRegistrar implements FormatterRegistrar {
/**
* Set the default format style of {@link java.time.LocalDate} objects.
* Default is {@link java.time.format.FormatStyle#SHORT}.
* <p>Default is {@link java.time.format.FormatStyle#SHORT}.
*/
public void setDateStyle(FormatStyle dateStyle) {
this.factories.get(Type.DATE).setDateStyle(dateStyle);
@@ -96,7 +96,7 @@ public class DateTimeFormatterRegistrar implements FormatterRegistrar {
/**
* Set the default format style of {@link java.time.LocalTime} objects.
* Default is {@link java.time.format.FormatStyle#SHORT}.
* <p>Default is {@link java.time.format.FormatStyle#SHORT}.
*/
public void setTimeStyle(FormatStyle timeStyle) {
this.factories.get(Type.TIME).setTimeStyle(timeStyle);
@@ -104,7 +104,7 @@ public class DateTimeFormatterRegistrar implements FormatterRegistrar {
/**
* Set the default format style of {@link java.time.LocalDateTime} objects.
* Default is {@link java.time.format.FormatStyle#SHORT}.
* <p>Default is {@link java.time.format.FormatStyle#SHORT}.
*/
public void setDateTimeStyle(FormatStyle dateTimeStyle) {
this.factories.get(Type.DATE_TIME).setDateTimeStyle(dateTimeStyle);
@@ -138,7 +138,7 @@ public class DateTimeFormatterRegistrar implements FormatterRegistrar {
/**
* Set the formatter that will be used for objects representing date and time values.
* <p>This formatter will be used for {@link LocalDateTime}, {@link ZonedDateTime}
* <p>This formatter will be used for {@link LocalDateTime}, {@link ZonedDateTime},
* and {@link OffsetDateTime} types. When specified, the
* {@link #setDateTimeStyle dateTimeStyle} and
* {@link #setUseIsoFormat useIsoFormat} properties will be ignored.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,10 +29,18 @@ import org.springframework.util.StringUtils;
*/
abstract class DateTimeFormatterUtils {
/**
* Create a {@link DateTimeFormatter} for the supplied pattern, configured with
* {@linkplain ResolverStyle#STRICT strict} resolution.
* <p>Note that the strict resolution does not affect the parsing.
* @param pattern the pattern to use
* @return a new {@code DateTimeFormatter}
* @see ResolverStyle#STRICT
*/
static DateTimeFormatter createStrictDateTimeFormatter(String pattern) {
// Using strict parsing to align with Joda-Time and standard DateFormat behavior:
// Using strict resolution to align with Joda-Time and standard DateFormat behavior:
// otherwise, an overflow like e.g. Feb 29 for a non-leap-year wouldn't get rejected.
// However, with strict parsing, a year digit needs to be specified as 'u'...
// However, with strict resolution, a year digit needs to be specified as 'u'...
String patternToUse = StringUtils.replace(pattern, "yy", "uu");
return DateTimeFormatter.ofPattern(patternToUse).withResolverStyle(ResolverStyle.STRICT);
}
@@ -44,6 +44,7 @@ import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
import org.springframework.beans.factory.config.SingletonBeanRegistry;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext;
@@ -155,6 +156,8 @@ public class ScheduledAnnotationBeanPostProcessor
private final Map<Object, List<Runnable>> reactiveSubscriptions = new IdentityHashMap<>(16);
private final Set<Object> manualCancellationOnContextClose = Collections.newSetFromMap(new IdentityHashMap<>(16));
/**
* Create a default {@code ScheduledAnnotationBeanPostProcessor}.
@@ -305,6 +308,12 @@ public class ScheduledAnnotationBeanPostProcessor
logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
"': " + annotatedMethods);
}
if ((this.beanFactory != null && !this.beanFactory.isSingleton(beanName)) ||
(this.beanFactory instanceof SingletonBeanRegistry sbr && sbr.containsSingleton(beanName))) {
// Either a prototype/scoped bean or a FactoryBean with a pre-existing managed singleton
// -> trigger manual cancellation when ContextClosedEvent comes in
this.manualCancellationOnContextClose.add(bean);
}
}
}
return bean;
@@ -595,6 +604,18 @@ public class ScheduledAnnotationBeanPostProcessor
@Override
public void postProcessBeforeDestruction(Object bean, String beanName) {
cancelScheduledTasks(bean);
this.manualCancellationOnContextClose.remove(bean);
}
@Override
public boolean requiresDestruction(Object bean) {
synchronized (this.scheduledTasks) {
return (this.scheduledTasks.containsKey(bean) || this.reactiveSubscriptions.containsKey(bean));
}
}
private void cancelScheduledTasks(Object bean) {
Set<ScheduledTask> tasks;
List<Runnable> liveSubscriptions;
synchronized (this.scheduledTasks) {
@@ -613,13 +634,6 @@ public class ScheduledAnnotationBeanPostProcessor
}
}
@Override
public boolean requiresDestruction(Object bean) {
synchronized (this.scheduledTasks) {
return (this.scheduledTasks.containsKey(bean) || this.reactiveSubscriptions.containsKey(bean));
}
}
@Override
public void destroy() {
synchronized (this.scheduledTasks) {
@@ -636,7 +650,10 @@ public class ScheduledAnnotationBeanPostProcessor
liveSubscription.run(); // equivalent to cancelling the subscription
}
}
this.reactiveSubscriptions.clear();
this.manualCancellationOnContextClose.clear();
}
this.registrar.destroy();
if (this.localScheduler != null) {
this.localScheduler.destroy();
@@ -659,15 +676,10 @@ public class ScheduledAnnotationBeanPostProcessor
finishRegistration();
}
else if (event instanceof ContextClosedEvent) {
synchronized (this.scheduledTasks) {
Collection<Set<ScheduledTask>> allTasks = this.scheduledTasks.values();
for (Set<ScheduledTask> tasks : allTasks) {
for (ScheduledTask task : tasks) {
// At this early point, let in-progress tasks complete still
task.cancel(false);
}
}
for (Object bean : this.manualCancellationOnContextClose) {
cancelScheduledTasks(bean);
}
this.manualCancellationOnContextClose.clear();
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -46,7 +46,7 @@ public class ConcurrentModel extends ConcurrentHashMap<String, Object> implement
}
/**
* Construct a new {@code ModelMap} containing the supplied attribute
* Construct a new {@code ConcurrentModel} containing the supplied attribute
* under the supplied name.
* @see #addAttribute(String, Object)
*/
@@ -55,8 +55,8 @@ public class ConcurrentModel extends ConcurrentHashMap<String, Object> implement
}
/**
* Construct a new {@code ModelMap} containing the supplied attribute.
* Uses attribute name generation to generate the key for the supplied model
* Construct a new {@code ConcurrentModel} containing the supplied attribute.
* <p>Uses attribute name generation to generate the key for the supplied model
* object.
* @see #addAttribute(Object)
*/
@@ -1,6 +1,6 @@
/**
* Generic support for UI layer concepts.
* Provides a generic ModelMap for model holding.
* <p>Provides generic {@code Model} and {@code ModelMap} holders for model attributes.
*/
@NonNullApi
@NonNullFields
@@ -66,7 +66,7 @@ public class DefaultBindingErrorProcessor implements BindingErrorProcessor {
@Override
public void processPropertyAccessException(PropertyAccessException ex, BindingResult bindingResult) {
// Create field error with the exceptions's code, e.g. "typeMismatch".
// Create field error with the code of the exception, e.g. "typeMismatch".
String field = ex.getPropertyName();
Assert.state(field != null, "No field in exception");
String[] codes = bindingResult.resolveMessageCodes(ex.getErrorCode(), field);
@@ -50,7 +50,6 @@ import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.function.SingletonSupplier;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindingResult;
@@ -234,8 +233,8 @@ public class MethodValidationAdapter implements MethodValidator {
@Override
public final MethodValidationResult validateArguments(
Object target, Method method, @Nullable MethodParameter[] parameters, Object[] arguments,
Class<?>[] groups) {
Object target, Method method, @Nullable MethodParameter[] parameters,
Object[] arguments, Class<?>[] groups) {
Set<ConstraintViolation<Object>> violations =
invokeValidatorForArguments(target, method, arguments, groups);
@@ -256,23 +255,21 @@ public class MethodValidationAdapter implements MethodValidator {
Object target, Method method, Object[] arguments, Class<?>[] groups) {
ExecutableValidator execVal = this.validator.get().forExecutables();
Set<ConstraintViolation<Object>> violations;
try {
violations = execVal.validateParameters(target, method, arguments, groups);
return execVal.validateParameters(target, method, arguments, groups);
}
catch (IllegalArgumentException ex) {
// Probably a generic type mismatch between interface and impl as reported in SPR-12237 / HV-1011
// Let's try to find the bridged method on the implementation class...
Method bridgedMethod = BridgeMethodResolver.getMostSpecificMethod(method, target.getClass());
violations = execVal.validateParameters(target, bridgedMethod, arguments, groups);
return execVal.validateParameters(target, bridgedMethod, arguments, groups);
}
return violations;
}
@Override
public final MethodValidationResult validateReturnValue(
Object target, Method method, @Nullable MethodParameter returnType, @Nullable Object returnValue,
Class<?>[] groups) {
Object target, Method method, @Nullable MethodParameter returnType,
@Nullable Object returnValue, Class<?>[] groups) {
Set<ConstraintViolation<Object>> violations =
invokeValidatorForReturnValue(target, method, returnValue, groups);
@@ -305,9 +302,9 @@ public class MethodValidationAdapter implements MethodValidator {
Map<Path.Node, ParamErrorsBuilder> nestedViolations = new LinkedHashMap<>();
for (ConstraintViolation<Object> violation : violations) {
Iterator<Path.Node> itr = violation.getPropertyPath().iterator();
while (itr.hasNext()) {
Path.Node node = itr.next();
Iterator<Path.Node> nodes = violation.getPropertyPath().iterator();
while (nodes.hasNext()) {
Path.Node node = nodes.next();
MethodParameter parameter;
if (node.getKind().equals(ElementKind.PARAMETER)) {
@@ -323,13 +320,13 @@ public class MethodValidationAdapter implements MethodValidator {
Object arg = argumentFunction.apply(parameter.getParameterIndex());
// If the arg is a container, we need to element, but the only way to extract it
// If the arg is a container, we need the element, but the only way to extract it
// is to check for and use a container index or key on the next node:
// https://github.com/jakartaee/validation/issues/194
Path.Node parameterNode = node;
if (itr.hasNext()) {
node = itr.next();
if (nodes.hasNext()) {
node = nodes.next();
}
Object value;
@@ -348,12 +345,16 @@ public class MethodValidationAdapter implements MethodValidator {
value = map.get(key);
container = map;
}
else if (arg instanceof Iterable<?>) {
// No index or key, cannot access the specific value
value = arg;
container = arg;
}
else if (arg instanceof Optional<?> optional) {
value = optional.orElse(null);
container = optional;
}
else {
Assert.state(!node.isInIterable(), "No way to unwrap Iterable without index");
value = arg;
container = null;
}
@@ -425,7 +426,6 @@ public class MethodValidationAdapter implements MethodValidator {
* @return the name to use
*/
String resolveName(MethodParameter parameter, @Nullable Object value);
}
@@ -456,6 +456,7 @@ public class MethodValidationAdapter implements MethodValidator {
public ParamValidationResultBuilder(
Object target, MethodParameter parameter, @Nullable Object value, @Nullable Object container,
@Nullable Integer containerIndex, @Nullable Object containerKey) {
this.target = target;
this.parameter = parameter;
this.value = value;
@@ -473,7 +474,6 @@ public class MethodValidationAdapter implements MethodValidator {
this.parameter, this.value, this.resolvableErrors, this.container,
this.containerIndex, this.containerKey);
}
}
@@ -527,8 +527,7 @@ public class MethodValidationAdapter implements MethodValidator {
/**
* Default algorithm to select an object name, as described in
* {@link #setObjectNameResolver(ObjectNameResolver)}.
* Default algorithm to select an object name, as described in {@link #setObjectNameResolver}.
*/
private static class DefaultObjectNameResolver implements ObjectNameResolver {
@@ -174,7 +174,7 @@ public class MethodValidationInterceptor implements MethodInterceptor {
Object returnValue = invocation.proceed();
if (this.adaptViolations) {
this.validationAdapter.applyReturnValueValidation(target, method, null, arguments, groups);
this.validationAdapter.applyReturnValueValidation(target, method, null, returnValue, groups);
}
else {
violations = this.validationAdapter.invokeValidatorForReturnValue(target, method, returnValue, groups);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -606,9 +606,9 @@ class CacheReproTests {
return CompletableFuture.completedFuture(item);
}
@CacheEvict(cacheNames = "itemCache", allEntries = true)
public CompletableFuture<Void> clear() {
return CompletableFuture.completedFuture(null);
@CacheEvict(cacheNames = "itemCache", allEntries = true, condition = "#result > 0")
public CompletableFuture<Integer> clear() {
return CompletableFuture.completedFuture(1);
}
}
@@ -655,9 +655,9 @@ class CacheReproTests {
return Mono.just(item);
}
@CacheEvict(cacheNames = "itemCache", allEntries = true)
public Mono<Void> clear() {
return Mono.empty();
@CacheEvict(cacheNames = "itemCache", allEntries = true, condition = "#result > 0")
public Mono<Integer> clear() {
return Mono.just(1);
}
}
@@ -706,9 +706,9 @@ class CacheReproTests {
return Flux.fromIterable(item);
}
@CacheEvict(cacheNames = "itemCache", allEntries = true)
public Flux<Void> clear() {
return Flux.empty();
@CacheEvict(cacheNames = "itemCache", allEntries = true, condition = "#result > 0")
public Flux<Integer> clear() {
return Flux.just(1);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,8 +18,10 @@ package org.springframework.cache.annotation;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.atomic.AtomicLong;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import reactor.core.publisher.Flux;
@@ -29,12 +31,15 @@ import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.LoggingCacheErrorHandler;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.Nullable;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.catchThrowable;
/**
* Tests for annotation-based caching methods that use reactive operators.
@@ -51,7 +56,9 @@ class ReactiveCachingTests {
LateCacheHitDeterminationConfig.class,
LateCacheHitDeterminationWithValueWrapperConfig.class})
void cacheHitDetermination(Class<?> configClass) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(configClass, ReactiveCacheableService.class);
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(configClass, ReactiveCacheableService.class);
ReactiveCacheableService service = ctx.getBean(ReactiveCacheableService.class);
Object key = new Object();
@@ -111,13 +118,60 @@ class ReactiveCachingTests {
ctx.close();
}
@Test
void cacheErrorHandlerWithLoggingCacheErrorHandler() {
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(ExceptionCacheManager.class, ReactiveCacheableService.class, ErrorHandlerCachingConfiguration.class);
ReactiveCacheableService service = ctx.getBean(ReactiveCacheableService.class);
Object key = new Object();
Long r1 = service.cacheFuture(key).join();
assertThat(r1).isNotNull();
assertThat(r1).as("cacheFuture").isEqualTo(0L);
key = new Object();
r1 = service.cacheMono(key).block();
assertThat(r1).isNotNull();
assertThat(r1).as("cacheMono").isEqualTo(1L);
key = new Object();
r1 = service.cacheFlux(key).blockFirst();
assertThat(r1).isNotNull();
assertThat(r1).as("cacheFlux blockFirst").isEqualTo(2L);
}
@Test
void cacheErrorHandlerWithSimpleCacheErrorHandler() {
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(ExceptionCacheManager.class, ReactiveCacheableService.class);
ReactiveCacheableService service = ctx.getBean(ReactiveCacheableService.class);
Throwable completableFuturThrowable = catchThrowable(() -> service.cacheFuture(new Object()).join());
assertThat(completableFuturThrowable).isInstanceOf(CompletionException.class)
.extracting(Throwable::getCause)
.isInstanceOf(UnsupportedOperationException.class);
Throwable monoThrowable = catchThrowable(() -> service.cacheMono(new Object()).block());
assertThat(monoThrowable).isInstanceOf(UnsupportedOperationException.class);
Throwable fluxThrowable = catchThrowable(() -> service.cacheFlux(new Object()).blockFirst());
assertThat(fluxThrowable).isInstanceOf(UnsupportedOperationException.class);
}
@ParameterizedTest
@ValueSource(classes = {EarlyCacheHitDeterminationConfig.class,
EarlyCacheHitDeterminationWithoutNullValuesConfig.class,
LateCacheHitDeterminationConfig.class,
LateCacheHitDeterminationWithValueWrapperConfig.class})
void fluxCacheDoesntDependOnFirstRequest(Class<?> configClass) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(configClass, ReactiveCacheableService.class);
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(configClass, ReactiveCacheableService.class);
ReactiveCacheableService service = ctx.getBean(ReactiveCacheableService.class);
Object key = new Object();
@@ -237,4 +291,41 @@ class ReactiveCachingTests {
}
}
@Configuration
static class ErrorHandlerCachingConfiguration implements CachingConfigurer {
@Bean
@Override
public CacheErrorHandler errorHandler() {
return new LoggingCacheErrorHandler();
}
}
@Configuration(proxyBeanMethods = false)
@EnableCaching
static class ExceptionCacheManager {
@Bean
CacheManager cacheManager() {
return new ConcurrentMapCacheManager("first") {
@Override
protected Cache createConcurrentMapCache(String name) {
return new ConcurrentMapCache(name, isAllowNullValues()) {
@Override
public CompletableFuture<?> retrieve(Object key) {
return CompletableFuture.supplyAsync(() -> {
throw new UnsupportedOperationException("Test exception on retrieve");
});
}
@Override
public void put(Object key, @Nullable Object value) {
throw new UnsupportedOperationException("Test exception on put");
}
};
}
};
}
}
}
@@ -0,0 +1,87 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.context.annotation;
import java.io.IOException;
import java.io.InputStream;
import java.security.SecureClassLoader;
import org.junit.jupiter.api.Test;
import org.springframework.core.SmartClassLoader;
import org.springframework.util.StreamUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Phillip Webb
* @author Juergen Hoeller
*/
class ConfigurationClassEnhancerTests {
@Test
void enhanceReloadedClass() throws Exception {
ConfigurationClassEnhancer configurationClassEnhancer = new ConfigurationClassEnhancer();
ClassLoader parentClassLoader = getClass().getClassLoader();
CustomClassLoader classLoader = new CustomClassLoader(parentClassLoader);
Class<?> myClass = parentClassLoader.loadClass(MyConfig.class.getName());
configurationClassEnhancer.enhance(myClass, parentClassLoader);
Class<?> myReloadedClass = classLoader.loadClass(MyConfig.class.getName());
Class<?> enhancedReloadedClass = configurationClassEnhancer.enhance(myReloadedClass, classLoader);
assertThat(enhancedReloadedClass.getClassLoader()).isEqualTo(classLoader);
}
@Configuration
static class MyConfig {
@Bean
public String myBean() {
return "bean";
}
}
static class CustomClassLoader extends SecureClassLoader implements SmartClassLoader {
CustomClassLoader(ClassLoader parent) {
super(parent);
}
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (name.contains("MyConfig")) {
String path = name.replace('.', '/').concat(".class");
try (InputStream in = super.getResourceAsStream(path)) {
byte[] bytes = StreamUtils.copyToByteArray(in);
if (bytes.length > 0) {
return defineClass(name, bytes, 0, bytes.length);
}
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
return super.loadClass(name, resolve);
}
@Override
public boolean isClassReloadable(Class<?> clazz) {
return clazz.getName().contains("MyConfig");
}
}
}
@@ -26,6 +26,7 @@ import jakarta.inject.Provider;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@@ -43,6 +44,7 @@ import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
import static org.assertj.core.api.Assertions.assertThat;
@@ -183,6 +185,14 @@ class AutowiredConfigurationTests {
context.close();
}
@Test
void testValueInjectionWithAccidentalAutowiredAnnotations() {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(ValueConfigWithAccidentalAutowiredAnnotations.class);
doTestValueInjection(context);
context.close();
}
private void doTestValueInjection(BeanFactory context) {
System.clearProperty("myProp");
@@ -494,6 +504,32 @@ class AutowiredConfigurationTests {
}
@Configuration
static class ValueConfigWithAccidentalAutowiredAnnotations implements InitializingBean {
boolean invoked;
@Override
public void afterPropertiesSet() {
Assert.state(!invoked, "Factory method must not get invoked on startup");
}
@Bean @Scope("prototype")
@Autowired
public TestBean testBean(@Value("#{systemProperties[myProp]}") Provider<String> name) {
invoked = true;
return new TestBean(name.get());
}
@Bean @Scope("prototype")
@Autowired
public TestBean testBean2(@Value("#{systemProperties[myProp]}") Provider<String> name2) {
invoked = true;
return new TestBean(name2.get());
}
}
@Configuration
static class PropertiesConfig {

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