Compare commits

...

5 Commits

Author SHA1 Message Date
Dariusz Jędrzejczyk 2cf6cbb6f0
Merge 3782e7719a into 7e6874ad80 2025-10-07 23:10:35 +03:00
Sam Brannen 7e6874ad80 Polish @⁠Autowired section of the reference manual
Build and Deploy Snapshot / Build and Deploy Snapshot (push) Waiting to run Details
Build and Deploy Snapshot / Verify (push) Blocked by required conditions Details
Deploy Docs / Dispatch docs deployment (push) Waiting to run Details
2025-10-07 17:17:27 +02:00
Sam Brannen 097463e3b7 Remove outdated reference to JSR 305 in the reference documentation
Closes gh-35580
2025-10-07 17:10:40 +02:00
Dariusz Jędrzejczyk 3782e7719a
Adapt to reactor-core map operator nullability change
Signed-off-by: Dariusz Jędrzejczyk <dariusz.jedrzejczyk@broadcom.com>
2025-09-25 14:49:42 +02:00
Dariusz Jędrzejczyk a84b311370
Adapt to Reactor 3.8 JSpecify annotations
Signed-off-by: Dariusz Jędrzejczyk <dariusz.jedrzejczyk@broadcom.com>
2025-09-17 12:27:32 +02:00
16 changed files with 65 additions and 58 deletions

View File

@ -37,18 +37,18 @@ Kotlin::
----
======
[NOTE]
[TIP]
====
As of Spring Framework 4.3, an `@Autowired` annotation on such a constructor is no longer
necessary if the target bean defines only one constructor to begin with. However, if
several constructors are available and there is no primary/default constructor, at least
one of the constructors must be annotated with `@Autowired` in order to instruct the
container which one to use. See the discussion on
xref:core/beans/annotation-config/autowired.adoc#beans-autowired-annotation-constructor-resolution[constructor resolution] for details.
An `@Autowired` annotation on such a constructor is not necessary if the target bean
defines only one constructor. However, if several constructors are available and there is
no primary or default constructor, at least one of the constructors must be annotated
with `@Autowired` in order to instruct the container which one to use. See the discussion
on xref:core/beans/annotation-config/autowired.adoc#beans-autowired-annotation-constructor-resolution[constructor resolution]
for details.
====
You can also apply the `@Autowired` annotation to _traditional_ setter methods,
as the following example shows:
You can apply the `@Autowired` annotation to _traditional_ setter methods, as the
following example shows:
[tabs]
======
@ -84,8 +84,8 @@ Kotlin::
----
======
You can also apply the annotation to methods with arbitrary names and multiple
arguments, as the following example shows:
You can apply `@Autowired` to methods with arbitrary names and multiple arguments, as the
following example shows:
[tabs]
======
@ -176,14 +176,15 @@ Kotlin::
====
Make sure that your target components (for example, `MovieCatalog` or `CustomerPreferenceDao`)
are consistently declared by the type that you use for your `@Autowired`-annotated
injection points. Otherwise, injection may fail due to a "no type match found" error at runtime.
injection points. Otherwise, injection may fail due to a "no type match found" error at
runtime.
For XML-defined beans or component classes found via classpath scanning, the container
usually knows the concrete type up front. However, for `@Bean` factory methods, you need
to make sure that the declared return type is sufficiently expressive. For components
that implement several interfaces or for components potentially referred to by their
implementation type, consider declaring the most specific return type on your factory
method (at least as specific as required by the injection points referring to your bean).
implementation type, declare the most specific return type on your factory method (at
least as specific as required by the injection points referring to your bean).
====
.[[beans-autowired-annotation-self-injection]]Self Injection
@ -312,8 +313,8 @@ through `@Order` values in combination with `@Primary` on a single bean for each
====
Even typed `Map` instances can be autowired as long as the expected key type is `String`.
The map values contain all beans of the expected type, and the keys contain the
corresponding bean names, as the following example shows:
The map values are all beans of the expected type, and the keys are the corresponding
bean names, as the following example shows:
[tabs]
======
@ -431,7 +432,7 @@ annotated constructor does not have to be public.
====
Alternatively, you can express the non-required nature of a particular dependency
through Java 8's `java.util.Optional`, as the following example shows:
through Java's `java.util.Optional`, as the following example shows:
[source,java,indent=0,subs="verbatim,quotes"]
----
@ -445,8 +446,8 @@ through Java 8's `java.util.Optional`, as the following example shows:
----
You can also use a parameter-level `@Nullable` annotation (of any kind in any package --
for example, `javax.annotation.Nullable` from JSR-305) or just leverage Kotlin built-in
null-safety support:
for example, `org.jspecify.annotations.Nullable` from JSpecify) or just leverage Kotlin's
built-in null-safety support:
[tabs]
======
@ -477,13 +478,6 @@ Kotlin::
----
======
[NOTE]
====
A type-level `@Nullable` annotation such as from JSpecify is not supported in Spring
Framework 6.2 yet. You need to upgrade to Spring Framework 7.0 where the framework
detects type-level annotations and consistently declares JSpecify in its own codebase.
====
You can also use `@Autowired` for interfaces that are well-known resolvable
dependencies: `BeanFactory`, `ApplicationContext`, `Environment`, `ResourceLoader`,
`ApplicationEventPublisher`, and `MessageSource`. These interfaces and their extended
@ -528,5 +522,6 @@ class MovieRecommender {
The `@Autowired`, `@Inject`, `@Value`, and `@Resource` annotations are handled by Spring
`BeanPostProcessor` implementations. This means that you cannot apply these annotations
within your own `BeanPostProcessor` or `BeanFactoryPostProcessor` types (if any).
These types must be 'wired up' explicitly by using XML or a Spring `@Bean` method.
====

View File

@ -10,7 +10,7 @@ dependencies {
api(platform("com.fasterxml.jackson:jackson-bom:2.20.0"))
api(platform("io.micrometer:micrometer-bom:1.16.0-M3"))
api(platform("io.netty:netty-bom:4.2.6.Final"))
api(platform("io.projectreactor:reactor-bom:2025.0.0-M7"))
api(platform("io.projectreactor:reactor-bom:2025.0.0-SNAPSHOT"))
api(platform("io.rsocket:rsocket-bom:1.1.5"))
api(platform("org.apache.groovy:groovy-bom:5.0.1"))
api(platform("org.apache.logging.log4j:log4j-bom:2.25.1"))

View File

@ -23,6 +23,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
@ -1170,7 +1171,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
if (invokeFailure.get()) {
return Mono.error(ex);
}
return (Mono) invokeOperation(invoker);
return (Mono) Objects.requireNonNull(invokeOperation(invoker));
}
catch (RuntimeException exception) {
return Mono.error(exception);
@ -1201,8 +1202,8 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
}
if (adapter.isMultiValue()) {
return adapter.fromPublisher(Flux.from(Mono.fromFuture(cachedFuture))
.switchIfEmpty(Flux.defer(() -> (Flux) evaluate(null, invoker, method, contexts)))
.flatMap(v -> evaluate(valueToFlux(v, contexts), invoker, method, contexts))
.switchIfEmpty(Flux.defer(() -> (Flux) Objects.requireNonNull(evaluate(null, invoker, method, contexts))))
.flatMap(v -> Objects.requireNonNull(evaluate(valueToFlux(v, contexts), invoker, method, contexts)))
.onErrorResume(RuntimeException.class, ex -> {
try {
getErrorHandler().handleCacheGetError((RuntimeException) ex, cache, key);
@ -1216,8 +1217,11 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
}
else {
return adapter.fromPublisher(Mono.fromFuture(cachedFuture)
.switchIfEmpty(Mono.defer(() -> (Mono) evaluate(null, invoker, method, contexts)))
.flatMap(v -> evaluate(Mono.justOrEmpty(unwrapCacheValue(v)), invoker, method, contexts))
// TODO: requireNonNull is not for
// free, perhaps we should
// suppress it
.switchIfEmpty(Mono.defer(() -> (Mono) Objects.requireNonNull(evaluate(null, invoker, method, contexts))))
.flatMap(v -> Objects.requireNonNull(evaluate(Mono.justOrEmpty(unwrapCacheValue(v)), invoker, method, contexts)))
.onErrorResume(RuntimeException.class, ex -> {
try {
getErrorHandler().handleCacheGetError((RuntimeException) ex, cache, key);

View File

@ -17,6 +17,7 @@
package org.springframework.core.codec;
import java.util.Map;
import java.util.Objects;
import org.jspecify.annotations.Nullable;
import org.reactivestreams.Publisher;
@ -84,15 +85,16 @@ public abstract class AbstractDataBufferDecoder<T> extends AbstractDecoder<T> {
public Flux<T> decode(Publisher<DataBuffer> input, ResolvableType elementType,
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
return Flux.from(input).map(buffer -> decodeDataBuffer(buffer, elementType, mimeType, hints));
return Flux.from(input).map(buffer ->
Objects.requireNonNull(decodeDataBuffer(buffer, elementType, mimeType, hints)));
}
@Override
public Mono<T> decodeToMono(Publisher<DataBuffer> input, ResolvableType elementType,
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
return DataBufferUtils.join(input, this.maxInMemorySize)
.map(buffer -> decodeDataBuffer(buffer, elementType, mimeType, hints));
return DataBufferUtils.join(input, this.maxInMemorySize).map(buffer ->
Objects.requireNonNull(decodeDataBuffer(buffer, elementType, mimeType, hints)));
}
/**

View File

@ -20,6 +20,7 @@ import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import org.apache.commons.logging.Log;
@ -227,7 +228,7 @@ public class PayloadMethodArgumentResolver implements HandlerMethodArgumentResol
if (adapter != null && adapter.isMultiValue()) {
Flux<?> flux = content
.filter(this::nonEmptyDataBuffer)
.map(buffer -> decoder.decode(buffer, elementType, mimeType, hints))
.map(buffer -> Objects.requireNonNull(decoder.decode(buffer, elementType, mimeType, hints)))
.onErrorMap(ex -> handleReadError(parameter, message, ex));
if (isContentRequired) {
flux = flux.switchIfEmpty(Flux.error(() -> handleMissingBody(parameter, message)));
@ -241,7 +242,7 @@ public class PayloadMethodArgumentResolver implements HandlerMethodArgumentResol
// Single-value (with or without reactive type wrapper)
Mono<?> mono = content.next()
.filter(this::nonEmptyDataBuffer)
.map(buffer -> decoder.decode(buffer, elementType, mimeType, hints))
.map(buffer -> Objects.requireNonNull(decoder.decode(buffer, elementType, mimeType, hints)))
.onErrorMap(ex -> handleReadError(parameter, message, ex));
if (isContentRequired) {
mono = mono.switchIfEmpty(Mono.error(() -> handleMissingBody(parameter, message)));

View File

@ -18,6 +18,7 @@ package org.springframework.messaging.rsocket;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import io.rsocket.Payload;
@ -291,7 +292,7 @@ final class DefaultRSocketRequester implements RSocketRequester {
Decoder<?> decoder = strategies.decoder(elementType, dataMimeType);
return (Mono<T>) payloadMono.map(this::retainDataAndReleasePayload)
.map(dataBuffer -> decoder.decode(dataBuffer, elementType, dataMimeType, EMPTY_HINTS));
.map(dataBuffer -> Objects.requireNonNull(decoder.decode(dataBuffer, elementType, dataMimeType, EMPTY_HINTS)));
}
@Override
@ -317,7 +318,7 @@ final class DefaultRSocketRequester implements RSocketRequester {
Decoder<?> decoder = strategies.decoder(elementType, dataMimeType);
return payloadFlux.map(this::retainDataAndReleasePayload).map(dataBuffer ->
(T) decoder.decode(dataBuffer, elementType, dataMimeType, EMPTY_HINTS));
(T) Objects.requireNonNull(decoder.decode(dataBuffer, elementType, dataMimeType, EMPTY_HINTS)));
}
private Mono<Payload> getPayloadMono() {

View File

@ -20,6 +20,7 @@ import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import io.r2dbc.spi.Connection;
@ -176,7 +177,7 @@ public class SingleConnectionFactory extends DelegatingConnectionFactory
this.connection =
(isSuppressClose() ? getCloseSuppressingConnectionProxy(connectionToUse) : connectionToUse);
}
return this.connection;
return Objects.requireNonNull(this.connection);
}).flatMap(this::prepareConnection);
}

View File

@ -17,6 +17,7 @@
package org.springframework.transaction.interceptor;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
@ -903,7 +904,7 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init
createTransactionIfNecessary(rtm, txAttr, joinpointIdentification),
tx -> {
try {
return (Mono<?>) invocation.proceedWithInvocation();
return (Mono<?>) Objects.requireNonNull(invocation.proceedWithInvocation());
}
catch (Throwable ex) {
return Mono.error(ex);

View File

@ -301,7 +301,7 @@ abstract class AbstractCoroutinesTransactionAspectTests {
private fun checkReactiveTransaction(expected: Boolean) {
Mono.deferContextual{context -> Mono.just(context)}
.handle { context: ContextView, sink: SynchronousSink<Any?> ->
.handle { context: ContextView, sink: SynchronousSink<ContextView> ->
if (context.hasKey(TransactionContext::class.java) != expected) {
Fail.fail<Any>("Should have thrown NoTransactionException")
}

View File

@ -165,8 +165,9 @@ public class ReactorClientHttpConnector implements ClientHttpConnector, SmartLif
return requestSender
.send((request, outbound) -> requestCallback.apply(adaptRequest(method, uri, request, outbound)))
.responseConnection((response, connection) -> {
responseRef.set(new ReactorClientHttpResponse(response, connection));
return Mono.just((ClientHttpResponse) responseRef.get());
ReactorClientHttpResponse clientResponse = new ReactorClientHttpResponse(response, connection);
responseRef.set(clientResponse);
return Mono.just((ClientHttpResponse) clientResponse);
})
.next()
.doOnCancel(() -> {

View File

@ -66,10 +66,10 @@ class KotlinSerializationCborEncoderTests : AbstractEncoderTests<KotlinSerializa
pojo3
)
val pojoBytes = Cbor.Default.encodeToByteArray(arrayOf(pojo1, pojo2, pojo3))
testEncode(input, Pojo::class.java) { step: FirstStep<DataBuffer?> ->
testEncode(input, Pojo::class.java) { step: FirstStep<DataBuffer> ->
step
.consumeNextWith(expectBytes(pojoBytes)
.andThen { dataBuffer: DataBuffer? -> DataBufferUtils.release(dataBuffer) })
.andThen { dataBuffer: DataBuffer -> DataBufferUtils.release(dataBuffer) })
.verifyComplete()
}
}
@ -78,7 +78,7 @@ class KotlinSerializationCborEncoderTests : AbstractEncoderTests<KotlinSerializa
fun encodeMono() {
val pojo = Pojo("foo", "bar")
val input = Mono.just(pojo)
testEncode(input, Pojo::class.java) { step: FirstStep<DataBuffer?> ->
testEncode(input, Pojo::class.java) { step: FirstStep<DataBuffer> ->
step
.consumeNextWith(expectBytes(Cbor.Default.encodeToByteArray(pojo))
.andThen { dataBuffer: DataBuffer? -> DataBufferUtils.release(dataBuffer) })

View File

@ -45,9 +45,9 @@ class CustomKotlinSerializationJsonEncoderTests :
@Test
override fun encode() {
val input = Mono.just(BigDecimal(1))
testEncode(input, BigDecimal::class.java) { step: StepVerifier.FirstStep<DataBuffer?> ->
testEncode(input, BigDecimal::class.java) { step: StepVerifier.FirstStep<DataBuffer> ->
step.consumeNextWith(expectString("1.0")
.andThen { dataBuffer: DataBuffer? -> DataBufferUtils.release(dataBuffer) })
.andThen { dataBuffer: DataBuffer -> DataBufferUtils.release(dataBuffer) })
.verifyComplete()
}
}

View File

@ -76,10 +76,10 @@ class KotlinSerializationProtobufEncoderTests : AbstractEncoderTests<KotlinSeria
pojo3
)
val pojoBytes = ProtoBuf.Default.encodeToByteArray(arrayOf(pojo1, pojo2, pojo3))
testEncode(input, Pojo::class.java) { step: FirstStep<DataBuffer?> ->
testEncode(input, Pojo::class.java) { step: FirstStep<DataBuffer> ->
step
.consumeNextWith(expectBytes(pojoBytes)
.andThen { dataBuffer: DataBuffer? -> DataBufferUtils.release(dataBuffer) })
.andThen { dataBuffer: DataBuffer -> DataBufferUtils.release(dataBuffer) })
.verifyComplete()
}
}
@ -88,10 +88,10 @@ class KotlinSerializationProtobufEncoderTests : AbstractEncoderTests<KotlinSeria
fun encodeMono() {
val pojo = Pojo("foo", "bar")
val input = Mono.just(pojo)
testEncode(input, Pojo::class.java) { step: FirstStep<DataBuffer?> ->
testEncode(input, Pojo::class.java) { step: FirstStep<DataBuffer> ->
step
.consumeNextWith(expectBytes(ProtoBuf.Default.encodeToByteArray(pojo))
.andThen { dataBuffer: DataBuffer? -> DataBufferUtils.release(dataBuffer) })
.andThen { dataBuffer: DataBuffer -> DataBufferUtils.release(dataBuffer) })
.verifyComplete()
}
}

View File

@ -53,11 +53,10 @@ public class SessionAttributeMethodArgumentResolver extends AbstractNamedValueAr
return new NamedValueInfo(ann.name(), ann.required(), ValueConstants.DEFAULT_NONE);
}
@SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/1290
@Override
protected Mono<Object> resolveName(String name, MethodParameter parameter, ServerWebExchange exchange) {
return exchange.getSession()
.filter(session -> session.getAttribute(name) != null)
.map(session -> session.getAttribute(name));
return exchange.getSession().mapNotNull(session -> session.getAttribute(name));
}
@Override

View File

@ -739,7 +739,8 @@ class CoRouterFunctionDsl internal constructor (private val init: (CoRouterFunct
}
@PublishedApi
internal fun <T> asMono(request: ServerRequest, context: CoroutineContext = Dispatchers.Unconfined, handler: suspend (ServerRequest) -> T): Mono<T> {
internal fun <T : Any> asMono(request: ServerRequest, context: CoroutineContext =
Dispatchers.Unconfined, handler: suspend (ServerRequest) -> T?): Mono<T> {
return mono(context) {
contextProvider?.let {
withContext(it.invoke(request)) {

View File

@ -381,7 +381,8 @@ class InvocableHandlerMethodKotlinTests {
StepVerifier.create(mono)
.consumeNextWith {
if (it.returnValue is Mono<*>) {
StepVerifier.create(it.returnValue as Mono<*>).expectNext(expected).verifyComplete()
StepVerifier.create(it.returnValue as Mono<*>).expectNext(requireNotNull(expected))
.verifyComplete()
} else {
assertThat(it.returnValue).isEqualTo(expected)
}