As reported in gh-34651, `DataBuffer#getByte` can be inefficient for
some implementations, as bound checks are performed for each call.
This commit introduces a new `forEachByte` method that helps with
traversing operations without paying the bound check cost for each byte.
Closes gh-35623
Prior to this commit, our @Retryable support as well as a RetryPolicy
created by the RetryPolicy.Builder only matched against top-level
exceptions when filtering included/excluded exceptions thrown by a
@Retryable method or Retryable operation.
With this commit, we now match against not only top-level exceptions
but also nested causes within those top-level exceptions. This is
achieved via the new ExceptionTypeFilter.match(Throwable, boolean)
support.
See gh-35592
Closes gh-35583
Prior to this commit, ExceptionTypeFilter only provided support for
filtering based on exact matches against exception types; however, some
use cases require that filtering be applied to nested causes in a given
exception. For example, this functionality is a prerequisite for
gh-35583.
This commit introduces a new match(Throwable, boolean) method in
ExceptionTypeFilter, where the boolean flag enables matching against
nested exceptions.
See gh-35583
Closes gh-35592
This commit apply several refinements to PropagationContextElement:
- Capture the ThreadLocal when instantiating the
PropagationContextElement in order to support dispatchers switching
threads
- Remove the constructor parameter which is not idiomatic and breaks
the support when switching threads, and use instead the
updateThreadContext(context: CoroutineContext) parameter
- Make the kotlinx-coroutines-reactor dependency optional
- Make the properties private
The Javadoc and tests are also updated to use the
`Dispatchers.IO + PropagationContextElement()` pattern performed
outside of the suspending lambda, which is the typical use case.
Closes gh-35469
Prior to this commit, annotations were not found on parameters in an
overridden method unless the method was public. Specifically, the
search algorithm in AnnotatedMethod did not consider a protected or
package-private method in a superclass to be a potential override
candidate. This affects parameter annotation searches in
spring-messaging, spring-webmvc, spring-webflux, and any other
components that use or extend AnnotatedMethod.
To address that, this commit revises the search algorithm in
AnnotatedMethod to consider all non-final declared methods as potential
override candidates, thereby aligning with the search logic in
AnnotationsScanner for the MergedAnnotations API.
Closes gh-35349
Prior to this commit, the Micrometer context-propagation project would
help propagating information from `ThreadLocal`, Reactor `Context` and
other context objects. This is already well supported for Micrometer
Observations.
In the case of Kotlin suspending functions, the processing of tasks
would not necessarily update the `ThreadLocal` when the function is
scheduled on a different thread.
This commit introduces the `PropagationContextElement` operator that
connects the `ThreadLocal`, Reactor `Context` and Coroutine `Context`
for all libraries using the "context-propagation" project.
Applications must manually use this operator in suspending functions
like so:
```
suspend fun suspendingFunction() {
return withContext(PropagationContextElement(currentCoroutineContext())) {
logger.info("Suspending function with traceId")
}
}
```
Closes gh-35185
Prior to this commit, a RetryException thrown for an
InterruptedException returned the wrong value from getRetryCount().
Specifically, the count was one more than it should have been, since the
suppressed exception list contains the initial exception as well as all
retry attempt exceptions.
To address that, this commit introduces an internal
RetryInterruptedException which accounts for this off-by-one error.
Closes gh-35434
In RetryTemplate, if we encounter an InterruptedException while
sleeping for the configured back-off duration, we throw a
RetryException with the InterruptedException as the cause.
However, prior to this commit, that RetryException propagated to the
caller without notifying the registered RetryListener.
To address that, this commit introduces a new
onRetryPolicyInterruption() callback in RetryListener as a companion to
the existing onRetryPolicyExhaustion() callback.
Closes gh-35442
Prior to this commit, gh-32097 added native support for Jetty for both
client and server integrations. The `JettyDataBufferFactory` was
promoted as a first class citizen, extracted from a private class in the
client support. To accomodate with server-side requirements, an extra
`buffer.retain()` call was performed.
While this is useful for server-side support, this introduced a bug in
the data buffer factory, as wrapping an existing chunk means that this
chunk is already retained.
This commit fixes the buffer factory implementation and moved existing
tests from mocks to actual pooled buffer implementations from Jetty.
The extra `buffer.retain()` is now done from the server support, right
before wrapping the buffer.
Fixes gh-35319
As of gh-33616, Spring now supports metadata reading with the ClassFile
API on JDK 24+ runtimes. This commit fixes a bug where
`ArrayStoreException` were thrown when reading annotation attribute
values for arrays.
Fixes gh-35252
Prior to this commit, the MergedAnnotations support (specifically
AnnotationsScanner) and AnnotatedMethod did not find annotations on
overridden methods in type hierarchies with unresolved generics.
The reason for this is that ResolvableType.resolve() returns null for
such an unresolved type, which prevents the search algorithms from
considering such methods as override candidates.
For example, given the following type hierarchy, the compiler does not
generate a method corresponding to processOneAndTwo(Long, String) for
GenericInterfaceImpl. Nonetheless, one would expect an invocation of
processOneAndTwo(Long, String) to be @Transactional since it is
effectively an invocation of processOneAndTwo(Long, C) in
GenericAbstractSuperclass, which overrides/implements
processOneAndTwo(A, B) in GenericInterface, which is annotated with
@Transactional.
However, the MergedAnnotations infrastructure currently does not
determine that processOneAndTwo(Long, C) is @Transactional since it is
not able to determine that processOneAndTwo(Long, C) overrides
processOneAndTwo(A, B) because of the unresolved generic C.
interface GenericInterface<A, B> {
@Transactional
void processOneAndTwo(A value1, B value2);
}
abstract class GenericAbstractSuperclass<C> implements GenericInterface<Long, C> {
@Override
public void processOneAndTwo(Long value1, C value2) {
}
}
static GenericInterfaceImpl extends GenericAbstractSuperclass<String> {
}
To address such issues, this commit changes the logic in
AnnotationsScanner.hasSameGenericTypeParameters() and
AnnotatedMethod.isOverrideFor() so that they use
ResolvableType.toClass() instead of ResolvableType.resolve(). The
former returns Object.class for an unresolved generic which in turn
allows the search algorithms to properly detect method overrides in
such type hierarchies.
Closes gh-35342
Prior to this commit, RetryTemplate supplied the wrong exception to
RetryListener.onRetryPolicyExhaustion(). Specifically, the execute()
method in RetryTemplate supplied the final, composite RetryException to
onRetryPolicyExhaustion() instead of the last exception thrown by the
Retryable operation.
This commit fixes that bug by ensuring that the last exception thrown by
the Retryable operation is supplied to onRetryPolicyExhaustion().
Closes gh-35334
While assessing #35195, I noticed the following issues with our
Checkstyle configuration regarding nullability annotations.
- "^(?!org\.jspecify|\.annotations).*(NonNull|Nullable)$" contains a "|".
- "^(?!org\.jspecify|\.annotations).*(NonNull|Nullable)$" matches against
NonNull but not against Nonnull, and therefore incorrectly permits
usage of javax.annotation.Nonnull.
- Some of the Checkstyle suppressions no longer apply.
This commit addresses all of the above issues and updates several tests
to use example annotations other than javax.annotation.Nonnull where
feasible.
See gh-35195
Closes gh-35205
If an application depends on automatic type conversion from
java.time.Instant to java.sql.Timestamp, the ObjectToObjectConverter
performs the conversion based on convention, by using reflection to
invoke Timestamp.from(Instant).
However, when running in a native image a user needs to explicitly
register runtime hints for that particular use of reflection.
To assist users who are running their applications in a native image,
this commit automatically registers the necessary runtime hints for
Timestamp.from(Instant) so that users do not have to.
See gh-35175
Closes gh-35156
In order to avoid unnecessary use of reflection and to simplify native
image deployments, this commit introduces explicit support for
automatic conversions from java.util.Date to java.time.Instant and vice
versa.
To achieve that, this commit introduces an InstantToDateConverter and a
DateToInstantConverter and registers them automatically in
DefaultConversionService.
See gh-35156
Closes gh-35175
Prior to this commit, ExceptionTypeFilter only supported matching
against an exception type. However, most use cases involve matching
against an exception instance. Moreover, every use case within the core
Spring Framework uses ExceptionTypeFilter to match against concrete
exception instances.
This commit therefore introduces an overloaded match(Throwable) method
in ExceptionTypeFilter in order to provide support for the most common
use cases.
See gh-35109
Closes gh-35160
Prior to this commit, the constructors for InstanceFilter and
ExceptionTypeFilter required one to supply the matchIfEmpty flag.
However, users will typically want that to be true. Moreover, we always
supply true for the matchIfEmpty flag within the Spring Framework.
This commit therefore makes the matchIfEmpty flag optional by
introducing overloaded constructors for InstanceFilter and
ExceptionTypeFilter that only accept the includes and excludes
collections.
In addition, this commit overhauls the Javadoc for InstanceFilter and
ExceptionTypeFilter, fixing several issues in the documentation.
Furthermore, this commit applies consistent @Nullable declarations
in ExceptionTypeFilter.
Closes gh-35158
When the core retry functionality was introduced, it had a built-in
MaxRetryDurationPolicy. In #35058, that was migrated to a
withMaxDuration() factory method, and in #35110 that was renamed to
withMaxElapsedTime() (with a corresponding maxElapsedTime() method on
the builder) in order to align with the maxElapsedTime feature of
ExponentialBackOff. The latter also changed the semantics of the
feature in the context of the RetryPolicy.
However, @Retryable does not provide maxElapsedTime support.
In addition, the maxElapsedTime feature is a bit misleading, since it
does not actually track CPU time or wall-clock time but rather only the
sum of individual, accumulated back-off intervals/delays, which is
likely not very useful. Furthermore, the maxElapsedTime will never
apply to a zero-valued delay/interval.
In light of the above, this commit removes the maxElapsedTime support
from the built-in RetryPolicy.
Users can still implement a custom BackOff strategy if they find they
need some form of "max elapsed time" or "max duration".
See gh-34716
See gh-35058
See gh-34529
See gh-35110
Closes gh-35144
Update `SpringFactoriesLoader` so that the cache stores only the
factories and not the complete loader.
Prior to this commit, if a cache entry was added with the thread
context classloader, the loader instance would be added and the
classloader stored. If the thread context classloader subsequently
changes, and a call is made to `forDefaultResourceLocation` with
`null` for the classloader, the cached entry would be used which
contains the older classloader.
Closes gh-34732
After experimenting with our newly introduced core retry support
(RetryPolicy, RetryTemplate, etc.) and @Retryable support, it
became apparent that there are overlapping concerns between the current
RetryPolicy and BackOff contracts.
- RetryPolicy and BackOff both have stateful executions: RetryExecution
and BackOffExecution. However, only one stateful execution is
necessary.
- FixedBackOff and ExponentialBackOff already incorporate "retry" logic
in terms of max attempts, max elapsed time, etc. Thus, there is no
need to duplicate such behavior in a RetryPolicy and its
RetryExecution.
- RetryTemplate currently accepts both a RetryPolicy and a BackOff in
order to instrument the retry algorithm. However, users would
probably rather focus on configuring all "retry" logic via a single
mechanism.
In light of the above, this commit directly incorporates BackOff
in RetryPolicy as follows.
- Remove the RetryExecution interface and move its shouldRetry() method
to RetryPolicy, replacing the current RetryExecution start() method.
- Introduce a default getBackOff() method in the RetryPolicy interface.
- Introduce RetryPolicy.withDefaults() factory method.
- Completely overhaul the RetryPolicy.Builder to provide support for
configuring a BackOff strategy.
- Remove BackOff configuration from RetryTemplate.
- Revise the method signatures of callbacks in RetryListener.
The collective result of these changes can be witnessed in the
reworked implementation of AbstractRetryInterceptor.
RetryPolicy retryPolicy = RetryPolicy.builder()
.includes(spec.includes())
.excludes(spec.excludes())
.predicate(spec.predicate().forMethod(method))
.maxAttempts(spec.maxAttempts())
.delay(Duration.ofMillis(spec.delay()))
.maxDelay(Duration.ofMillis(spec.maxDelay()))
.jitter(Duration.ofMillis(spec.jitter()))
.multiplier(spec.multiplier())
.build();
RetryTemplate retryTemplate = new RetryTemplate(retryPolicy);
See gh-34716
See gh-34529
See gh-35058
Closes gh-35110
gh-33616 refactored `CachingMetadataReaderFactory` and broke the
behavior as it bypassed the cache for `getMetadataReader(String
className)` operations.
This commit restores the original behavior.
Fixes gh-35112