Compare commits

...

154 Commits

Author SHA1 Message Date
Stéphane Nicoll 34764252dc Release v6.1.12 2024-08-14 12:38:39 +02:00
rstoyanchev bb17ad8314 Efficient ETag parsing 2024-08-14 07:19:59 +03:00
Juergen Hoeller 63486bf19c Upgrade to Tomcat 10.1.28, Jetty 12.0.12, Netty 4.1.112, Undertow 2.3.15 2024-08-13 18:52:15 +02:00
Juergen Hoeller d0bff584d7 Polishing 2024-08-13 18:51:02 +02:00
Juergen Hoeller 8be5010f5d LocaleEditor supports BCP 47 language tags as well
Closes gh-33348
2024-08-13 18:50:30 +02:00
Stéphane Nicoll 86761cd233 Upgrade to Reactor 2023.0.9
Closes gh-33326
2024-08-13 17:27:02 +02:00
Stéphane Nicoll 07cdef410e Upgrade to Micrometer 1.12.9
Closes gh-33325
2024-08-13 17:26:38 +02:00
Sam Brannen e74406afd0 Introduce withAssignmentDisabled() option for SimpleEvaluationContext
To support additional use cases, this commit introduces a
withAssignmentDisabled() method in the Builder for
SimpleEvaluationContext.

Closes gh-33319
2024-08-13 17:26:34 +03:00
rstoyanchev fe4fd00429 Polishing contribution
Closes gh-33374
2024-08-13 16:59:13 +03:00
Riley Park 1703b71563 Fix incorrect weak ETag assertion
See gh-33374
2024-08-13 16:31:14 +03:00
Sébastien Deleuze 1911ca728d Support invoking bridged suspending functions in AopUtils
Closes gh-33045
2024-08-12 17:27:41 +02:00
Simon Baslé f4a73b79b8 Avoid CacheAspectSupport#findInCaches fall through to reactive handler
Prior to this commit if the return type is a CompletableFuture but a
cache get returns null, the execution falls through to the
reactiveCachingHandler.

This commit ensures that evaluation instead continues onto the next
cache (if any).

Closes gh-33371
2024-08-12 14:54:15 +02:00
Stéphane Nicoll 289c35c829 Merge pull request #33368 from tafjwr
* pr/33368:
  Polish "Fix predicate setup in test"
  Fix predicate setup in test

Closes gh-33368
2024-08-12 11:02:02 +02:00
Stéphane Nicoll 749145be2a Polish "Fix predicate setup in test"
See gh-33368
2024-08-12 10:29:34 +02:00
tafjwr 28eece3da1 Fix predicate setup in test
See gh-33368
2024-08-12 10:29:23 +02:00
Stéphane Nicoll 2b8225f11c Polish "Sync GHA setup" 2024-08-09 15:52:27 +02:00
Stéphane Nicoll 1e6767ac03 Sync GHA setup 2024-08-09 15:37:43 +02:00
Stéphane Nicoll 7a250aa8ea Polish
See gh-33354
2024-08-09 09:52:06 +02:00
Stéphane Nicoll 21bc8726d6 Merge pull request #33354 from GoncaloPT
* pr/33354:
  Polish "Fix error codes in Customizing Validation Errors section"
  Fix error codes in Customizing Validation Errors section

Closes gh-33354
2024-08-09 09:04:34 +02:00
Stéphane Nicoll df7ac9b308 Polish "Fix error codes in Customizing Validation Errors section"
See gh-33354
2024-08-09 09:03:05 +02:00
Gonçalo Silva 43217bdf06 Fix error codes in Customizing Validation Errors section
See gh-33354
2024-08-09 09:02:53 +02:00
Robert Danczak a31945f8b6 Fix syntax error in RestClient documentation
Closes gh-33350
2024-08-08 17:15:27 +03:00
Simon Baslé a0edf129eb Fix misleading statement in ReactorResourceFactory javadoc
Closes gh-33338
2024-08-08 15:26:10 +02:00
Sam Brannen bcffa15c7d Reinstate qualifier support for JSR-330 @⁠javax.inject.Named
This commit revises QualifierAnnotationAutowireCandidateResolver to
reinstate "qualifier" support for the legacy JSR-330
@⁠javax.inject.Named annotation.

See gh-31090
Closes gh-33345
2024-08-08 14:20:06 +03:00
Sam Brannen f9d2641fd5 Update field name to reflect switch to Jakarta 2024-08-08 13:22:00 +03:00
Sam Brannen 3b506e11a1 Polishing 2024-08-08 13:22:00 +03:00
Stéphane Nicoll ddee309e11 Merge pull request #33341 from pcvolkmer
* pr/33341:
  Add missing Kotlin example for expectAll()

Closes gh-33341
2024-08-08 11:41:48 +02:00
Paul-Christian Volkmer 7e4884fcfa Add missing Kotlin example for expectAll()
See gh-33341
2024-08-08 11:35:12 +02:00
Jonatan Ivanov 38a3d18256 Start Observation before scope open for Scheduled operation
When ScheduledAnnotationReactiveSupport adds the Observation to the
context, Reactor opens a scope through the Context Propagation API.
This happens before TrackingSubscriber would start the Observation and
opening a scope without starting an Observation is invalid.

This change moves the Observation start before the scope opening.

Closes gh-33349
2024-08-08 10:46:19 +02:00
Jonatan Ivanov 6c74fee5e8 Remove unnecessary error signal from DefaultRestClient
Before UnknownContentTypeException is thrown, it is added to the current
Observation but since UnknownContentTypeException is a
RestClientException, the exception is added again in one of the
catch blocks later on.

Closes gh-33347
2024-08-08 09:30:22 +02:00
Jonatan Ivanov 36e84a5209 Fix RestClient instrumentation
ClientHttpResponse implements Closeable and the close method of
DefaultConvertibleClientHttpResponse also stops the current Observation.

Before this change exiting the try-with-resource block stopped the
Observation since it called close on ClientHttpResponse.
After this, there were multiple error and stop calls on the Observation
in the catch blocks after the Observation was already stopped
which is invalid.

This change reorders the flow by stopping the Observation in the
finally block (closing ClientHttpResponse) and not stopping
the Observation in any of the catch blocks.

Closes gh-33346
2024-08-08 09:11:57 +02:00
Sam Brannen 1e97b2137b Disable class data sharing (CDS) for tests
Prior to this commit, the Gradle build output the following warning
multiple times.

OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended

Since we don't need CDS enabled for our tests, I've added `-Xshare:off`
as a JVM argument for our tests to disable CDS.

(cherry picked from commit 27985b1439)
2024-08-07 18:08:03 +03:00
Sam Brannen 5e4e530c59 Remove Java 21 classpath entries in Eclipse projects
Since the me.champeau.mrjar stores Java 21 main and test sources in
non-standard locations, and since we can only use a single JDK within
Eclipse, this commit updates `ide.gradle` to remove `java21` classpath
entries in Eclipse projects.
2024-08-07 18:02:43 +03:00
Stéphane Nicoll 76b2d13b2c Add support for changing context path in ServletRequestPath
This commit implements modifyContextPath in ServletRequestPath and
apply the same logic of concatenating the servlet path with the
context path.

Closes gh-33251
2024-08-07 16:28:17 +02:00
Aleksey Genus 9b85a246d8 SimpleAsyncTaskScheduler stops accepting new tasks when isShutdown
This changes the criteria for simplified task rejection logging from
to be that the executor is in the shutdown phase, not fully terminated.

See gh-33334
Closes gh-33336
2024-08-07 15:58:27 +02:00
Stéphane Nicoll 5a99407847 Start building against Reactor 2023.0.9 snapshots
See gh-33326
2024-08-07 10:02:20 +02:00
Stéphane Nicoll 05bd246cab Start building against Micrometer 1.12.9 snapshots
See gh-33325
2024-08-07 10:01:54 +02:00
Sam Brannen 0127de5a7a Enforce read-only semantics in SpEL's SimpleEvaluationContext
SimpleEvaluationContext.forReadOnlyDataBinding() documents that it
creates a SimpleEvaluationContext for read-only access to public
properties; however, prior to this commit write access was not disabled
for indexed structures when using the assignment operator, the
increment operator, or the decrement operator.

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

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

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

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

Closes gh-33319
2024-08-06 13:54:11 +03:00
Sam Brannen fcc99a67b6 Support lists for varargs invocations in SpEL
The changes made in conjunction with gh-33013 resulted in a regression
for varargs support in SpEL expressions. Specifically, before gh-33013
one could supply a list -- for example, an "inline list" -- as the
varargs array when invoking a varargs constructor, method, or function
within a SpEL expression. However, after gh-33013 an inline list (or
collection in general) is no longer converted to an array for varargs
invocations. Instead, the list is supplied as a single argument of the
resulting varargs array which breaks applications that depend on the
previous behavior.

Although it was never intended that one could supply a collection as
the set of varargs, we concede that this is a regression in existing
behavior, and this commit therefore restores support for supplying a
java.util.List as the varargs "array".

In addition, this commit introduces the same "list to array" conversion
support for MethodHandle-based functions that accept varargs.

Note, however, that this commit does not restore support for converting
arbitrary single objects to an array for varargs invocations if the
single object is already an instance of the varargs array's component
type.

See gh-33013
Closes gh-33315
2024-08-05 16:28:11 +03:00
Sam Brannen 8afff3359b Polishing 2024-08-05 15:01:41 +03:00
Sam Brannen c57c2272a1 Throw exception for failure to set property as index in SpEL
Prior to this commit, the Indexer in the Spring Expression Language
(SpEL) silently ignored a failure to set a property via the indexed
property syntax (['<property name>'] = <new value>) – for example, if
property write access was disabled in the EvaluationContext.

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

Closes gh-33310
2024-08-05 14:15:55 +03:00
Sam Brannen 8ec23a0fcc Polishing 2024-08-05 14:15:39 +03:00
Sam Brannen 8badae9d72 Convert primitive array to Object[] when invoking varargs method in SpEL
Prior to this commit, SpEL incorrectly wrapped a primitive array in an
Object[] array when invoking Object[] varargs methods and constructors.

This commit addresses this by updating the convertArguments(...) method
in ReflectionHelper as follows when the user supplies the varargs
already packaged in a primitive array.

- When deciding whether to convert a single element passed as varargs,
  we now check if the argument is an array that is assignable to the
  varargs array type.

- When converting an array supplied as the varargs, we convert that
  array to the varargs array type.

Closes gh-33317
2024-08-05 11:56:48 +03:00
Sam Brannen 444e7eeb74 Enable test for primitive array to Object[] conversion in SpEL
See gh-33212
2024-08-05 11:05:53 +03:00
Sam Brannen cb6a5baac5 Support conversion from primitive array to Object[] in ConversionService
Prior to this commit, the ConversionService failed to convert a primitive
array (such as int[]) to an Object[] due to an error in the logic in
ArrayToArrayConverter.

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

Closes gh-33212
2024-08-04 17:13:56 +03:00
Stéphane Nicoll 3b2a818f1e Upgrade to Java 17.0.12 2024-08-04 09:34:13 +02:00
Stéphane Nicoll 84d1122190 Shorten CI and deploy snapshot jobs to 60 minutes
Closes gh-33302
2024-08-02 10:56:25 +02:00
Simon Baslé 722dcae1b1 Fix buffer leak in MultipartParser
This change fixes a buffer leak when the MultipartParser is encountering
a body DataBuffer that only contains a boundary.

Closes gh-33094
2024-08-01 16:28:50 +02:00
Stéphane Nicoll 850a0de1b0 Suppress deprecated warnings for autowiring
This commit extends our handling of deprecated with AOT to autowiring.

Closes gh-33295
2024-07-31 16:51:06 +02:00
Stéphane Nicoll 321e8a58ae Document that NoOpResponseErrorHandler is not a good fit with RestClient
Closes gh-33276
2024-07-30 17:17:39 +02:00
Stéphane Nicoll 46ba13b645 Review Caching infrastructure documentation
Closes gh-33288
2024-07-29 11:29:35 +02:00
Juergen Hoeller 9d9e621efe Defensive singleton check for non-registered bean
Closes gh-33286
2024-07-29 10:47:08 +02:00
Sam Brannen 5aa38833dd Fix XJC configuration to re-enable Eclipse IDE support
Our recent switch from the `org.unbroken-dome.xjc` plugin to the
`com.github.bjornvester.xjc` plugin resulted in errors when trying to
import Spring Framework projects into the Eclipse IDE.

This commit fixes those issues as follows.

- @wilkinsona revised the XJC configuration in `spring-oxm.gradle` to
  avoid the ConcurrentModificationException encountered when running
  `./gradlew eclipse`.

- I added a workaround in `ide.gradle` to manually remove lingering
  "main" classpath entries for sources generated by XJC.

Co-authored-by: Andy Wilkinson <andy.wilkinson@broadcom.com>

Closes gh-33264
2024-07-26 16:39:40 +03:00
Stéphane Nicoll 9aac24c18a Merge pull request #33277 from sheip9
* pr/33277:
  Polish "Fix reference to configurePathMatching in code sample"
  Fix reference to configurePathMatching in code sample

Closes gh-33277
2024-07-26 10:02:50 +02:00
Stéphane Nicoll ea665d776d Polish "Fix reference to configurePathMatching in code sample"
See gh-33277
2024-07-26 10:01:39 +02:00
sheip9 7a2fc2ff09 Fix reference to configurePathMatching in code sample
See gh-33277
2024-07-26 10:01:21 +02:00
Stéphane Nicoll 52849819de Detect deprecation on enclosing classes as well
This commit improves CodeWarnings so that it detects if an enclosing
class is deprecated. Previously, it would only consider the annotated
element itself and the enclosing element is important for a class as
it is required to refer to it.

Closes gh-33273
2024-07-25 16:28:40 +02:00
Stéphane Nicoll bcdc991838 Switch to Shadow plugin fork
This commit fixes to a fork of the shadow plugin that fixes support
with recent Gradle releases.

See https://github.com/johnrengelman/shadow/pull/876
2024-07-24 11:42:54 +02:00
Stéphane Nicoll 589f17f48b Polish "Upgrade XJC processing to com.github.bjornvester.xjc"
See gh-33264
2024-07-24 11:42:25 +02:00
Brian Clozel 9366308285 Fix Gradle warning in RuntimeHintsAgentPlugin
Fixing the following warning in the runtime hints plugin configuration:
https://docs.gradle.org/8.9/userguide/upgrading_version_8.html#test_task_default_classpath
2024-07-23 17:09:13 +02:00
Stéphane Nicoll 12b996b07d Upgrade XJC processing to com.github.bjornvester.xjc
This commit upgrades our build to use a different plugin for XJC
processing, so that Gradle doesn't output a compatibility warning
anymore.

Unfortunately, com.github.bjornvester.xjc only works against main
sources and our schema is only used for test purposes. This commit
therefore reconfigure the task to remove the xjc main source set and
apply it to the test source set instead.

Closes gh-33264
2024-07-23 16:19:30 +02:00
Stéphane Nicoll 656d08f075 Fix warning in doc generation 2024-07-23 15:06:59 +02:00
Stéphane Nicoll 6e9a19212f Consistently check for Content-Length value
This commit makes sure to consistently check that the content length
is not set above 2GB. Previously it was only checked in
setContentLength.

Closes gh-33256
2024-07-23 10:43:48 +02:00
Dawid Olbiński 83ff8e4e98 Support JAXBElement subtypes in Jaxb2XmlEncoder
Closes gh-33258
2024-07-23 09:19:11 +02:00
Stéphane Nicoll cbdaafd409 Order dependencies 2024-07-21 13:02:15 +02:00
Stéphane Nicoll e5c505b71a Merge pull request #33248 from ngocnhan-tran1996
* pr/33248:
  Polish "Stop referring to STRUCT and ARRAY as they are deprecated"
  Stop referring to STRUCT and ARRAY as they are deprecated

Closes gh-33248
2024-07-21 11:40:11 +02:00
Stéphane Nicoll af8dc44469 Polish "Stop referring to STRUCT and ARRAY as they are deprecated"
See gh-33248
2024-07-21 11:39:16 +02:00
Tran Ngoc Nhan 955602bdbd Stop referring to STRUCT and ARRAY as they are deprecated
See gh-33248
2024-07-21 11:18:12 +02:00
Stéphane Nicoll b62662296e Merge pull request #33245 from ngocnhan-tran1996
* pr/33245:
  Polish "Update links in reference manual"
  Update links in reference manual

Closes gh-33245
2024-07-20 11:52:59 +02:00
Stéphane Nicoll a3b737ed00 Polish "Update links in reference manual"
See gh-33245
2024-07-20 11:51:49 +02:00
Tran Ngoc Nhan 61d1fde797 Update links in reference manual
See gh-33245
2024-07-20 11:32:35 +02:00
Brian Clozel 0bb309f433 Instrument @JmsListener session for response messages
Prior to this commit, the observation instrumentation for `@JmsListener`
annotated methods (implemented in `AbstractMessageListenerContainer`
would not instrument the JMS session using the Micrometer JMS support.
This means that response messages returned from the listener method
would be sent but no observation would be recorded. As a result, tracing
message properties would be also missing.

This commit ensures that the session provided to the listener method is
instrumented beforehand, if Micrometer is on the classpath and an
observation registry has been configured.

Fixes gh-33221
2024-07-19 17:17:18 +02:00
Stéphane Nicoll e8630f3409 Remove useless permissions on build-and-deploy-snapshot workflow
Closes gh-33238
2024-07-19 11:17:50 +02:00
Stéphane Nicoll c250817284 Update GHA actions 2024-07-16 09:59:14 +02:00
rstoyanchev 77bdbf7e37 Polishing contribution
Closes gh-33181
2024-07-15 15:21:19 +01:00
kevin.kep 2fe7ab1f92 Trim last allowed origin in comma-delimited list
See gh-33181
2024-07-15 15:17:03 +01:00
Simon Baslé 6becfe2508 Fix AOP for Kotlin suspending function with aspect + @Cacheable
This change simplifies the CacheInterceptor way of dealing with cached
coroutines, thanks to the fact that lower level support for AOP has been
introduced in c8169e5c. This fix is similar to the one applied for
`@Transactional` in gh-33095.

Closes gh-33210
2024-07-15 11:47:12 +02:00
Stéphane Nicoll dfcd8374c7 Upgrade to Gradle 8.9
Closes gh-33214
2024-07-14 11:21:52 +02:00
Sam Brannen ae5dd54115 Add @⁠Disabled tests for primitive varargs array to Object[] conversion 2024-07-12 17:36:54 +02:00
Sam Brannen e088892fc1 Support MethodHandle invocation with primitive varargs array in SpEL
Prior to this commit, the Spring Expression Language (SpEL) could not
invoke a varargs MethodHandle function with a primitive array
containing the variable arguments, although that is supported for a
varargs Method function. Attempting to do so resulted in the first
element of the primitive array being supplied as a single argument to
the MethodHandle, effectively ignoring any variable arguments after the
first one.

This commit addresses this by updating the
convertAllMethodHandleArguments(...) method in ReflectionHelper as
follows when the user supplies the varargs already packaged in a
primitive array.

- Regarding conversion, use the wrapper type for a primitive varargs
  array, since we eventually need an Object array in order to invoke
  the MethodHandle in FunctionReference#executeFunctionViaMethodHandle().

- When deciding whether to convert a single element passed as varargs,
  we now check if the argument is an array that is assignable to the
  varargs array type.

- When converting an array supplied as the varargs, we now convert that
  array to the varargs array type instead of the varargs component type.

Note, however, that a SpEL expression cannot provide a primitive array
for an Object[] varargs target. This is due to the fact that the
ArrayToArrayConverter used by Spring's ConversionService does not
support conversion from a primitive array to Object[] -- for example,
from int[] to Object[].

See gh-33191
Closes gh-33198
2024-07-12 17:36:54 +02:00
Simon Baslé 152914a752 Fix multipart async servlet request temporary files deletion
This change tracks the multipart nature of the async request
within the `DispatcherServlet`, in the `WebAsyncManager`.

This allows for the second ASYNC dispatch to recognize the
multipart aspect and clean up the associated resources.

Closes gh-33161
2024-07-12 12:50:59 +02:00
Stéphane Nicoll 0f3f979d16 Align classloader used to create the JAXBContext
This commit makes sure that JAXBContext.newInstance consistently use
the target class classloader to detect the necessary resources.

Previously, the current thread's context classloader was used, which
could lead to not finding the required JAXB components.

Closes gh-33158
2024-07-12 12:00:07 +02:00
Stéphane Nicoll f8875ea970 Add missing artifact properties for staging
This commit makes sure that docs artifacts have their attributes set
for staging as well. Previously they were not and deployment of Javadoc
did not occur.

Closes gh-33204
2024-07-12 08:37:36 +02:00
Stéphane Nicoll c9375167fd Harmonize properties of deploy action 2024-07-12 08:37:36 +02:00
Sam Brannen 48a6bd6ce7 Polishing 2024-07-11 18:30:52 +02:00
Stéphane Nicoll e15f123486 Remove concourse configuration now that CI is using GitHub Actions 2024-07-11 17:00:29 +02:00
Juergen Hoeller 2bfff7fc37 Retain original URL instance in case of custom URLStreamHandler
Closes gh-33199
2024-07-11 16:15:42 +02:00
Stéphane Nicoll 1880e109c3 Next development version (v6.1.12-SNAPSHOT) 2024-07-11 10:14:04 +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
213 changed files with 5000 additions and 2193 deletions
@@ -0,0 +1,20 @@
name: Await HTTP Resource
description: Waits for an HTTP resource to be available (a HEAD request succeeds)
inputs:
url:
description: 'The URL of the resource to await'
required: true
runs:
using: composite
steps:
- name: Await HTTP resource
shell: bash
run: |
url=${{ inputs.url }}
echo "Waiting for $url"
until curl --fail --head --silent ${{ inputs.url }} > /dev/null
do
echo "."
sleep 60
done
echo "$url is available"
+56
View File
@@ -0,0 +1,56 @@
name: 'Build'
description: 'Builds the project, optionally publishing it to a local deployment repository'
inputs:
java-version:
required: false
default: '17'
description: 'The Java version to compile and test with'
java-early-access:
required: false
default: 'false'
description: 'Whether the Java version is in early access'
java-toolchain:
required: false
default: 'false'
description: 'Whether a Java toolchain should be used'
publish:
required: false
default: 'false'
description: 'Whether to publish artifacts ready for deployment to Artifactory'
develocity-access-key:
required: false
description: 'The access key for authentication with ge.spring.io'
outputs:
build-scan-url:
description: 'The URL, if any, of the build scan produced by the build'
value: ${{ (inputs.publish == 'true' && steps.publish.outputs.build-scan-url) || steps.build.outputs.build-scan-url }}
version:
description: 'The version that was built'
value: ${{ steps.read-version.outputs.version }}
runs:
using: composite
steps:
- name: Prepare Gradle Build
uses: ./.github/actions/prepare-gradle-build
with:
develocity-access-key: ${{ inputs.develocity-access-key }}
java-version: ${{ inputs.java-version }}
java-early-access: ${{ inputs.java-early-access }}
java-toolchain: ${{ inputs.java-toolchain }}
- name: Build
id: build
if: ${{ inputs.publish == 'false' }}
shell: bash
run: ./gradlew check antora
- name: Publish
id: publish
if: ${{ inputs.publish == 'true' }}
shell: bash
run: ./gradlew -PdeploymentRepository=$(pwd)/deployment-repository build publishAllPublicationsToDeploymentRepository
- name: Read Version From gradle.properties
id: read-version
shell: bash
run: |
version=$(sed -n 's/version=\(.*\)/\1/p' gradle.properties)
echo "Version is $version"
echo "version=$version" >> $GITHUB_OUTPUT
@@ -0,0 +1,23 @@
name: Create GitHub Release
description: Create the release on GitHub with a changelog
inputs:
milestone:
description: Name of the GitHub milestone for which a release will be created
required: true
token:
description: Token to use for authentication with GitHub
required: true
runs:
using: composite
steps:
- name: Generate Changelog
uses: spring-io/github-changelog-generator@185319ad7eaa75b0e8e72e4b6db19c8b2cb8c4c1 #v0.0.11
with:
milestone: ${{ inputs.milestone }}
token: ${{ inputs.token }}
config-file: .github/actions/create-github-release/changelog-generator.yml
- name: Create GitHub Release
env:
GITHUB_TOKEN: ${{ inputs.token }}
shell: bash
run: gh release create ${{ format('v{0}', inputs.milestone) }} --notes-file changelog.md
@@ -17,4 +17,12 @@ changelog:
- "type: dependency-upgrade"
contributors:
exclude:
names: ["bclozel", "jhoeller", "poutsma", "rstoyanchev", "sbrannen", "sdeleuze", "snicoll", "simonbasle"]
names:
- "bclozel"
- "jhoeller"
- "poutsma"
- "rstoyanchev"
- "sbrannen"
- "sdeleuze"
- "simonbasle"
- "snicoll"
@@ -0,0 +1,49 @@
name: 'Prepare Gradle Build'
description: 'Prepares a Gradle build. Sets up Java and Gradle and configures Gradle properties'
inputs:
java-version:
required: false
default: '17'
description: 'The Java version to use for the build'
java-early-access:
required: false
default: 'false'
description: 'Whether the Java version is in early access'
java-toolchain:
required: false
default: 'false'
description: 'Whether a Java toolchain should be used'
develocity-access-key:
required: false
description: 'The access key for authentication with ge.spring.io'
runs:
using: composite
steps:
- name: Set Up Java
uses: actions/setup-java@v4
with:
distribution: ${{ inputs.java-early-access == 'true' && 'temurin' || 'liberica' }}
java-version: |
${{ inputs.java-early-access == 'true' && format('{0}-ea', inputs.java-version) || inputs.java-version }}
${{ inputs.java-toolchain == 'true' && '17' || '' }}
- name: Set Up Gradle
uses: gradle/actions/setup-gradle@d9c87d481d55275bb5441eef3fe0e46805f9ef70 # v3.5.0
with:
cache-read-only: false
develocity-access-key: ${{ inputs.develocity-access-key }}
- name: Configure Gradle Properties
shell: bash
run: |
mkdir -p $HOME/.gradle
echo 'systemProp.user.name=spring-builds+github' >> $HOME/.gradle/gradle.properties
echo 'systemProp.org.gradle.internal.launcher.welcomeMessageEnabled=false' >> $HOME/.gradle/gradle.properties
echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties
echo 'org.gradle.daemon=4' >> $HOME/.gradle/gradle.properties
- name: Configure Toolchain Properties
if: ${{ inputs.java-toolchain == 'true' }}
shell: bash
run: |
echo toolchainVersion=${{ inputs.java-version }} >> $HOME/.gradle/gradle.properties
echo systemProp.org.gradle.java.installations.auto-detect=false >> $HOME/.gradle/gradle.properties
echo systemProp.org.gradle.java.installations.auto-download=false >> $HOME/.gradle/gradle.properties
echo systemProp.org.gradle.java.installations.paths=${{ format('$JAVA_HOME_{0}_X64', inputs.java-version) }} >> $HOME/.gradle/gradle.properties
+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,43 @@
name: Sync to Maven Central
description: Syncs a release to Maven Central and waits for it to be available for use
inputs:
jfrog-cli-config-token:
description: 'Config token for the JFrog CLI'
required: true
spring-framework-version:
description: 'The version of Spring Framework that is being synced to Central'
required: true
ossrh-s01-token-username:
description: 'Username for authentication with s01.oss.sonatype.org'
required: true
ossrh-s01-token-password:
description: 'Password for authentication with s01.oss.sonatype.org'
required: true
ossrh-s01-staging-profile:
description: 'Staging profile to use when syncing to Central'
required: true
runs:
using: composite
steps:
- name: Set Up JFrog CLI
uses: jfrog/setup-jfrog-cli@105617d23456a69a92485207c4f28ae12297581d # v4.2.1
env:
JF_ENV_SPRING: ${{ inputs.jfrog-cli-config-token }}
- name: Download Release Artifacts
shell: bash
run: jf rt download --spec ${{ format('{0}/artifacts.spec', github.action_path) }} --spec-vars 'buildName=${{ format('spring-framework-{0}', inputs.spring-framework-version) }};buildNumber=${{ github.run_number }}'
- name: Sync
uses: spring-io/nexus-sync-action@42477a2230a2f694f9eaa4643fa9e76b99b7ab84 # v0.0.1
with:
username: ${{ inputs.ossrh-s01-token-username }}
password: ${{ inputs.ossrh-s01-token-password }}
staging-profile-name: ${{ inputs.ossrh-s01-staging-profile }}
create: true
upload: true
close: true
release: true
generate-checksums: true
- name: Await
uses: ./.github/actions/await-http-resource
with:
url: ${{ format('https://repo.maven.apache.org/maven2/org/springframework/spring-context/{0}/spring-context-{0}.jar', inputs.spring-framework-version) }}
@@ -0,0 +1,20 @@
{
"files": [
{
"aql": {
"items.find": {
"$and": [
{
"@build.name": "${buildName}",
"@build.number": "${buildNumber}",
"path": {
"$nmatch": "org/springframework/framework-api/*"
}
}
]
}
},
"target": "nexus/"
}
]
}
+28 -32
View File
@@ -1,4 +1,4 @@
name: Build and deploy snapshot
name: Build and Deploy Snapshot
on:
push:
branches:
@@ -7,43 +7,26 @@ 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
timeout-minutes: 60
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 +35,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 }}
+17 -37
View File
@@ -7,6 +7,9 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
ci:
name: '${{ matrix.os.name}} | Java ${{ matrix.java.version}}'
runs-on: ${{ matrix.os.id }}
timeout-minutes: 60
if: ${{ github.repository == 'spring-projects/spring-framework' }}
strategy:
matrix:
@@ -18,61 +21,38 @@ jobs:
toolchain: false
- version: 21
toolchain: true
- version: 22
toolchain: true
- version: 23
early-access: true
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-early-access: ${{ matrix.java.early-access || 'false' }}
java-toolchain: ${{ matrix.java.toolchain }}
develocity-access-key: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
- name: Send Notification
uses: ./.github/actions/send-notification
if: always()
with:
webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
status: ${{ job.status }}
build-scan-url: ${{ steps.build.outputs.build-scan-url }}
run-name: ${{ format('{0} | {1} | Java {2}', github.ref_name, matrix.os.name, matrix.java.version) }}
run-name: ${{ format('{0} | {1} | Java {2}', github.ref_name, matrix.os.name, matrix.java.version) }}
+94
View File
@@ -0,0 +1,94 @@
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:
uri: 'https://repo.spring.io'
username: ${{ secrets.ARTIFACTORY_USERNAME }}
password: ${{ secrets.ARTIFACTORY_PASSWORD }}
build-name: ${{ format('spring-framework-{0}', steps.build-and-publish.outputs.version)}}
repository: 'libs-staging-local'
folder: 'deployment-repository'
signing-key: ${{ secrets.GPG_PRIVATE_KEY }}
signing-passphrase: ${{ secrets.GPG_PASSPHRASE }}
artifact-properties: |
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
/**/framework-api-*-docs.zip::zip.type=docs
/**/framework-api-*-schema.zip::zip.type=schema
outputs:
version: ${{ steps.build-and-publish.outputs.version }}
verify:
name: Verify
needs: build-and-stage-release
uses: ./.github/workflows/verify.yml
with:
staging: true
version: ${{ needs.build-and-stage-release.outputs.version }}
secrets:
google-chat-webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
repository-password: ${{ secrets.ARTIFACTORY_PASSWORD }}
repository-username: ${{ secrets.ARTIFACTORY_USERNAME }}
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
sync-to-maven-central:
name: Sync to Maven Central
needs:
- build-and-stage-release
- verify
runs-on: ubuntu-latest
steps:
- name: Check Out Code
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Sync to Maven Central
uses: ./.github/actions/sync-to-maven-central
with:
jfrog-cli-config-token: ${{ secrets.JF_ARTIFACTORY_SPRING }}
ossrh-s01-staging-profile: ${{ secrets.OSSRH_S01_STAGING_PROFILE }}
ossrh-s01-token-password: ${{ secrets.OSSRH_S01_TOKEN_PASSWORD }}
ossrh-s01-token-username: ${{ secrets.OSSRH_S01_TOKEN_USERNAME }}
spring-framework-version: ${{ needs.build-and-stage-release.outputs.version }}
promote-release:
name: Promote Release
needs:
- build-and-stage-release
- sync-to-maven-central
runs-on: ubuntu-latest
steps:
- name: Set up JFrog CLI
uses: jfrog/setup-jfrog-cli@105617d23456a69a92485207c4f28ae12297581d # v4.2.1
env:
JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }}
- name: Promote build
run: jfrog rt build-promote ${{ format('spring-framework-{0}', needs.build-and-stage-release.outputs.version)}} ${{ github.run_number }} libs-release-local
create-github-release:
name: Create GitHub Release
needs:
- build-and-stage-release
- promote-release
runs-on: ubuntu-latest
steps:
- name: Check Out Code
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Create GitHub Release
uses: ./.github/actions/create-github-release
with:
milestone: ${{ needs.build-and-stage-release.outputs.version }}
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
@@ -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@d9c87d481d55275bb5441eef3fe0e46805f9ef70 # v3.5.0
+71
View File
@@ -0,0 +1,71 @@
name: Verify
on:
workflow_call:
inputs:
version:
required: true
type: string
staging:
required: false
default: false
type: boolean
secrets:
repository-username:
required: false
repository-password:
required: false
google-chat-webhook-url:
required: true
token:
required: true
jobs:
verify:
name: Verify
runs-on: ubuntu-latest
steps:
- name: Check Out Release Verification Tests
uses: actions/checkout@v4
with:
repository: spring-projects/spring-framework-release-verification
ref: 'v0.0.2'
token: ${{ secrets.token }}
- name: Check Out Send Notification Action
uses: actions/checkout@v4
with:
path: spring-framework
sparse-checkout: .github/actions/send-notification
- name: Set Up Java
uses: actions/setup-java@v4
with:
distribution: 'liberica'
java-version: 17
- name: Set Up Gradle
uses: gradle/actions/setup-gradle@d9c87d481d55275bb5441eef3fe0e46805f9ef70 # v3.5.0
with:
cache-read-only: false
- name: Configure Gradle Properties
shell: bash
run: |
mkdir -p $HOME/.gradle
echo 'org.gradle.daemon=false' >> $HOME/.gradle/gradle.properties
- name: Run Release Verification Tests
env:
RVT_VERSION: ${{ inputs.version }}
RVT_RELEASE_TYPE: oss
RVT_STAGING: ${{ inputs.staging }}
RVT_OSS_REPOSITORY_USERNAME: ${{ secrets.repository-username }}
RVT_OSS_REPOSITORY_PASSWORD: ${{ secrets.repository-password }}
run: ./gradlew spring-framework-release-verification-tests:test
- name: Upload Build Reports on Failure
uses: actions/upload-artifact@v4
if: failure()
with:
name: build-reports
path: '**/build/reports/'
- name: Send Notification
uses: ./spring-framework/.github/actions/send-notification
if: failure()
with:
webhook-url: ${{ secrets.google-chat-webhook-url }}
status: ${{ job.status }}
run-name: ${{ format('{0} | Verification | {1}', github.ref_name, inputs.version) }}
+1 -1
View File
@@ -1,3 +1,3 @@
# Enable auto-env through the sdkman_auto_env config
# Add key=value pairs of SDKs to use below
java=17.0.11-librca
java=17.0.12-librca
+2 -2
View File
@@ -3,10 +3,10 @@ plugins {
// kotlinVersion is managed in gradle.properties
id 'org.jetbrains.kotlin.plugin.serialization' version "${kotlinVersion}" apply false
id 'org.jetbrains.dokka' version '1.8.20'
id 'org.unbroken-dome.xjc' version '2.0.0' apply false
id 'com.github.ben-manes.versions' version '0.51.0'
id 'com.github.johnrengelman.shadow' version '8.1.1' apply false
id 'com.github.bjornvester.xjc' version '1.8.2' apply false
id 'de.undercouch.download' version '5.4.0'
id 'io.github.goooler.shadow' version '8.1.8' apply false
id 'me.champeau.jmh' version '0.7.2' apply false
id 'me.champeau.mrjar' version '0.1.1'
}
@@ -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.
@@ -62,9 +62,11 @@ class TestConventions {
if (project.hasProperty("testGroups")) {
test.systemProperty("testGroups", project.getProperties().get("testGroups"));
}
test.jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED",
test.jvmArgs(
"--add-opens=java.base/java.lang=ALL-UNNAMED",
"--add-opens=java.base/java.util=ALL-UNNAMED",
"-Djava.locale.providers=COMPAT");
"-Xshare:off"
);
}
private void configureTestRetryPlugin(Project project, Test test) {
@@ -26,7 +26,9 @@ import org.gradle.api.attributes.LibraryElements;
import org.gradle.api.attributes.Usage;
import org.gradle.api.attributes.java.TargetJvmVersion;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.jvm.JvmTestSuite;
import org.gradle.api.tasks.testing.Test;
import org.gradle.testing.base.TestingExtension;
import java.util.Collections;
@@ -47,6 +49,8 @@ public class RuntimeHintsAgentPlugin implements Plugin<Project> {
public void apply(Project project) {
project.getPlugins().withType(JavaPlugin.class, javaPlugin -> {
TestingExtension testing = project.getExtensions().getByType(TestingExtension.class);
JvmTestSuite jvmTestSuite = (JvmTestSuite) testing.getSuites().getByName("test");
RuntimeHintsAgentExtension agentExtension = createRuntimeHintsAgentExtension(project);
Test agentTest = project.getTasks().create(RUNTIMEHINTS_TEST_TASK, Test.class, test -> {
test.useJUnitPlatform(options -> {
@@ -55,6 +59,8 @@ public class RuntimeHintsAgentPlugin implements Plugin<Project> {
test.include("**/*Tests.class", "**/*Test.class");
test.systemProperty("java.awt.headless", "true");
test.systemProperty("org.graalvm.nativeimage.imagecode", "runtime");
test.setTestClassesDirs(jvmTestSuite.getSources().getOutput().getClassesDirs());
test.setClasspath(jvmTestSuite.getSources().getRuntimeClasspath());
test.getJvmArgumentProviders().add(createRuntimeHintsAgentArgumentProvider(project, agentExtension));
});
project.getTasks().getByName("check", task -> task.dependsOn(agentTest));
-59
View File
@@ -1,59 +0,0 @@
== Spring Framework Concourse pipeline
NOTE: CI is being migrated to GitHub Actions.
The Spring Framework uses https://concourse-ci.org/[Concourse] for its CI build and other automated tasks.
The Spring team has a dedicated Concourse instance available at https://ci.spring.io with a build pipeline
for https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-6.1.x[Spring Framework 6.1.x].
=== Setting up your development environment
If you're part of the Spring Framework project on GitHub, you can get access to CI management features.
First, you need to go to https://ci.spring.io and install the client CLI for your platform (see bottom right of the screen).
You can then login with the instance using:
[source]
----
$ fly -t spring login -n spring-framework -c https://ci.spring.io
----
Once logged in, you should get something like:
[source]
----
$ fly ts
name url team expiry
spring https://ci.spring.io spring-framework Wed, 25 Mar 2020 17:45:26 UTC
----
=== Pipeline configuration and structure
The build pipelines are described in `pipeline.yml` file.
This file is listing Concourse resources, i.e. build inputs and outputs such as container images, artifact repositories, source repositories, notification services, etc.
It also describes jobs (a job is a sequence of inputs, tasks and outputs); jobs are organized by groups.
The `pipeline.yml` definition contains `((parameters))` which are loaded from the `parameters.yml` file or from our https://docs.cloudfoundry.org/credhub/[credhub instance].
You'll find in this folder the following resources:
* `pipeline.yml` the build pipeline
* `parameters.yml` the build parameters used for the pipeline
* `images/` holds the container images definitions used in this pipeline
* `scripts/` holds the build scripts that ship within the CI container images
* `tasks` contains the task definitions used in the main `pipeline.yml`
=== Updating the build pipeline
Updating files on the repository is not enough to update the build pipeline, as changes need to be applied.
The pipeline can be deployed using the following command:
[source]
----
$ fly -t spring set-pipeline -p spring-framework-6.1.x -c ci/pipeline.yml -l ci/parameters.yml
----
NOTE: This assumes that you have credhub integration configured with the appropriate secrets.
-10
View File
@@ -1,10 +0,0 @@
logging:
level:
io.spring.concourse: DEBUG
spring:
main:
banner-mode: off
sonatype:
exclude:
- 'build-info\.json'
- '.*\.zip'
-21
View File
@@ -1,21 +0,0 @@
== CI Images
These images are used by CI to run the actual builds.
To build the image locally run the following from this directory:
----
$ docker build --no-cache -f <image-folder>/Dockerfile .
----
For example
----
$ docker build --no-cache -f spring-framework-ci-image/Dockerfile .
----
To test run:
----
$ docker run -it --entrypoint /bin/bash <SHA>
----
-12
View File
@@ -1,12 +0,0 @@
FROM ubuntu:jammy-20240125
ADD setup.sh /setup.sh
ADD get-jdk-url.sh /get-jdk-url.sh
RUN ./setup.sh
ENV JAVA_HOME /opt/openjdk/java17
ENV JDK17 /opt/openjdk/java17
ENV JDK21 /opt/openjdk/java21
ENV JDK23 /opt/openjdk/java23
ENV PATH $JAVA_HOME/bin:$PATH
-17
View File
@@ -1,17 +0,0 @@
#!/bin/bash
set -e
case "$1" in
java17)
echo "https://github.com/bell-sw/Liberica/releases/download/17.0.10%2B13/bellsoft-jdk17.0.10+13-linux-amd64.tar.gz"
;;
java21)
echo "https://github.com/bell-sw/Liberica/releases/download/21.0.2%2B14/bellsoft-jdk21.0.2+14-linux-amd64.tar.gz"
;;
java23)
echo "https://download.java.net/java/early_access/jdk23/17/GPL/openjdk-23-ea+17_linux-x64_bin.tar.gz"
;;
*)
echo $"Unknown java version"
exit 1
esac
-40
View File
@@ -1,40 +0,0 @@
#!/bin/bash
set -ex
###########################################################
# UTILS
###########################################################
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install --no-install-recommends -y tzdata ca-certificates net-tools libxml2-utils git curl libudev1 libxml2-utils iptables iproute2 jq fontconfig
ln -fs /usr/share/zoneinfo/UTC /etc/localtime
dpkg-reconfigure --frontend noninteractive tzdata
rm -rf /var/lib/apt/lists/*
curl https://raw.githubusercontent.com/spring-io/concourse-java-scripts/v0.0.4/concourse-java.sh > /opt/concourse-java.sh
###########################################################
# JAVA
###########################################################
mkdir -p /opt/openjdk
pushd /opt/openjdk > /dev/null
for jdk in java17 java21 java23
do
JDK_URL=$( /get-jdk-url.sh $jdk )
mkdir $jdk
pushd $jdk > /dev/null
curl -L ${JDK_URL} | tar zx --strip-components=1
test -f bin/java
test -f bin/javac
popd > /dev/null
done
popd
###########################################################
# GRADLE ENTERPRISE
###########################################################
cd /
mkdir ~/.gradle
echo 'systemProp.user.name=concourse' > ~/.gradle/gradle.properties
-10
View File
@@ -1,10 +0,0 @@
github-repo: "https://github.com/spring-projects/spring-framework.git"
github-repo-name: "spring-projects/spring-framework"
sonatype-staging-profile: "org.springframework"
docker-hub-organization: "springci"
artifactory-server: "https://repo.spring.io"
branch: "6.1.x"
milestone: "6.1.x"
build-name: "spring-framework"
pipeline-name: "spring-framework"
concourse-url: "https://ci.spring.io"
-293
View File
@@ -1,293 +0,0 @@
anchors:
git-repo-resource-source: &git-repo-resource-source
uri: ((github-repo))
username: ((github-username))
password: ((github-ci-release-token))
branch: ((branch))
gradle-enterprise-task-params: &gradle-enterprise-task-params
DEVELOCITY_ACCESS_KEY: ((gradle_enterprise_secret_access_key))
sonatype-task-params: &sonatype-task-params
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
ARTIFACTORY_SERVER: ((artifactory-server))
ARTIFACTORY_USERNAME: ((artifactory-username))
ARTIFACTORY_PASSWORD: ((artifactory-password))
build-project-task-params: &build-project-task-params
BRANCH: ((branch))
<<: *gradle-enterprise-task-params
docker-resource-source: &docker-resource-source
username: ((docker-hub-username))
password: ((docker-hub-password))
changelog-task-params: &changelog-task-params
name: generated-changelog/tag
tag: generated-changelog/tag
body: generated-changelog/changelog.md
github-task-params: &github-task-params
GITHUB_USERNAME: ((github-username))
GITHUB_TOKEN: ((github-ci-release-token))
resource_types:
- name: registry-image
type: registry-image
source:
<<: *docker-resource-source
repository: concourse/registry-image-resource
tag: 1.8.0
- name: artifactory-resource
type: registry-image
source:
<<: *docker-resource-source
repository: springio/artifactory-resource
tag: 0.0.18
- name: github-release
type: registry-image
source:
<<: *docker-resource-source
repository: concourse/github-release-resource
tag: 1.8.0
- name: github-status-resource
type: registry-image
source:
<<: *docker-resource-source
repository: dpb587/github-status-resource
tag: master
resources:
- name: git-repo
type: git
icon: github
source:
<<: *git-repo-resource-source
- name: ci-images-git-repo
type: git
icon: github
source:
uri: ((github-repo))
branch: ((branch))
paths: ["ci/images/*"]
- name: ci-image
type: registry-image
icon: docker
source:
<<: *docker-resource-source
repository: ((docker-hub-organization))/spring-framework-ci
tag: ((milestone))
- name: artifactory-repo
type: artifactory-resource
icon: package-variant
source:
uri: ((artifactory-server))
username: ((artifactory-username))
password: ((artifactory-password))
build_name: ((build-name))
- name: github-pre-release
type: github-release
icon: briefcase-download-outline
source:
owner: spring-projects
repository: spring-framework
access_token: ((github-ci-release-token))
pre_release: true
release: false
- name: github-release
type: github-release
icon: briefcase-download
source:
owner: spring-projects
repository: spring-framework
access_token: ((github-ci-release-token))
pre_release: false
jobs:
- name: build-ci-images
plan:
- get: git-repo
- get: ci-images-git-repo
trigger: true
- task: build-ci-image
privileged: true
file: git-repo/ci/tasks/build-ci-image.yml
output_mapping:
image: ci-image
vars:
ci-image-name: ci-image
<<: *docker-resource-source
- put: ci-image
params:
image: ci-image/image.tar
- name: stage-milestone
serial: true
plan:
- get: ci-image
- get: git-repo
trigger: false
- task: stage
image: ci-image
file: git-repo/ci/tasks/stage-version.yml
params:
RELEASE_TYPE: M
<<: *gradle-enterprise-task-params
- put: artifactory-repo
params: &artifactory-params
signing_key: ((signing-key))
signing_passphrase: ((signing-passphrase))
repo: libs-staging-local
folder: distribution-repository
build_uri: "https://ci.spring.io/teams/${BUILD_TEAM_NAME}/pipelines/${BUILD_PIPELINE_NAME}/jobs/${BUILD_JOB_NAME}/builds/${BUILD_NAME}"
build_number: "${BUILD_PIPELINE_NAME}-${BUILD_JOB_NAME}-${BUILD_NAME}"
disable_checksum_uploads: true
threads: 8
artifact_set:
- include:
- "/**/framework-api-*.zip"
properties:
"zip.name": "spring-framework"
"zip.displayname": "Spring Framework"
"zip.deployed": "false"
- include:
- "/**/framework-api-*-docs.zip"
properties:
"zip.type": "docs"
- include:
- "/**/framework-api-*-schema.zip"
properties:
"zip.type": "schema"
get_params:
threads: 8
- put: git-repo
params:
repository: stage-git-repo
- name: promote-milestone
serial: true
plan:
- get: ci-image
- get: git-repo
trigger: false
- get: artifactory-repo
trigger: false
passed: [stage-milestone]
params:
download_artifacts: false
save_build_info: true
- task: promote
file: git-repo/ci/tasks/promote-version.yml
params:
RELEASE_TYPE: M
<<: *artifactory-task-params
- task: generate-changelog
file: git-repo/ci/tasks/generate-changelog.yml
params:
RELEASE_TYPE: M
<<: *github-task-params
<<: *docker-resource-source
- put: github-pre-release
params:
<<: *changelog-task-params
- name: stage-rc
serial: true
plan:
- get: ci-image
- get: git-repo
trigger: false
- task: stage
image: ci-image
file: git-repo/ci/tasks/stage-version.yml
params:
RELEASE_TYPE: RC
<<: *gradle-enterprise-task-params
- put: artifactory-repo
params:
<<: *artifactory-params
- put: git-repo
params:
repository: stage-git-repo
- name: promote-rc
serial: true
plan:
- get: ci-image
- get: git-repo
trigger: false
- get: artifactory-repo
trigger: false
passed: [stage-rc]
params:
download_artifacts: false
save_build_info: true
- task: promote
file: git-repo/ci/tasks/promote-version.yml
params:
RELEASE_TYPE: RC
<<: *docker-resource-source
<<: *artifactory-task-params
- task: generate-changelog
file: git-repo/ci/tasks/generate-changelog.yml
params:
RELEASE_TYPE: RC
<<: *github-task-params
- put: github-pre-release
params:
<<: *changelog-task-params
- name: stage-release
serial: true
plan:
- get: ci-image
- get: git-repo
trigger: false
- task: stage
image: ci-image
file: git-repo/ci/tasks/stage-version.yml
params:
RELEASE_TYPE: RELEASE
<<: *gradle-enterprise-task-params
- put: artifactory-repo
params:
<<: *artifactory-params
- put: git-repo
params:
repository: stage-git-repo
- name: promote-release
serial: true
plan:
- get: ci-image
- get: git-repo
trigger: false
- get: artifactory-repo
trigger: false
passed: [stage-release]
params:
download_artifacts: true
save_build_info: true
- task: promote
file: git-repo/ci/tasks/promote-version.yml
params:
RELEASE_TYPE: RELEASE
<<: *docker-resource-source
<<: *artifactory-task-params
<<: *sonatype-task-params
- name: create-github-release
serial: true
plan:
- get: ci-image
- get: git-repo
- get: artifactory-repo
trigger: true
passed: [promote-release]
params:
download_artifacts: false
save_build_info: true
- task: generate-changelog
file: git-repo/ci/tasks/generate-changelog.yml
params:
RELEASE_TYPE: RELEASE
<<: *docker-resource-source
<<: *github-task-params
- put: github-release
params:
<<: *changelog-task-params
groups:
- name: "releases"
jobs: ["stage-milestone", "stage-rc", "stage-release", "promote-milestone", "promote-rc", "promote-release", "create-github-release"]
- name: "ci-images"
jobs: ["build-ci-images"]
-2
View File
@@ -1,2 +0,0 @@
source /opt/concourse-java.sh
setup_symlinks
-12
View File
@@ -1,12 +0,0 @@
#!/bin/bash
set -e
CONFIG_DIR=git-repo/ci/config
version=$( cat artifactory-repo/build-info.json | jq -r '.buildInfo.modules[0].id' | sed 's/.*:.*:\(.*\)/\1/' )
java -jar /github-changelog-generator.jar \
--spring.config.location=${CONFIG_DIR}/changelog-generator.yml \
${version} generated-changelog/changelog.md
echo ${version} > generated-changelog/version
echo v${version} > generated-changelog/tag
-17
View File
@@ -1,17 +0,0 @@
#!/bin/bash
CONFIG_DIR=git-repo/ci/config
version=$( cat artifactory-repo/build-info.json | jq -r '.buildInfo.modules[0].id' | sed 's/.*:.*:\(.*\)/\1/' )
export BUILD_INFO_LOCATION=$(pwd)/artifactory-repo/build-info.json
java -jar /concourse-release-scripts.jar \
--spring.config.location=${CONFIG_DIR}/release-scripts.yml \
publishToCentral $RELEASE_TYPE $BUILD_INFO_LOCATION artifactory-repo || { exit 1; }
java -jar /concourse-release-scripts.jar \
--spring.config.location=${CONFIG_DIR}/release-scripts.yml \
promote $RELEASE_TYPE $BUILD_INFO_LOCATION || { exit 1; }
echo "Promotion complete"
echo $version > version/version
-51
View File
@@ -1,51 +0,0 @@
#!/bin/bash
set -e
source $(dirname $0)/common.sh
repository=$(pwd)/distribution-repository
pushd git-repo > /dev/null
git fetch --tags --all > /dev/null
popd > /dev/null
git clone git-repo stage-git-repo > /dev/null
pushd stage-git-repo > /dev/null
snapshotVersion=$( awk -F '=' '$1 == "version" { print $2 }' gradle.properties )
if [[ $RELEASE_TYPE = "M" ]]; then
stageVersion=$( get_next_milestone_release $snapshotVersion)
nextVersion=$snapshotVersion
elif [[ $RELEASE_TYPE = "RC" ]]; then
stageVersion=$( get_next_rc_release $snapshotVersion)
nextVersion=$snapshotVersion
elif [[ $RELEASE_TYPE = "RELEASE" ]]; then
stageVersion=$( get_next_release $snapshotVersion)
nextVersion=$( bump_version_number $snapshotVersion)
else
echo "Unknown release type $RELEASE_TYPE" >&2; exit 1;
fi
echo "Staging $stageVersion (next version will be $nextVersion)"
sed -i "s/version=$snapshotVersion/version=$stageVersion/" gradle.properties
git config user.name "Spring Builds" > /dev/null
git config user.email "spring-builds@users.noreply.github.com" > /dev/null
git add gradle.properties > /dev/null
git commit -m"Release v$stageVersion" > /dev/null
git tag -a "v$stageVersion" -m"Release v$stageVersion" > /dev/null
./gradlew --no-daemon --max-workers=4 -PdeploymentRepository=${repository} -Porg.gradle.java.installations.fromEnv=JDK17,JDK21 \
build publishAllPublicationsToDeploymentRepository
git reset --hard HEAD^ > /dev/null
if [[ $nextVersion != $snapshotVersion ]]; then
echo "Setting next development version (v$nextVersion)"
sed -i "s/version=$snapshotVersion/version=$nextVersion/" gradle.properties
git add gradle.properties > /dev/null
git commit -m"Next development version (v$nextVersion)" > /dev/null
fi;
echo "Staging Complete"
popd > /dev/null
-30
View File
@@ -1,30 +0,0 @@
---
platform: linux
image_resource:
type: registry-image
source:
repository: concourse/oci-build-task
tag: 0.10.0
username: ((docker-hub-username))
password: ((docker-hub-password))
inputs:
- name: ci-images-git-repo
outputs:
- name: image
caches:
- path: ci-image-cache
params:
CONTEXT: ci-images-git-repo/ci/images
DOCKERFILE: ci-images-git-repo/ci/images/ci-image/Dockerfile
DOCKER_HUB_AUTH: ((docker-hub-auth))
run:
path: /bin/sh
args:
- "-c"
- |
mkdir -p /root/.docker
cat > /root/.docker/config.json <<EOF
{ "auths": { "https://index.docker.io/v1/": { "auth": "$DOCKER_HUB_AUTH" }}}
EOF
build
-22
View File
@@ -1,22 +0,0 @@
---
platform: linux
image_resource:
type: registry-image
source:
repository: springio/github-changelog-generator
tag: '0.0.8'
username: ((docker-hub-username))
password: ((docker-hub-password))
inputs:
- name: git-repo
- name: artifactory-repo
outputs:
- name: generated-changelog
params:
GITHUB_ORGANIZATION:
GITHUB_REPO:
GITHUB_USERNAME:
GITHUB_TOKEN:
RELEASE_TYPE:
run:
path: git-repo/ci/scripts/generate-changelog.sh
-25
View File
@@ -1,25 +0,0 @@
---
platform: linux
image_resource:
type: registry-image
source:
repository: springio/concourse-release-scripts
tag: '0.4.0'
username: ((docker-hub-username))
password: ((docker-hub-password))
inputs:
- name: git-repo
- name: artifactory-repo
outputs:
- name: version
params:
RELEASE_TYPE:
ARTIFACTORY_SERVER:
ARTIFACTORY_USERNAME:
ARTIFACTORY_PASSWORD:
SONATYPE_USER:
SONATYPE_PASSWORD:
SONATYPE_URL:
SONATYPE_STAGING_PROFILE:
run:
path: git-repo/ci/scripts/promote-version.sh
-17
View File
@@ -1,17 +0,0 @@
---
platform: linux
inputs:
- name: git-repo
outputs:
- name: stage-git-repo
- name: distribution-repository
params:
RELEASE_TYPE:
CI: true
GRADLE_ENTERPRISE_CACHE_USERNAME:
GRADLE_ENTERPRISE_CACHE_PASSWORD:
GRADLE_ENTERPRISE_URL: https://ge.spring.io
caches:
- path: gradle
run:
path: git-repo/ci/scripts/stage-version.sh
+2 -2
View File
@@ -28,7 +28,7 @@ javadoc {
header = rootProject.description
use = true
overview = project.relativePath("$rootProject.rootDir/framework-docs/src/docs/api/overview.html")
destinationDir = file("$project.docsDir/javadoc-api")
destinationDir = project.java.docsDir.dir("javadoc-api").get().asFile
splitIndex = true
links(rootProject.ext.javadocLinks)
addBooleanOption('Xdoclint:syntax,reference', true) // only check syntax and reference with doclint
@@ -52,7 +52,7 @@ rootProject.tasks.dokkaHtmlMultiModule.configure {
tasks.named("javadoc")
}
moduleName.set("spring-framework")
outputDirectory.set(file("$docsDir/kdoc-api"))
outputDirectory.set(project.java.docsDir.dir("kdoc-api").get().asFile)
includes.from("$rootProject.rootDir/framework-docs/src/docs/api/dokka-overview.md")
}
+5 -1
View File
@@ -20,6 +20,7 @@ asciidoc:
fold: 'all'
table-stripes: 'odd'
include-java: 'example$docs-src/main/java/org/springframework/docs'
include-kotlin: 'example$docs-src/main/kotlin/org/springframework/docs'
spring-site: 'https://spring.io'
spring-site-blog: '{spring-site}/blog'
spring-site-cve: "{spring-site}/security"
@@ -70,6 +71,8 @@ asciidoc:
kotlin-coroutines-api: '{kotlin-site}/api/kotlinx.coroutines'
kotlin-github-org: 'https://github.com/Kotlin'
kotlin-issues: 'https://youtrack.jetbrains.com/issue'
micrometer-docs: 'https://docs.micrometer.io/micrometer/reference'
micrometer-context-propagation-docs: 'https://docs.micrometer.io/context-propagation/reference'
reactive-streams-site: 'https://www.reactive-streams.org'
reactive-streams-spec: 'https://github.com/reactive-streams/reactive-streams-jvm/blob/master/README.md#specification'
reactor-github-org: 'https://github.com/reactor'
@@ -87,4 +90,5 @@ asciidoc:
stackoverflow-questions: '{stackoverflow-site}/questions'
stackoverflow-spring-tag: "{stackoverflow-questions}/tagged/spring"
stackoverflow-spring-kotlin-tags: "{stackoverflow-spring-tag}+kotlin"
testcontainers-site: 'https://www.testcontainers.org'
testcontainers-site: 'https://www.testcontainers.org'
vavr-docs: 'https://vavr-io.github.io/vavr-docs'
+5
View File
@@ -43,10 +43,15 @@ repositories {
dependencies {
api(project(":spring-context"))
api(project(":spring-jdbc"))
api(project(":spring-jms"))
api(project(":spring-web"))
api(project(":spring-webflux"))
api("com.oracle.database.jdbc:ojdbc11")
api("jakarta.jms:jakarta.jms-api")
api("jakarta.servlet:jakarta.servlet-api")
api("org.jetbrains.kotlin:kotlin-stdlib")
implementation(project(":spring-core-test"))
implementation("org.assertj:assertj-core")
@@ -2,13 +2,14 @@
= Using `depends-on`
If a bean is a dependency of another bean, that usually means that one bean is set as a
property of another. Typically you accomplish this with the <<beans-ref-element, `<ref/>`
element>> in XML-based configuration metadata. However, sometimes dependencies between
beans are less direct. An example is when a static initializer in a class needs to be
triggered, such as for database driver registration. The `depends-on` attribute can
explicitly force one or more beans to be initialized before the bean using this element
is initialized. The following example uses the `depends-on` attribute to express a
dependency on a single bean:
property of another. Typically you accomplish this with the
xref:core/beans/dependencies/factory-properties-detailed.adoc#beans-ref-element[`<ref/>` element>]
in XML-based configuration metadata. However, sometimes dependencies between beans are
less direct. An example is when a static initializer in a class needs to be triggered,
such as for database driver registration. The `depends-on` attribute can explicitly force
one or more beans to be initialized before the bean using this element is initialized.
The following example uses the `depends-on` attribute to express a dependency on a single
bean:
[source,xml,indent=0,subs="verbatim,quotes"]
----
@@ -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>
@@ -431,7 +431,7 @@ Kotlin::
A `ConstraintViolation` on `Person.name()` is adapted to a `FieldError` with the following:
- Error codes `"Size.student.name"`, `"Size.name"`, `"Size.java.lang.String"`, and `"Size"`
- Error codes `"Size.person.name"`, `"Size.name"`, `"Size.java.lang.String"`, and `"Size"`
- Message arguments `"name"`, `10`, and `1` (the field name and the constraint attributes)
- Default message "size must be between 1 and 10"
@@ -439,14 +439,14 @@ To customize the default message, you can add properties to
xref:core/beans/context-introduction.adoc#context-functionality-messagesource[MessageSource]
resource bundles using any of the above errors codes and message arguments. Note also that the
message argument `"name"` is itself a `MessagreSourceResolvable` with error codes
`"student.name"` and `"name"` and can customized too. For example:
`"person.name"` and `"name"` and can customized too. For example:
Properties::
+
[source,properties,indent=0,subs="verbatim,quotes",role="secondary"]
----
Size.student.name=Please, provide a {0} that is between {2} and {1} characters long
student.name=username
Size.person.name=Please, provide a {0} that is between {2} and {1} characters long
person.name=username
----
A `ConstraintViolation` on the `degrees` method parameter is adapted to a
@@ -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.
====
@@ -209,141 +209,26 @@ are passed in as a parameter to the stored procedure.
The `SqlReturnType` interface has a single method (named `getTypeValue`) that must be
implemented. This interface is used as part of the declaration of an `SqlOutParameter`.
The following example shows returning the value of an Oracle `STRUCT` object of the user
The following example shows returning the value of a `java.sql.Struct` object of the user
declared type `ITEM_TYPE`:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
public class TestItemStoredProcedure extends StoredProcedure {
public TestItemStoredProcedure(DataSource dataSource) {
// ...
declareParameter(new SqlOutParameter("item", OracleTypes.STRUCT, "ITEM_TYPE",
(CallableStatement cs, int colIndx, int sqlType, String typeName) -> {
STRUCT struct = (STRUCT) cs.getObject(colIndx);
Object[] attr = struct.getAttributes();
TestItem item = new TestItem();
item.setId(((Number) attr[0]).longValue());
item.setDescription((String) attr[1]);
item.setExpirationDate((java.util.Date) attr[2]);
return item;
}));
// ...
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
class TestItemStoredProcedure(dataSource: DataSource) : StoredProcedure() {
init {
// ...
declareParameter(SqlOutParameter("item", OracleTypes.STRUCT, "ITEM_TYPE") { cs, colIndx, sqlType, typeName ->
val struct = cs.getObject(colIndx) as STRUCT
val attr = struct.getAttributes()
TestItem((attr[0] as Long, attr[1] as String, attr[2] as Date)
})
// ...
}
}
----
======
include-code::./TestItemStoredProcedure[]
You can use `SqlTypeValue` to pass the value of a Java object (such as `TestItem`) to a
stored procedure. The `SqlTypeValue` interface has a single method (named
`createTypeValue`) that you must implement. The active connection is passed in, and you
can use it to create database-specific objects, such as `StructDescriptor` instances
or `ArrayDescriptor` instances. The following example creates a `StructDescriptor` instance:
can use it to create database-specific objects, such as `java.sql.Struct` instances
or `java.sql.Array` instances. The following example creates a `java.sql.Struct` instance:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
final TestItem testItem = new TestItem(123L, "A test item",
new SimpleDateFormat("yyyy-M-d").parse("2010-12-31"));
SqlTypeValue value = new AbstractSqlTypeValue() {
protected Object createTypeValue(Connection conn, int sqlType, String typeName) throws SQLException {
StructDescriptor itemDescriptor = new StructDescriptor(typeName, conn);
Struct item = new STRUCT(itemDescriptor, conn,
new Object[] {
testItem.getId(),
testItem.getDescription(),
new java.sql.Date(testItem.getExpirationDate().getTime())
});
return item;
}
};
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val (id, description, expirationDate) = TestItem(123L, "A test item",
SimpleDateFormat("yyyy-M-d").parse("2010-12-31"))
val value = object : AbstractSqlTypeValue() {
override fun createTypeValue(conn: Connection, sqlType: Int, typeName: String?): Any {
val itemDescriptor = StructDescriptor(typeName, conn)
return STRUCT(itemDescriptor, conn,
arrayOf(id, description, java.sql.Date(expirationDate.time)))
}
}
----
======
include-code::./SqlTypeValueFactory[tag=struct,indent=0]
You can now add this `SqlTypeValue` to the `Map` that contains the input parameters for the
`execute` call of the stored procedure.
Another use for the `SqlTypeValue` is passing in an array of values to an Oracle stored
procedure. Oracle has its own internal `ARRAY` class that must be used in this case, and
you can use the `SqlTypeValue` to create an instance of the Oracle `ARRAY` and populate
it with values from the Java `ARRAY`, as the following example shows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
final Long[] ids = new Long[] {1L, 2L};
SqlTypeValue value = new AbstractSqlTypeValue() {
protected Object createTypeValue(Connection conn, int sqlType, String typeName) throws SQLException {
ArrayDescriptor arrayDescriptor = new ArrayDescriptor(typeName, conn);
ARRAY idArray = new ARRAY(arrayDescriptor, conn, ids);
return idArray;
}
};
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
class TestItemStoredProcedure(dataSource: DataSource) : StoredProcedure() {
init {
val ids = arrayOf(1L, 2L)
val value = object : AbstractSqlTypeValue() {
override fun createTypeValue(conn: Connection, sqlType: Int, typeName: String?): Any {
val arrayDescriptor = ArrayDescriptor(typeName, conn)
return ARRAY(arrayDescriptor, conn, ids)
}
}
}
}
----
======
procedure. Oracle has an `createOracleArray` method on `OracleConnection` that you can
access by unwrapping it. You can use the `SqlTypeValue` to create an array and populate
it with values from the Java `java.sql.Array`, as the following example shows:
include-code::./SqlTypeValueFactory[tag=oracle-array,indent=0]
@@ -323,12 +323,12 @@ use these alternative input classes.
The `SimpleJdbcCall` class uses metadata in the database to look up names of `in`
and `out` parameters so that you do not have to explicitly declare them. You can
declare parameters if you prefer to do that or if you have parameters (such as `ARRAY`
or `STRUCT`) that do not have an automatic mapping to a Java class. The first example
shows a simple procedure that returns only scalar values in `VARCHAR` and `DATE` format
from a MySQL database. The example procedure reads a specified actor entry and returns
`first_name`, `last_name`, and `birth_date` columns in the form of `out` parameters.
The following listing shows the first example:
declare parameters if you prefer to do that or if you have parameters that do not
have an automatic mapping to a Java class. The first example shows a simple procedure
that returns only scalar values in `VARCHAR` and `DATE` format from a MySQL database.
The example procedure reads a specified actor entry and returns `first_name`,
`last_name`, and `birth_date` columns in the form of `out` parameters. The following
listing shows the first example:
[source,sql,indent=0,subs="verbatim,quotes"]
----
@@ -23,7 +23,7 @@ As of Spring Framework 5.2, the default configuration also provides support for
Vavr's `Try` method to trigger transaction rollbacks when it returns a 'Failure'.
This allows you to handle functional-style errors using Try and have the transaction
automatically rolled back in case of a failure. For more information on Vavr's Try,
refer to the https://docs.vavr.io/#_try[official Vavr documentation].
refer to the {vavr-docs}/#_try[official Vavr documentation].
Here's an example of how to use Vavr's Try with a transactional method:
[tabs]
@@ -524,7 +524,7 @@ To enable caching annotations add the annotation `@EnableCaching` to one of your
----
@Configuration
@EnableCaching
public class AppConfig {
class AppConfig {
@Bean
CacheManager cacheManager() {
@@ -1,7 +1,7 @@
[[observability]]
= Observability Support
Micrometer defines an https://micrometer.io/docs/observation[Observation concept that enables both Metrics and Traces] in applications.
Micrometer defines an {micrometer-docs}/observation.html[Observation concept that enables both Metrics and Traces] in applications.
Metrics support offers a way to create timers, gauges, or counters for collecting statistics about the runtime behavior of your application.
Metrics can help you to track error rates, usage patterns, performance, and more.
Traces provide a holistic view of an entire system, crossing application boundaries; you can zoom in on particular user requests and follow their entire completion across applications.
@@ -38,7 +38,7 @@ As outlined xref:integration/observability.adoc[at the beginning of this section
|===
NOTE: Observations are using Micrometer's official naming convention, but Metrics names will be automatically converted
https://micrometer.io/docs/concepts#_naming_meters[to the format preferred by the monitoring system backend]
{micrometer-docs}/concepts/naming.html[to the format preferred by the monitoring system backend]
(Prometheus, Atlas, Graphite, InfluxDB...).
@@ -162,6 +162,8 @@ include-code::./JmsTemplatePublish[]
It uses the `io.micrometer.jakarta9.instrument.jms.DefaultJmsPublishObservationConvention` by default, backed by the `io.micrometer.jakarta9.instrument.jms.JmsPublishObservationContext`.
Similar observations are recorded with `@JmsListener` annotated methods when response messages are returned from the listener method.
[[observability.jms.process]]
=== JMS message Processing instrumentation
@@ -366,7 +368,7 @@ This means that during the execution of that task, the ThreadLocals and logging
If the application globally configures a custom `ApplicationEventMulticaster` with a strategy that schedules event processing on different threads, this is no longer true.
All `@EventListener` methods will be processed on a different thread, outside the main event publication thread.
In these cases, the https://micrometer.io/docs/contextPropagation[Micrometer Context Propagation library] can help propagate such values and better correlate the processing of the events.
In these cases, the {micrometer-context-propagation-docs}/[Micrometer Context Propagation library] can help propagate such values and better correlate the processing of the events.
The application can configure the chosen `TaskExecutor` to use a `ContextPropagatingTaskDecorator` that decorates tasks and propagates context.
For this to work, the `io.micrometer:context-propagation` library must be present on the classpath:
@@ -289,7 +289,7 @@ String result = restClient.get() <1>
.uri("https://example.com/this-url-does-not-exist") <1>
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, (request, response) -> { <2>
throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()) <3>
throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()); <3>
})
.body(String.class);
----
@@ -339,6 +339,19 @@ Java::
spec -> spec.expectHeader().contentType(MediaType.APPLICATION_JSON)
);
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
client.get().uri("/persons/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectAll(
{ spec -> spec.expectStatus().isOk() },
{ spec -> spec.expectHeader().contentType(MediaType.APPLICATION_JSON) }
)
----
======
You can then choose to decode the response body through one of the following:
@@ -704,4 +717,3 @@ Kotlin::
.andExpect(model().attribute("string", "a string value"));
----
======
@@ -688,40 +688,7 @@ You can customize options related to path matching. For details on the individua
{spring-framework-api}/web/reactive/config/PathMatchConfigurer.html[`PathMatchConfigurer`] javadoc.
The following example shows how to use `PathMatchConfigurer`:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix(
"/api", HandlerTypePredicate.forAnnotation(RestController.class));
}
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {
@Override
fun configurePathMatch(configurer: PathMatchConfigurer) {
configurer.addPathPrefix(
"/api", HandlerTypePredicate.forAnnotation(RestController::class.java))
}
}
----
======
include-code::./WebConfig[]
[TIP]
====
@@ -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.
@@ -445,9 +445,8 @@ directly. For example:
}
----
For more details, see the
https://micrometer.io/docs/contextPropagation[documentation] of the Micrometer Context
Propagation library.
For more details, see the {micrometer-context-propagation-docs}/[documentation] of the
Micrometer Context Propagation library.
@@ -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
@@ -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.
@@ -0,0 +1,59 @@
/*
* 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.docs.dataaccess.jdbc.jdbccomplextypes;
import java.sql.Connection;
import java.sql.SQLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import oracle.jdbc.driver.OracleConnection;
import org.springframework.jdbc.core.SqlTypeValue;
import org.springframework.jdbc.core.support.AbstractSqlTypeValue;
@SuppressWarnings("unused")
class SqlTypeValueFactory {
void createStructSample() throws ParseException {
// tag::struct[]
TestItem testItem = new TestItem(123L, "A test item",
new SimpleDateFormat("yyyy-M-d").parse("2010-12-31"));
SqlTypeValue value = new AbstractSqlTypeValue() {
protected Object createTypeValue(Connection connection, int sqlType, String typeName) throws SQLException {
Object[] item = new Object[] { testItem.getId(), testItem.getDescription(),
new java.sql.Date(testItem.getExpirationDate().getTime()) };
return connection.createStruct(typeName, item);
}
};
// end::struct[]
}
void createOracleArray() {
// tag::oracle-array[]
Long[] ids = new Long[] {1L, 2L};
SqlTypeValue value = new AbstractSqlTypeValue() {
protected Object createTypeValue(Connection conn, int sqlType, String typeName) throws SQLException {
return conn.unwrap(OracleConnection.class).createOracleArray(typeName, ids);
}
};
// end::oracle-array[]
}
}
@@ -0,0 +1,61 @@
/*
* 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.docs.dataaccess.jdbc.jdbccomplextypes;
import java.util.Date;
class TestItem {
private Long id;
private String description;
private Date expirationDate;
public TestItem() {
}
public TestItem(Long id, String description, Date expirationDate) {
this.id = id;
this.description = description;
this.expirationDate = expirationDate;
}
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public String getDescription() {
return this.description;
}
public void setDescription(String description) {
this.description = description;
}
public Date getExpirationDate() {
return this.expirationDate;
}
public void setExpirationDate(Date expirationDate) {
this.expirationDate = expirationDate;
}
}
@@ -0,0 +1,46 @@
/*
* 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.docs.dataaccess.jdbc.jdbccomplextypes;
import java.sql.CallableStatement;
import java.sql.Struct;
import java.sql.Types;
import javax.sql.DataSource;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.object.StoredProcedure;
@SuppressWarnings("unused")
public class TestItemStoredProcedure extends StoredProcedure {
public TestItemStoredProcedure(DataSource dataSource) {
super(dataSource, "get_item");
declareParameter(new SqlOutParameter("item", Types.STRUCT, "ITEM_TYPE",
(CallableStatement cs, int colIndx, int sqlType, String typeName) -> {
Struct struct = (Struct) cs.getObject(colIndx);
Object[] attr = struct.getAttributes();
TestItem item = new TestItem();
item.setId(((Number) attr[0]).longValue());
item.setDescription((String) attr[1]);
item.setExpirationDate((java.util.Date) attr[2]);
return item;
}));
// ...
}
}
@@ -0,0 +1,35 @@
/*
* 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.docs.web.webflux.webfluxconfigpathmatching;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.method.HandlerTypePredicate;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.reactive.config.PathMatchConfigurer;
import org.springframework.web.reactive.config.WebFluxConfigurer;
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Override
public void configurePathMatching(PathMatchConfigurer configurer) {
configurer.addPathPrefix(
"/api", HandlerTypePredicate.forAnnotation(RestController.class));
}
}
@@ -0,0 +1,57 @@
/*
* 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.docs.dataaccess.jdbc.jdbccomplextypes
import oracle.jdbc.driver.OracleConnection
import org.springframework.jdbc.core.SqlTypeValue
import org.springframework.jdbc.core.support.AbstractSqlTypeValue
import java.sql.Connection
import java.sql.Date
import java.text.SimpleDateFormat
@Suppress("unused")
class SqlTypeValueFactory {
fun createStructSample(): AbstractSqlTypeValue {
// tag::struct[]
val testItem = TestItem(123L, "A test item",
SimpleDateFormat("yyyy-M-d").parse("2010-12-31"))
val value = object : AbstractSqlTypeValue() {
override fun createTypeValue(connection: Connection, sqlType: Int, typeName: String?): Any {
val item = arrayOf<Any>(testItem.id, testItem.description,
Date(testItem.expirationDate.time))
return connection.createStruct(typeName, item)
}
}
// end::struct[]
return value
}
fun createOracleArray() : SqlTypeValue {
// tag::oracle-array[]
val ids = arrayOf(1L, 2L)
val value: SqlTypeValue = object : AbstractSqlTypeValue() {
override fun createTypeValue(conn: Connection, sqlType: Int, typeName: String?): Any {
return conn.unwrap(OracleConnection::class.java).createOracleArray(typeName, ids)
}
}
// end::oracle-array[]
return value
}
}
@@ -0,0 +1,42 @@
/*
* 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.docs.dataaccess.jdbc.jdbccomplextypes
import org.springframework.jdbc.core.SqlOutParameter
import org.springframework.jdbc.`object`.StoredProcedure
import java.sql.CallableStatement
import java.sql.Struct
import java.sql.Types
import java.util.Date
import javax.sql.DataSource
@Suppress("unused")
class TestItemStoredProcedure(dataSource: DataSource) : StoredProcedure(dataSource, "get_item") {
init {
declareParameter(SqlOutParameter("item",Types.STRUCT,"ITEM_TYPE") {
cs: CallableStatement, colIndx: Int, _: Int, _: String? ->
val struct = cs.getObject(colIndx) as Struct
val attr = struct.attributes
val item = TestItem()
item.id = (attr[0] as Number).toLong()
item.description = attr[1] as String
item.expirationDate = attr[2] as Date
item
})
// ...
}
}
@@ -0,0 +1,34 @@
/*
* 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.docs.web.webflux.webfluxconfigpathmatching
import org.springframework.context.annotation.Configuration
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.method.HandlerTypePredicate
import org.springframework.web.reactive.config.EnableWebFlux
import org.springframework.web.reactive.config.PathMatchConfigurer
import org.springframework.web.reactive.config.WebFluxConfigurer
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {
override fun configurePathMatching(configurer: PathMatchConfigurer) {
configurer.addPathPrefix(
"/api", HandlerTypePredicate.forAnnotation(RestController::class.java))
}
}
+24 -23
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.7"))
api(platform("io.netty:netty-bom:4.1.111.Final"))
api(platform("io.micrometer:micrometer-bom:1.12.9"))
api(platform("io.netty:netty-bom:4.1.112.Final"))
api(platform("io.netty:netty5-bom:5.0.0.Alpha5"))
api(platform("io.projectreactor:reactor-bom:2023.0.7"))
api(platform("io.projectreactor:reactor-bom:2023.0.9"))
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.26.0"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.10"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.10"))
api(platform("org.eclipse.jetty:jetty-bom:12.0.12"))
api(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.0.12"))
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 {
@@ -34,6 +34,7 @@ dependencies {
api("com.google.protobuf:protobuf-java-util:3.25.3")
api("com.h2database:h2:2.2.224")
api("com.jayway.jsonpath:json-path:2.9.0")
api("com.oracle.database.jdbc:ojdbc11:21.9.0.0")
api("com.rometools:rome:1.19.0")
api("com.squareup.okhttp3:mockwebserver:3.14.9")
api("com.squareup.okhttp3:okhttp:3.14.9")
@@ -55,9 +56,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.15.Final")
api("io.undertow:undertow-servlet:2.3.15.Final")
api("io.undertow:undertow-websockets-jsr:2.3.15.Final")
api("io.vavr:vavr:0.10.4")
api("jakarta.activation:jakarta.activation-api:2.0.1")
api("jakarta.annotation:jakarta.annotation-api:2.0.0")
@@ -101,26 +102,26 @@ 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.28")
api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.28")
api("org.apache.tomcat:tomcat-util:10.1.28")
api("org.apache.tomcat:tomcat-websocket:10.1.28")
api("org.aspectj:aspectjrt:1.9.22.1")
api("org.aspectj:aspectjtools:1.9.22.1")
api("org.aspectj:aspectjweaver:1.9.22.1")
api("org.awaitility:awaitility:4.2.1")
api("org.awaitility:awaitility:4.2.0")
api("org.bouncycastle:bcpkix-jdk18on:1.72")
api("org.codehaus.jettison:jettison:1.5.4")
api("org.crac:crac:1.4.0")
api("org.dom4j:dom4j:2.1.4")
api("org.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,16 +130,16 @@ 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.7.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.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")
+2 -2
View File
@@ -1,10 +1,10 @@
version=6.1.10-SNAPSHOT
version=6.1.12
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
+20
View File
@@ -69,6 +69,13 @@ eclipse.classpath.file.whenMerged {
}
}
// Remove Java 21 classpath entries, since we currently use Java 17
// within Eclipse. Consequently, Java 21 features managed via the
// me.champeau.mrjar plugin cannot be built or tested within Eclipse.
eclipse.classpath.file.whenMerged { classpath ->
classpath.entries.removeAll { it.path =~ /src\/(main|test)\/java21/ }
}
// Remove classpath entries for non-existent libraries added by the me.champeau.mrjar
// plugin, such as "spring-core/build/classes/kotlin/java21".
eclipse.classpath.file.whenMerged {
@@ -77,6 +84,19 @@ eclipse.classpath.file.whenMerged {
}
}
// Due to an apparent bug in Gradle, even though we exclude the "main" classpath
// entries for sources generated by XJC in spring-oxm.gradle, the Gradle eclipse
// plugin still includes them in the generated .classpath file. So, we have to
// manually remove those lingering "main" entries.
if (project.name == "spring-oxm") {
eclipse.classpath.file.whenMerged { classpath ->
classpath.entries.removeAll {
it.path =~ /build\/generated\/sources\/xjc\/.+/ &&
it.entryAttributes.get("gradle_scope") == "main"
}
}
}
// Include project specific settings
task eclipseSettings(type: Copy) {
from rootProject.files(
+1 -1
View File
@@ -3,7 +3,7 @@ apply plugin: 'org.springframework.build.conventions'
apply plugin: 'org.springframework.build.optional-dependencies'
// Uncomment the following for Shadow support in the jmhJar block.
// Currently commented out due to ZipException: archive is not a ZIP archive
// apply plugin: 'com.github.johnrengelman.shadow'
// apply plugin: 'io.github.goooler.shadow'
apply plugin: 'me.champeau.jmh'
apply from: "$rootDir/gradle/publications.gradle"
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+5 -2
View File
@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -84,7 +86,8 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
Vendored
+2
View File
@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@@ -20,18 +20,33 @@ 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
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.cache.CacheManager
import org.springframework.cache.annotation.Cacheable
import org.springframework.cache.annotation.EnableCaching
import org.springframework.cache.concurrent.ConcurrentMapCacheManager
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 +58,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 +84,39 @@ 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()
}
@Test // gh-33210
fun `Aspect and cacheable with suspending function`() {
assertThat(countingAspect.counter).isZero()
val value = "Hello!"
runBlocking {
assertThat(echo.suspendingCacheableEcho(value)).isEqualTo("$value 0")
assertThat(echo.suspendingCacheableEcho(value)).isEqualTo("$value 0")
assertThat(echo.suspendingCacheableEcho(value)).isEqualTo("$value 0")
assertThat(countingAspect.counter).`as`("aspect applied once").isOne()
assertThat(echo.suspendingCacheableEcho("$value bis")).isEqualTo("$value bis 1")
assertThat(echo.suspendingCacheableEcho("$value bis")).isEqualTo("$value bis 1")
}
assertThat(countingAspect.counter).`as`("aspect applied once per key").isEqualTo(2)
}
@Configuration
@EnableAspectJAutoProxy
@EnableTransactionManagement
@EnableCaching
open class InterceptorConfig {
@Bean
@@ -77,6 +125,18 @@ 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 cacheManager(): CacheManager {
return ConcurrentMapCacheManager()
}
@Bean
open fun echo(): Echo {
@@ -107,6 +167,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++
}.checkpoint("CountingAspect")
}
}
open class Echo {
open fun echo(value: String): String {
@@ -118,6 +196,22 @@ class AspectJAutoProxyInterceptorKotlinIntegrationTests(
return value
}
@Transactional
@Counting
open suspend fun suspendingTransactionalEcho(value: String): String {
delay(1)
return value
}
open var cacheCounter: Int = 0
@Counting
@Cacheable("something")
open suspend fun suspendingCacheableEcho(value: String): String {
delay(1)
return "$value ${cacheCounter++}"
}
}
}
@@ -553,7 +553,8 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
return true;
}
}
return false;
Class<?> superclass = clazz.getSuperclass();
return (superclass != null && compiledByAjc(superclass));
}
@@ -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");
});
@@ -349,9 +349,10 @@ public abstract class AopUtils {
// Use reflection to invoke the method.
try {
ReflectionUtils.makeAccessible(method);
return (coroutinesReactorPresent && KotlinDetector.isSuspendingFunction(method) ?
KotlinDelegate.invokeSuspendingFunction(method, target, args) : method.invoke(target, args));
Method originalMethod = BridgeMethodResolver.findBridgedMethod(method);
ReflectionUtils.makeAccessible(originalMethod);
return (coroutinesReactorPresent && KotlinDetector.isSuspendingFunction(originalMethod) ?
KotlinDelegate.invokeSuspendingFunction(originalMethod, target, args) : originalMethod.invoke(target, args));
}
catch (InvocationTargetException ex) {
// Invoked method threw a checked exception.
@@ -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.
@@ -34,19 +34,48 @@ class AopUtilsKotlinTests {
@Test
fun `Invoking suspending function should return Mono`() {
val value = "foo"
val method = ReflectionUtils.findMethod(AopUtilsKotlinTests::class.java, "suspendingFunction",
String::class.java, Continuation::class.java)!!
val method = ReflectionUtils.findMethod(WithoutInterface::class.java, "handle",
String::class. java, Continuation::class.java)!!
val continuation = Continuation<Any>(CoroutineName("test")) { }
val result = AopUtils.invokeJoinpointUsingReflection(this, method, arrayOf(value, continuation))
val result = AopUtils.invokeJoinpointUsingReflection(WithoutInterface(), method, arrayOf(value, continuation))
assertThat(result).isInstanceOfSatisfying(Mono::class.java) {
assertThat(it.block()).isEqualTo(value)
}
}
@Test
fun `Invoking suspending function on bridged method should return Mono`() {
val value = "foo"
val bridgedMethod = ReflectionUtils.findMethod(WithInterface::class.java, "handle", Object::class.java, Continuation::class.java)!!
val continuation = Continuation<Any>(CoroutineName("test")) { }
val result = AopUtils.invokeJoinpointUsingReflection(WithInterface(), bridgedMethod, arrayOf(value, continuation))
assertThat(result).isInstanceOfSatisfying(Mono::class.java) {
assertThat(it.block()).isEqualTo(value)
}
}
@Suppress("unused")
suspend fun suspendingFunction(value: String): String {
delay(1)
return value
}
class WithoutInterface {
suspend fun handle(value: String): String {
delay(1)
return value
}
}
interface ProxyInterface<T> {
suspend fun handle(value: T): T
}
class WithInterface : ProxyInterface<String> {
override suspend fun handle(value: String): String {
delay(1)
return value
}
}
}
+1
View File
@@ -16,4 +16,5 @@ dependencies {
testImplementation(project(":spring-core-test"))
testImplementation(testFixtures(project(":spring-core")))
testImplementation("jakarta.annotation:jakarta.annotation-api")
testImplementation("javax.inject:javax.inject")
}
@@ -63,6 +63,7 @@ import org.springframework.beans.factory.aot.AutowiredMethodArgumentsResolver;
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
import org.springframework.beans.factory.aot.BeanRegistrationCode;
import org.springframework.beans.factory.aot.CodeWarnings;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor;
@@ -984,8 +985,11 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
method.addParameter(RegisteredBean.class, REGISTERED_BEAN_PARAMETER);
method.addParameter(this.target, INSTANCE_PARAMETER);
method.returns(this.target);
method.addCode(generateMethodCode(generatedClass.getName(),
generationContext.getRuntimeHints()));
CodeWarnings codeWarnings = new CodeWarnings();
codeWarnings.detectDeprecation(this.target);
method.addCode(generateMethodCode(codeWarnings,
generatedClass.getName(), generationContext.getRuntimeHints()));
codeWarnings.suppress(method);
});
beanRegistrationCode.addInstancePostProcessor(generateMethod.toMethodReference());
@@ -994,35 +998,37 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
}
}
private CodeBlock generateMethodCode(ClassName targetClassName, RuntimeHints hints) {
private CodeBlock generateMethodCode(CodeWarnings codeWarnings,
ClassName targetClassName, RuntimeHints hints) {
CodeBlock.Builder code = CodeBlock.builder();
for (AutowiredElement autowiredElement : this.autowiredElements) {
code.addStatement(generateMethodStatementForElement(
targetClassName, autowiredElement, hints));
codeWarnings, targetClassName, autowiredElement, hints));
}
code.addStatement("return $L", INSTANCE_PARAMETER);
return code.build();
}
private CodeBlock generateMethodStatementForElement(ClassName targetClassName,
AutowiredElement autowiredElement, RuntimeHints hints) {
private CodeBlock generateMethodStatementForElement(CodeWarnings codeWarnings,
ClassName targetClassName, AutowiredElement autowiredElement, RuntimeHints hints) {
Member member = autowiredElement.getMember();
boolean required = autowiredElement.required;
if (member instanceof Field field) {
return generateMethodStatementForField(
targetClassName, field, required, hints);
codeWarnings, targetClassName, field, required, hints);
}
if (member instanceof Method method) {
return generateMethodStatementForMethod(
targetClassName, method, required, hints);
codeWarnings, targetClassName, method, required, hints);
}
throw new IllegalStateException(
"Unsupported member type " + member.getClass().getName());
}
private CodeBlock generateMethodStatementForField(ClassName targetClassName,
Field field, boolean required, RuntimeHints hints) {
private CodeBlock generateMethodStatementForField(CodeWarnings codeWarnings,
ClassName targetClassName, Field field, boolean required, RuntimeHints hints) {
hints.reflection().registerField(field);
CodeBlock resolver = CodeBlock.of("$T.$L($S)",
@@ -1033,18 +1039,22 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
return CodeBlock.of("$L.resolveAndSet($L, $L)", resolver,
REGISTERED_BEAN_PARAMETER, INSTANCE_PARAMETER);
}
return CodeBlock.of("$L.$L = $L.resolve($L)", INSTANCE_PARAMETER,
field.getName(), resolver, REGISTERED_BEAN_PARAMETER);
else {
codeWarnings.detectDeprecation(field);
return CodeBlock.of("$L.$L = $L.resolve($L)", INSTANCE_PARAMETER,
field.getName(), resolver, REGISTERED_BEAN_PARAMETER);
}
}
private CodeBlock generateMethodStatementForMethod(ClassName targetClassName,
Method method, boolean required, RuntimeHints hints) {
private CodeBlock generateMethodStatementForMethod(CodeWarnings codeWarnings,
ClassName targetClassName, Method method, boolean required, RuntimeHints hints) {
CodeBlock.Builder code = CodeBlock.builder();
code.add("$T.$L", AutowiredMethodArgumentsResolver.class,
(!required ? "forMethod" : "forRequiredMethod"));
code.add("($S", method.getName());
if (method.getParameterCount() > 0) {
codeWarnings.detectDeprecation(method.getParameterTypes());
code.add(", $L", generateParameterTypesCode(method.getParameterTypes()));
}
code.add(")");
@@ -1054,6 +1064,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
code.add(".resolveAndInvoke($L, $L)", REGISTERED_BEAN_PARAMETER, INSTANCE_PARAMETER);
}
else {
codeWarnings.detectDeprecation(method);
hints.reflection().registerMethod(method, ExecutableMode.INTROSPECT);
CodeBlock arguments = new AutowiredArgumentsCodeGenerator(this.target,
method).generateCode(method.getParameterTypes());
@@ -1098,7 +1109,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.
@@ -24,16 +24,24 @@ import org.springframework.aot.hint.TypeReference;
import org.springframework.lang.Nullable;
/**
* {@link RuntimeHintsRegistrar} for Jakarta annotations.
* {@link RuntimeHintsRegistrar} for Jakarta annotations and their pre-Jakarta equivalents.
*
* @author Brian Clozel
* @author Sam Brannen
*/
class JakartaAnnotationsRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
Stream.of("jakarta.inject.Inject", "jakarta.inject.Provider", "jakarta.inject.Qualifier").forEach(typeName ->
hints.reflection().registerType(TypeReference.of(typeName)));
// javax.inject.Provider is omitted from the list, since we do not currently load
// it via reflection.
Stream.of(
"jakarta.inject.Inject",
"jakarta.inject.Provider",
"jakarta.inject.Qualifier",
"javax.inject.Inject",
"javax.inject.Qualifier"
).forEach(typeName -> hints.reflection().registerType(TypeReference.of(typeName)));
}
}
@@ -47,11 +47,13 @@ import org.springframework.util.ObjectUtils;
* against {@link Qualifier qualifier annotations} on the field or parameter to be autowired.
* Also supports suggested expression values through a {@link Value value} annotation.
*
* <p>Also supports JSR-330's {@link jakarta.inject.Qualifier} annotation, if available.
* <p>Also supports JSR-330's {@link jakarta.inject.Qualifier} annotation (as well as its
* pre-Jakarta {@code javax.inject.Qualifier} equivalent), if available.
*
* @author Mark Fisher
* @author Juergen Hoeller
* @author Stephane Nicoll
* @author Sam Brannen
* @since 2.5
* @see AutowireCandidateQualifier
* @see Qualifier
@@ -65,9 +67,10 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
/**
* Create a new QualifierAnnotationAutowireCandidateResolver
* for Spring's standard {@link Qualifier} annotation.
* <p>Also supports JSR-330's {@link jakarta.inject.Qualifier} annotation, if available.
* Create a new {@code QualifierAnnotationAutowireCandidateResolver} for Spring's
* standard {@link Qualifier} annotation.
* <p>Also supports JSR-330's {@link jakarta.inject.Qualifier} annotation (as well as
* its pre-Jakarta {@code javax.inject.Qualifier} equivalent), if available.
*/
@SuppressWarnings("unchecked")
public QualifierAnnotationAutowireCandidateResolver() {
@@ -76,14 +79,21 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
this.qualifierTypes.add((Class<? extends Annotation>) ClassUtils.forName("jakarta.inject.Qualifier",
QualifierAnnotationAutowireCandidateResolver.class.getClassLoader()));
}
catch (ClassNotFoundException ex) {
// JSR-330 API (as included in Jakarta EE) not available - simply skip.
}
try {
this.qualifierTypes.add((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Qualifier",
QualifierAnnotationAutowireCandidateResolver.class.getClassLoader()));
}
catch (ClassNotFoundException ex) {
// JSR-330 API not available - simply skip.
}
}
/**
* Create a new QualifierAnnotationAutowireCandidateResolver
* for the given qualifier annotation type.
* Create a new {@code QualifierAnnotationAutowireCandidateResolver} for the given
* qualifier annotation type.
* @param qualifierType the qualifier annotation to look for
*/
public QualifierAnnotationAutowireCandidateResolver(Class<? extends Annotation> qualifierType) {
@@ -92,8 +102,8 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
}
/**
* Create a new QualifierAnnotationAutowireCandidateResolver
* for the given qualifier annotation types.
* Create a new {@code QualifierAnnotationAutowireCandidateResolver} for the given
* qualifier annotation types.
* @param qualifierTypes the qualifier annotations to look for
*/
public QualifierAnnotationAutowireCandidateResolver(Set<Class<? extends Annotation>> qualifierTypes) {
@@ -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);
}
@@ -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.
@@ -21,12 +21,16 @@ import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.StringJoiner;
import java.util.function.Consumer;
import java.util.stream.Stream;
import org.springframework.core.ResolvableType;
import org.springframework.javapoet.AnnotationSpec;
import org.springframework.javapoet.AnnotationSpec.Builder;
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.FieldSpec;
import org.springframework.javapoet.MethodSpec;
import org.springframework.javapoet.TypeSpec;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
@@ -38,7 +42,7 @@ import org.springframework.util.ClassUtils;
* @since 6.1
* @see SuppressWarnings
*/
class CodeWarnings {
public class CodeWarnings {
private final Set<String> warnings = new LinkedHashSet<>();
@@ -59,7 +63,7 @@ class CodeWarnings {
*/
public CodeWarnings detectDeprecation(AnnotatedElement... elements) {
for (AnnotatedElement element : elements) {
register(element.getAnnotation(Deprecated.class));
registerDeprecationIfNecessary(element);
}
return this;
}
@@ -99,10 +103,31 @@ class CodeWarnings {
* @param method the method to update
*/
public void suppress(MethodSpec.Builder method) {
if (this.warnings.isEmpty()) {
return;
suppress(annotationBuilder -> method.addAnnotation(annotationBuilder.build()));
}
/**
* Include {@link SuppressWarnings} on the specified type if necessary.
* @param type the type to update
*/
public void suppress(TypeSpec.Builder type) {
suppress(annotationBuilder -> type.addAnnotation(annotationBuilder.build()));
}
/**
* Consume the builder for {@link SuppressWarnings} if necessary. If this
* instance has no warnings registered, the consumer is not invoked.
* @param annotationSpec a consumer of the {@link AnnotationSpec.Builder}
* @see MethodSpec.Builder#addAnnotation(AnnotationSpec)
* @see TypeSpec.Builder#addAnnotation(AnnotationSpec)
* @see FieldSpec.Builder#addAnnotation(AnnotationSpec)
*/
protected void suppress(Consumer<AnnotationSpec.Builder> annotationSpec) {
if (!this.warnings.isEmpty()) {
Builder annotation = AnnotationSpec.builder(SuppressWarnings.class)
.addMember("value", generateValueCode());
annotationSpec.accept(annotation);
}
method.addAnnotation(buildAnnotationSpec());
}
/**
@@ -113,6 +138,16 @@ class CodeWarnings {
return Collections.unmodifiableSet(this.warnings);
}
private void registerDeprecationIfNecessary(@Nullable AnnotatedElement element) {
if (element == null) {
return;
}
register(element.getAnnotation(Deprecated.class));
if (element instanceof Class<?> type) {
registerDeprecationIfNecessary(type.getEnclosingClass());
}
}
private void register(@Nullable Deprecated annotation) {
if (annotation != null) {
if (annotation.forRemoval()) {
@@ -124,11 +159,6 @@ class CodeWarnings {
}
}
private AnnotationSpec buildAnnotationSpec() {
return AnnotationSpec.builder(SuppressWarnings.class)
.addMember("value", generateValueCode()).build();
}
private CodeBlock generateValueCode() {
if (this.warnings.size() == 1) {
return CodeBlock.of("$S", this.warnings.iterator().next());
@@ -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.
@@ -123,16 +123,16 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
@Nullable
private static Class<?> javaxInjectProviderClass;
private static Class<?> jakartaInjectProviderClass;
static {
try {
javaxInjectProviderClass =
jakartaInjectProviderClass =
ClassUtils.forName("jakarta.inject.Provider", DefaultListableBeanFactory.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
// JSR-330 API not available - Provider interface simply not supported then.
javaxInjectProviderClass = null;
jakartaInjectProviderClass = null;
}
}
@@ -1340,7 +1340,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
ObjectProvider.class == descriptor.getDependencyType()) {
return new DependencyObjectProvider(descriptor, requestingBeanName);
}
else if (javaxInjectProviderClass == descriptor.getDependencyType()) {
else if (jakartaInjectProviderClass == descriptor.getDependencyType()) {
return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName);
}
else if (descriptor.supportsLazyResolution()) {
@@ -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-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.
@@ -30,13 +30,13 @@ import org.springframework.util.StringUtils;
* @author Juergen Hoeller
* @since 26.05.2003
* @see java.util.Locale
* @see org.springframework.util.StringUtils#parseLocaleString
* @see org.springframework.util.StringUtils#parseLocale
*/
public class LocaleEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) {
setValue(StringUtils.parseLocaleString(text));
setValue(StringUtils.parseLocale(text));
}
@Override
@@ -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);
}
}
}
@@ -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.
@@ -21,6 +21,7 @@ import java.util.function.BiFunction;
import javax.lang.model.element.Modifier;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.aot.generate.MethodReference;
@@ -28,9 +29,17 @@ import org.springframework.aot.generate.MethodReference.ArgumentCodeGenerator;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.aot.test.generate.TestGenerationContext;
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
import org.springframework.beans.factory.aot.CodeWarnings;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.testfixture.beans.factory.annotation.DeprecatedInjectionSamples.DeprecatedFieldInjectionPointSample;
import org.springframework.beans.testfixture.beans.factory.annotation.DeprecatedInjectionSamples.DeprecatedFieldInjectionTypeSample;
import org.springframework.beans.testfixture.beans.factory.annotation.DeprecatedInjectionSamples.DeprecatedMethodInjectionPointSample;
import org.springframework.beans.testfixture.beans.factory.annotation.DeprecatedInjectionSamples.DeprecatedMethodInjectionTypeSample;
import org.springframework.beans.testfixture.beans.factory.annotation.DeprecatedInjectionSamples.DeprecatedPrivateFieldInjectionTypeSample;
import org.springframework.beans.testfixture.beans.factory.annotation.DeprecatedInjectionSamples.DeprecatedPrivateMethodInjectionTypeSample;
import org.springframework.beans.testfixture.beans.factory.annotation.DeprecatedInjectionSamples.DeprecatedSample;
import org.springframework.beans.testfixture.beans.factory.annotation.PackagePrivateFieldInjectionSample;
import org.springframework.beans.testfixture.beans.factory.annotation.PackagePrivateMethodInjectionSample;
import org.springframework.beans.testfixture.beans.factory.annotation.PrivateFieldInjectionSample;
@@ -49,6 +58,7 @@ import org.springframework.javapoet.MethodSpec;
import org.springframework.javapoet.ParameterizedTypeName;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatNoException;
/**
* Tests for {@link AutowiredAnnotationBeanPostProcessor} for AOT contributions.
@@ -199,6 +209,69 @@ class AutowiredAnnotationBeanRegistrationAotContributionTests {
assertThat(contribution).isNull();
}
@Nested
@SuppressWarnings("deprecation")
class DeprecationTests {
private static final TestCompiler TEST_COMPILER = TestCompiler.forSystem()
.withCompilerOptions("-Xlint:all", "-Xlint:-rawtypes", "-Werror");
@Test
void contributeWhenTargetClassIsDeprecated() {
RegisteredBean registeredBean = getAndApplyContribution(DeprecatedSample.class);
compileAndCheckWarnings(registeredBean);
}
@Test
void contributeWhenFieldInjectionsUsesADeprecatedType() {
RegisteredBean registeredBean = getAndApplyContribution(
DeprecatedFieldInjectionTypeSample.class);
compileAndCheckWarnings(registeredBean);
}
@Test
void contributeWhenFieldInjectionsUsesADeprecatedTypeWithReflection() {
RegisteredBean registeredBean = getAndApplyContribution(
DeprecatedPrivateFieldInjectionTypeSample.class);
compileAndCheckWarnings(registeredBean);
}
@Test
void contributeWhenFieldInjectionsIsDeprecated() {
RegisteredBean registeredBean = getAndApplyContribution(
DeprecatedFieldInjectionPointSample.class);
compileAndCheckWarnings(registeredBean);
}
@Test
void contributeWhenMethodInjectionsUsesADeprecatedType() {
RegisteredBean registeredBean = getAndApplyContribution(
DeprecatedMethodInjectionTypeSample.class);
compileAndCheckWarnings(registeredBean);
}
@Test
void contributeWhenMethodInjectionsUsesADeprecatedTypeWithReflection() {
RegisteredBean registeredBean = getAndApplyContribution(
DeprecatedPrivateMethodInjectionTypeSample.class);
compileAndCheckWarnings(registeredBean);
}
@Test
void contributeWhenMethodInjectionsIsDeprecated() {
RegisteredBean registeredBean = getAndApplyContribution(
DeprecatedMethodInjectionPointSample.class);
compileAndCheckWarnings(registeredBean);
}
private void compileAndCheckWarnings(RegisteredBean registeredBean) {
assertThatNoException().isThrownBy(() -> compile(TEST_COMPILER, registeredBean,
((instanceSupplier, compiled) -> {})));
}
}
private RegisteredBean getAndApplyContribution(Class<?> beanClass) {
RegisteredBean registeredBean = registerBean(beanClass);
BeanRegistrationAotContribution contribution = this.beanPostProcessor.processAheadOfTime(registeredBean);
@@ -218,11 +291,17 @@ class AutowiredAnnotationBeanRegistrationAotContributionTests {
return compiled.getSourceFileFromPackage(sample.getPackageName());
}
@SuppressWarnings("unchecked")
private void compile(RegisteredBean registeredBean,
BiConsumer<BiFunction<RegisteredBean, Object, Object>, Compiled> result) {
compile(TestCompiler.forSystem(), registeredBean, result);
}
@SuppressWarnings("unchecked")
private void compile(TestCompiler testCompiler, RegisteredBean registeredBean,
BiConsumer<BiFunction<RegisteredBean, Object, Object>, Compiled> result) {
Class<?> target = registeredBean.getBeanClass();
MethodReference methodReference = this.beanRegistrationCode.getInstancePostProcessors().get(0);
CodeWarnings codeWarnings = new CodeWarnings();
this.beanRegistrationCode.getTypeBuilder().set(type -> {
CodeBlock methodInvocation = methodReference.toInvokeCodeBlock(
ArgumentCodeGenerator.of(RegisteredBean.class, "registeredBean").and(target, "instance"),
@@ -235,10 +314,11 @@ class AutowiredAnnotationBeanRegistrationAotContributionTests {
.addParameter(target, "instance").returns(target)
.addStatement("return $L", methodInvocation)
.build());
codeWarnings.detectDeprecation(target);
codeWarnings.suppress(type);
});
this.generationContext.writeGeneratedContent();
TestCompiler.forSystem().with(this.generationContext).compile(compiled ->
testCompiler.with(this.generationContext).printFiles(System.out).compile(compiled ->
result.accept(compiled.getInstance(BiFunction.class), compiled));
}
@@ -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.
@@ -35,6 +35,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* Tests for {@link JakartaAnnotationsRuntimeHints}.
*
* @author Brian Clozel
* @author Sam Brannen
*/
class JakartaAnnotationsRuntimeHintsTests {
@@ -62,4 +63,14 @@ class JakartaAnnotationsRuntimeHintsTests {
assertThat(RuntimeHintsPredicates.reflection().onType(Qualifier.class)).accepts(this.hints);
}
@Test // gh-33345
void javaxInjectAnnotationHasHints() {
assertThat(RuntimeHintsPredicates.reflection().onType(javax.inject.Inject.class)).accepts(this.hints);
}
@Test // gh-33345
void javaxQualifierAnnotationHasHints() {
assertThat(RuntimeHintsPredicates.reflection().onType(javax.inject.Qualifier.class)).accepts(this.hints);
}
}
@@ -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";
}
}
}
@@ -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.
@@ -34,8 +34,10 @@ import org.springframework.beans.testfixture.beans.factory.generator.deprecation
import org.springframework.core.ResolvableType;
import org.springframework.core.test.tools.Compiled;
import org.springframework.core.test.tools.TestCompiler;
import org.springframework.javapoet.FieldSpec;
import org.springframework.javapoet.MethodSpec;
import org.springframework.javapoet.MethodSpec.Builder;
import org.springframework.javapoet.TypeSpec;
import static org.assertj.core.api.Assertions.assertThat;
@@ -59,30 +61,49 @@ class CodeWarningsTests {
}
@Test
void registerNoWarningDoesNotIncludeAnnotation() {
compile(method -> {
void registerNoWarningDoesNotIncludeAnnotationOnMethod() {
compileWithMethod(method -> {
this.codeWarnings.suppress(method);
method.addStatement("$T bean = $S", String.class, "Hello");
}, compiled -> assertThat(compiled.getSourceFile()).doesNotContain("@SuppressWarnings"));
}
@Test
void registerNoWarningDoesNotIncludeAnnotationOnType() {
compile(type -> {
this.codeWarnings.suppress(type);
type.addField(FieldSpec.builder(String.class, "type").build());
}, compiled -> assertThat(compiled.getSourceFile()).doesNotContain("@SuppressWarnings"));
}
@Test
@SuppressWarnings("deprecation")
void registerWarningSuppressesIt() {
void registerWarningSuppressesItOnMethod() {
this.codeWarnings.register("deprecation");
compile(method -> {
compileWithMethod(method -> {
this.codeWarnings.suppress(method);
method.addStatement("$T bean = new $T()", DeprecatedBean.class, DeprecatedBean.class);
}, compiled -> assertThat(compiled.getSourceFile())
.contains("@SuppressWarnings(\"deprecation\")"));
}
@Test
@SuppressWarnings("deprecation")
void registerWarningSuppressesItOnType() {
this.codeWarnings.register("deprecation");
compile(type -> {
this.codeWarnings.suppress(type);
type.addField(FieldSpec.builder(DeprecatedBean.class, "bean").build());
}, compiled -> assertThat(compiled.getSourceFile())
.contains("@SuppressWarnings(\"deprecation\")"));
}
@Test
@SuppressWarnings({ "deprecation", "removal" })
void registerSeveralWarningsSuppressesThem() {
void registerSeveralWarningsSuppressesThemOnMethod() {
this.codeWarnings.register("deprecation");
this.codeWarnings.register("removal");
compile(method -> {
compileWithMethod(method -> {
this.codeWarnings.suppress(method);
method.addStatement("$T bean = new $T()", DeprecatedBean.class, DeprecatedBean.class);
method.addStatement("$T another = new $T()", DeprecatedForRemovalBean.class, DeprecatedForRemovalBean.class);
@@ -90,6 +111,19 @@ class CodeWarningsTests {
.contains("@SuppressWarnings({ \"deprecation\", \"removal\" })"));
}
@Test
@SuppressWarnings({ "deprecation", "removal" })
void registerSeveralWarningsSuppressesThemOnType() {
this.codeWarnings.register("deprecation");
this.codeWarnings.register("removal");
compile(type -> {
this.codeWarnings.suppress(type);
type.addField(FieldSpec.builder(DeprecatedBean.class, "bean").build());
type.addField(FieldSpec.builder(DeprecatedForRemovalBean.class, "another").build());
}, compiled -> assertThat(compiled.getSourceFile())
.contains("@SuppressWarnings({ \"deprecation\", \"removal\" })"));
}
@Test
@SuppressWarnings("deprecation")
void detectDeprecationOnAnnotatedElementWithDeprecated() {
@@ -97,6 +131,13 @@ class CodeWarningsTests {
assertThat(this.codeWarnings.getWarnings()).containsExactly("deprecation");
}
@Test
@SuppressWarnings("deprecation")
void detectDeprecationOnAnnotatedElementWhoseEnclosingElementIsDeprecated() {
this.codeWarnings.detectDeprecation(DeprecatedBean.Nested.class);
assertThat(this.codeWarnings.getWarnings()).containsExactly("deprecation");
}
@Test
@SuppressWarnings("removal")
void detectDeprecationOnAnnotatedElementWithDeprecatedForRemoval() {
@@ -104,6 +145,13 @@ class CodeWarningsTests {
assertThat(this.codeWarnings.getWarnings()).containsExactly("removal");
}
@Test
@SuppressWarnings("removal")
void detectDeprecationOnAnnotatedElementWhoseEnclosingElementIsDeprecatedForRemoval() {
this.codeWarnings.detectDeprecation(DeprecatedForRemovalBean.Nested.class);
assertThat(this.codeWarnings.getWarnings()).containsExactly("removal");
}
@ParameterizedTest
@MethodSource("resolvableTypesWithDeprecated")
void detectDeprecationOnResolvableTypeWithDeprecated(ResolvableType resolvableType) {
@@ -113,11 +161,17 @@ class CodeWarningsTests {
@SuppressWarnings("deprecation")
static Stream<Arguments> resolvableTypesWithDeprecated() {
Class<?> deprecatedBean = DeprecatedBean.class;
Class<?> nested = DeprecatedBean.Nested.class;
return Stream.of(
Arguments.of(ResolvableType.forClass(DeprecatedBean.class)),
Arguments.of(ResolvableType.forClassWithGenerics(GenericBean.class, DeprecatedBean.class)),
Arguments.of(ResolvableType.forClass(deprecatedBean)),
Arguments.of(ResolvableType.forClass(nested)),
Arguments.of(ResolvableType.forClassWithGenerics(GenericBean.class, deprecatedBean)),
Arguments.of(ResolvableType.forClassWithGenerics(GenericBean.class, nested)),
Arguments.of(ResolvableType.forClassWithGenerics(GenericBean.class,
ResolvableType.forClassWithGenerics(GenericBean.class, DeprecatedBean.class)))
ResolvableType.forClassWithGenerics(GenericBean.class, deprecatedBean))),
Arguments.of(ResolvableType.forClassWithGenerics(GenericBean.class,
ResolvableType.forClassWithGenerics(GenericBean.class, nested)))
);
}
@@ -130,11 +184,17 @@ class CodeWarningsTests {
@SuppressWarnings("removal")
static Stream<Arguments> resolvableTypesWithDeprecatedForRemoval() {
Class<?> deprecatedBean = DeprecatedForRemovalBean.class;
Class<?> nested = DeprecatedForRemovalBean.Nested.class;
return Stream.of(
Arguments.of(ResolvableType.forClass(DeprecatedForRemovalBean.class)),
Arguments.of(ResolvableType.forClassWithGenerics(GenericBean.class, DeprecatedForRemovalBean.class)),
Arguments.of(ResolvableType.forClass(deprecatedBean)),
Arguments.of(ResolvableType.forClass(nested)),
Arguments.of(ResolvableType.forClassWithGenerics(GenericBean.class, deprecatedBean)),
Arguments.of(ResolvableType.forClassWithGenerics(GenericBean.class, nested)),
Arguments.of(ResolvableType.forClassWithGenerics(GenericBean.class,
ResolvableType.forClassWithGenerics(GenericBean.class, DeprecatedForRemovalBean.class)))
ResolvableType.forClassWithGenerics(GenericBean.class, deprecatedBean))),
Arguments.of(ResolvableType.forClassWithGenerics(GenericBean.class,
ResolvableType.forClassWithGenerics(GenericBean.class, nested)))
);
}
@@ -145,16 +205,20 @@ class CodeWarningsTests {
assertThat(this.codeWarnings).hasToString("CodeWarnings[deprecation, rawtypes]");
}
private void compile(Consumer<Builder> method, Consumer<Compiled> result) {
DeferredTypeBuilder typeBuilder = new DeferredTypeBuilder();
this.generationContext.getGeneratedClasses().addForFeature("TestCode", typeBuilder);
typeBuilder.set(type -> {
private void compileWithMethod(Consumer<Builder> method, Consumer<Compiled> result) {
compile(type -> {
type.addModifiers(Modifier.PUBLIC);
Builder methodBuilder = MethodSpec.methodBuilder("apply")
.addModifiers(Modifier.PUBLIC);
method.accept(methodBuilder);
type.addMethod(methodBuilder.build());
});
}, result);
}
private void compile(Consumer<TypeSpec.Builder> type, Consumer<Compiled> result) {
DeferredTypeBuilder typeBuilder = new DeferredTypeBuilder();
this.generationContext.getGeneratedClasses().addForFeature("TestCode", typeBuilder);
typeBuilder.set(type);
this.generationContext.writeGeneratedContent();
TEST_COMPILER.with(this.generationContext).compile(result);
}
@@ -648,6 +648,10 @@ class CustomEditorTests {
assertThat(localeEditor.getValue()).isEqualTo(Locale.CANADA);
assertThat(localeEditor.getAsText()).isEqualTo("en_CA");
localeEditor = new LocaleEditor();
localeEditor.setAsText("zh-Hans");
assertThat(localeEditor.getValue()).isEqualTo(Locale.forLanguageTag("zh-Hans"));
localeEditor = new LocaleEditor();
assertThat(localeEditor.getAsText()).isEmpty();
}
@@ -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();
@@ -0,0 +1,74 @@
/*
* 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.beans.testfixture.beans.factory.annotation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
public abstract class DeprecatedInjectionSamples {
@Deprecated
public static class DeprecatedEnvironment {}
@Deprecated
public static class DeprecatedSample {
@Autowired
Environment environment;
}
public static class DeprecatedFieldInjectionPointSample {
@Autowired
@Deprecated
Environment environment;
}
public static class DeprecatedFieldInjectionTypeSample {
@Autowired
DeprecatedEnvironment environment;
}
public static class DeprecatedPrivateFieldInjectionTypeSample {
@Autowired
private DeprecatedEnvironment environment;
}
public static class DeprecatedMethodInjectionPointSample {
@Autowired
@Deprecated
void setEnvironment(Environment environment) {}
}
public static class DeprecatedMethodInjectionTypeSample {
@Autowired
void setEnvironment(DeprecatedEnvironment environment) {}
}
public static class DeprecatedPrivateMethodInjectionTypeSample {
@Autowired
private void setEnvironment(DeprecatedEnvironment environment) {}
}
}
@@ -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.
@@ -23,4 +23,8 @@ package org.springframework.beans.testfixture.beans.factory.generator.deprecatio
*/
@Deprecated
public class DeprecatedBean {
// This isn't flag deprecated on purpose
public static class Nested {}
}
@@ -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.
@@ -23,4 +23,7 @@ package org.springframework.beans.testfixture.beans.factory.generator.deprecatio
*/
@Deprecated(forRemoval = true)
public class DeprecatedForRemovalBean {
// This isn't flag deprecated on purpose
public static class Nested {}
}
@@ -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

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