From 4861392714de8d52abb585cc2c118f8fdec103ac Mon Sep 17 00:00:00 2001 From: Glenn Renfro Date: Fri, 27 Jun 2025 16:05:32 -0400 Subject: [PATCH 1/2] Apply jspecify nullify to spring-integration-core aop/annotation packages --- .../integration/annotation/package-info.java | 1 + .../aop/CompoundTriggerAdvice.java | 4 ++- .../aop/MessagePublishingInterceptor.java | 25 +++++++++++-------- ...thodAnnotationPublisherMetadataSource.java | 6 ++--- ...hodNameMappingPublisherMetadataSource.java | 8 +++--- .../aop/PublisherAnnotationAdvisor.java | 8 +++--- .../PublisherAnnotationBeanPostProcessor.java | 16 ++++++++++-- .../aop/PublisherMetadataSource.java | 8 +++--- .../SimpleActiveIdleReceiveMessageAdvice.java | 4 ++- .../aop/SimplePublisherMetadataSource.java | 14 ++++++----- .../integration/aop/package-info.java | 1 + ...ConfigurableCompositeMessageConverter.java | 1 + 12 files changed, 63 insertions(+), 33 deletions(-) diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/package-info.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/package-info.java index 7fcadb2b4f5..95336d04d75 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/package-info.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/package-info.java @@ -1,4 +1,5 @@ /** * Provides annotations for annotation-based configuration. */ +@org.jspecify.annotations.NullMarked package org.springframework.integration.annotation; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aop/CompoundTriggerAdvice.java b/spring-integration-core/src/main/java/org/springframework/integration/aop/CompoundTriggerAdvice.java index 01e6602d4f8..c309f4ae149 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/aop/CompoundTriggerAdvice.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/aop/CompoundTriggerAdvice.java @@ -16,6 +16,8 @@ package org.springframework.integration.aop; +import org.jspecify.annotations.Nullable; + import org.springframework.integration.core.MessageSource; import org.springframework.integration.util.CompoundTrigger; import org.springframework.messaging.Message; @@ -56,7 +58,7 @@ public CompoundTriggerAdvice(CompoundTrigger compoundTrigger, Trigger overrideTr * @return the message or null */ @Override - public Message afterReceive(Message result, MessageSource source) { + public @Nullable Message afterReceive(@Nullable Message result, MessageSource source) { if (result == null) { this.compoundTrigger.setOverride(this.override); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aop/MessagePublishingInterceptor.java b/spring-integration-core/src/main/java/org/springframework/integration/aop/MessagePublishingInterceptor.java index 9964ab20f7b..44001dd2438 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/aop/MessagePublishingInterceptor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/aop/MessagePublishingInterceptor.java @@ -19,9 +19,11 @@ import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; +import org.jspecify.annotations.Nullable; import org.springframework.aop.support.AopUtils; import org.springframework.beans.BeansException; @@ -64,12 +66,15 @@ public class MessagePublishingInterceptor implements MethodInterceptor, BeanFact private final PublisherMetadataSource metadataSource; + @Nullable private DestinationResolver channelResolver; + @SuppressWarnings("NullAway.Init") private BeanFactory beanFactory; private MessageBuilderFactory messageBuilderFactory = new DefaultMessageBuilderFactory(); + @Nullable private String defaultChannelName; private volatile boolean messageBuilderFactorySet; @@ -85,7 +90,7 @@ public MessagePublishingInterceptor(PublisherMetadataSource metadataSource) { * @param defaultChannelName the default channel name. * @since 4.0.3 */ - public void setDefaultChannelName(String defaultChannelName) { + public void setDefaultChannelName(@Nullable String defaultChannelName) { this.defaultChannelName = defaultChannelName; } @@ -100,24 +105,22 @@ public void setBeanFactory(BeanFactory beanFactory) throws BeansException { protected MessageBuilderFactory getMessageBuilderFactory() { if (!this.messageBuilderFactorySet) { - if (this.beanFactory != null) { - this.messageBuilderFactory = IntegrationUtils.getMessageBuilderFactory(this.beanFactory); - } + this.messageBuilderFactory = IntegrationUtils.getMessageBuilderFactory(this.beanFactory); this.messageBuilderFactorySet = true; } return this.messageBuilderFactory; } @Override - public final Object invoke(MethodInvocation invocation) throws Throwable { + public final @Nullable Object invoke(MethodInvocation invocation) throws Throwable { initMessagingTemplateIfAny(); StandardEvaluationContext context = ExpressionUtils.createStandardEvaluationContext(this.beanFactory); - Class targetClass = AopUtils.getTargetClass(invocation.getThis()); + Class targetClass = AopUtils.getTargetClass(Objects.requireNonNull(invocation.getThis())); Method method = AopUtils.getMostSpecificMethod(invocation.getMethod(), targetClass); - String[] argumentNames = resolveArgumentNames(method); + @Nullable String[] argumentNames = resolveArgumentNames(method); context.setVariable(PublisherMetadataSource.METHOD_NAME_VARIABLE_NAME, method.getName()); - if (invocation.getArguments().length > 0 && argumentNames != null) { - Map argumentMap = new HashMap<>(); + if (invocation.getArguments().length > 0 && argumentNames != null) { + Map<@Nullable Object, @Nullable Object> argumentMap = new HashMap<>(); for (int i = 0; i < argumentNames.length; i++) { if (invocation.getArguments().length <= i) { break; @@ -152,7 +155,7 @@ private void initMessagingTemplateIfAny() { } } - private String[] resolveArgumentNames(Method method) { + private @Nullable String @Nullable [] resolveArgumentNames(Method method) { return this.parameterNameDiscoverer.getParameterNames(method); } @@ -187,7 +190,7 @@ private void publishMessage(Method method, StandardEvaluationContext context) { } } - private Map evaluateHeaders(Method method, StandardEvaluationContext context) { + private @Nullable Map evaluateHeaders(Method method, StandardEvaluationContext context) { Map headerExpressionMap = this.metadataSource.getExpressionsForHeaders(method); if (headerExpressionMap != null) { return ExpressionEvalMap.from(headerExpressionMap) diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aop/MethodAnnotationPublisherMetadataSource.java b/spring-integration-core/src/main/java/org/springframework/integration/aop/MethodAnnotationPublisherMetadataSource.java index a10614c4dc8..771eaa453e6 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/aop/MethodAnnotationPublisherMetadataSource.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/aop/MethodAnnotationPublisherMetadataSource.java @@ -80,7 +80,7 @@ public void setChannelAttributeName(String channelAttributeName) { } @Override - public String getChannelName(Method method) { + public @Nullable String getChannelName(Method method) { return this.channels.computeIfAbsent(method, method1 -> { String channelName = getAnnotationValue(method, this.channelAttributeName); @@ -92,7 +92,7 @@ public String getChannelName(Method method) { } @Override - public Expression getExpressionForPayload(Method method) { + public @Nullable Expression getExpressionForPayload(Method method) { return this.payloadExpressions.computeIfAbsent(method, method1 -> { Expression payloadExpression = null; @@ -141,7 +141,7 @@ public Map getExpressionsForHeaders(Method method) { return this.headersExpressions.computeIfAbsent(method, method1 -> { Map headerExpressions = new HashMap<>(); - String[] parameterNames = this.parameterNameDiscoverer.getParameterNames(method); + @Nullable String[] parameterNames = this.parameterNameDiscoverer.getParameterNames(method); Annotation[][] annotationArray = method.getParameterAnnotations(); for (int i = 0; i < annotationArray.length; i++) { Annotation[] parameterAnnotations = annotationArray[i]; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aop/MethodNameMappingPublisherMetadataSource.java b/spring-integration-core/src/main/java/org/springframework/integration/aop/MethodNameMappingPublisherMetadataSource.java index 0f84e2424bc..3ffe91970e3 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/aop/MethodNameMappingPublisherMetadataSource.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/aop/MethodNameMappingPublisherMetadataSource.java @@ -21,6 +21,8 @@ import java.util.Map; import java.util.stream.Collectors; +import org.jspecify.annotations.Nullable; + import org.springframework.expression.Expression; import org.springframework.util.Assert; import org.springframework.util.PatternMatchUtils; @@ -66,7 +68,7 @@ public void setChannelMap(Map channelMap) { } @Override - public Expression getExpressionForPayload(Method method) { + public @Nullable Expression getExpressionForPayload(Method method) { for (Map.Entry entry : this.payloadExpressionMap.entrySet()) { if (PatternMatchUtils.simpleMatch(entry.getKey(), method.getName())) { return entry.getValue(); @@ -76,7 +78,7 @@ public Expression getExpressionForPayload(Method method) { } @Override - public Map getExpressionsForHeaders(Method method) { + public @Nullable Map getExpressionsForHeaders(Method method) { return this.headerExpressionMap .entrySet() .stream() @@ -87,7 +89,7 @@ public Map getExpressionsForHeaders(Method method) { } @Override - public String getChannelName(Method method) { + public @Nullable String getChannelName(Method method) { for (Map.Entry entry : this.channelMap.entrySet()) { if (PatternMatchUtils.simpleMatch(entry.getKey(), method.getName())) { return entry.getValue(); diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aop/PublisherAnnotationAdvisor.java b/spring-integration-core/src/main/java/org/springframework/integration/aop/PublisherAnnotationAdvisor.java index ffedaaa5558..387c87ce85a 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/aop/PublisherAnnotationAdvisor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/aop/PublisherAnnotationAdvisor.java @@ -18,9 +18,11 @@ import java.lang.annotation.Annotation; import java.util.Arrays; +import java.util.Objects; import java.util.stream.Collectors; import org.aopalliance.aop.Advice; +import org.jspecify.annotations.Nullable; import org.springframework.aop.ClassFilter; import org.springframework.aop.MethodMatcher; @@ -73,7 +75,7 @@ public PublisherAnnotationAdvisor(Class... publisherAnnota * @param defaultChannelName the default channel name. * @since 4.0.3 */ - public void setDefaultChannelName(String defaultChannelName) { + public void setDefaultChannelName(@Nullable String defaultChannelName) { this.interceptor.setDefaultChannelName(defaultChannelName); } @@ -104,7 +106,7 @@ private static Pointcut buildPointcut(Class[] publisherAnn result.union(cpc).union(mpc); } } - return result; + return Objects.requireNonNull(result); } private static final class MetaAnnotationMatchingPointcut implements Pointcut { @@ -133,7 +135,7 @@ private static final class MetaAnnotationMatchingPointcut implements Pointcut { * (can be null) */ MetaAnnotationMatchingPointcut( - Class classAnnotationType, Class methodAnnotationType) { + @Nullable Class classAnnotationType, @Nullable Class methodAnnotationType) { Assert.isTrue((classAnnotationType != null || methodAnnotationType != null), "Either Class annotation type or Method annotation type needs to be specified (or both)"); diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aop/PublisherAnnotationBeanPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/aop/PublisherAnnotationBeanPostProcessor.java index 63829b70c3c..81b071fc1c1 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/aop/PublisherAnnotationBeanPostProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/aop/PublisherAnnotationBeanPostProcessor.java @@ -16,19 +16,22 @@ package org.springframework.integration.aop; +import org.jspecify.annotations.Nullable; + import org.springframework.aop.framework.autoproxy.AbstractBeanFactoryAwareAdvisingPostProcessor; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.beans.factory.SmartInitializingSingleton; +import org.springframework.util.Assert; /** * Post-processes beans that contain the * method-level @{@link org.springframework.integration.annotation.Publisher} annotation. *

* Only one bean instance of this processor can be declared in the application context, manual - * or automatic by thr framework via annotation or XML processing. + * or automatic by the framework via annotation or XML processing. * * @author Oleg Zhurakousky * @author Mark Fisher @@ -42,10 +45,18 @@ public class PublisherAnnotationBeanPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor implements BeanNameAware, SmartInitializingSingleton { - private String defaultChannelName; + /** + * This value is optional and may be {@code null}, indicating that no default channel + * is configured and message routing must be handled explicitly elsewhere. For example: + * {@link MessagePublishingInterceptor} can accept a null value for this attribute. + * + */ + private @Nullable String defaultChannelName; + @SuppressWarnings("NullAway.Init") private String beanName; + @SuppressWarnings("NullAway.Init") private BeanFactory beanFactory; /** @@ -60,6 +71,7 @@ public void setDefaultChannelName(String defaultChannelName) { @Override public void setBeanName(String name) { + Assert.notNull(name, "'name' must not be null"); this.beanName = name; } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aop/PublisherMetadataSource.java b/spring-integration-core/src/main/java/org/springframework/integration/aop/PublisherMetadataSource.java index 87657ea0e4e..6d633416a09 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/aop/PublisherMetadataSource.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/aop/PublisherMetadataSource.java @@ -19,6 +19,8 @@ import java.lang.reflect.Method; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; @@ -55,7 +57,7 @@ interface PublisherMetadataSource { * @param method The Method. * @return The channel name. */ - String getChannelName(Method method); + @Nullable String getChannelName(Method method); /** * Returns the SpEL expression to be evaluated for creating the Message @@ -64,7 +66,7 @@ interface PublisherMetadataSource { * @return rhe payload expression. * @since 5.0.4 */ - Expression getExpressionForPayload(Method method); + @Nullable Expression getExpressionForPayload(Method method); /** * Returns the map of expression strings to be evaluated for any headers @@ -73,6 +75,6 @@ interface PublisherMetadataSource { * @param method The Method. * @return The header expressions. */ - Map getExpressionsForHeaders(Method method); + @Nullable Map getExpressionsForHeaders(Method method); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aop/SimpleActiveIdleReceiveMessageAdvice.java b/spring-integration-core/src/main/java/org/springframework/integration/aop/SimpleActiveIdleReceiveMessageAdvice.java index a8b1879e499..d2ee1810f31 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/aop/SimpleActiveIdleReceiveMessageAdvice.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/aop/SimpleActiveIdleReceiveMessageAdvice.java @@ -18,6 +18,8 @@ import java.time.Duration; +import org.jspecify.annotations.Nullable; + import org.springframework.integration.util.DynamicPeriodicTrigger; import org.springframework.messaging.Message; import org.springframework.util.Assert; @@ -67,7 +69,7 @@ public void setActivePollPeriod(long activePollPeriod) { } @Override - public Message afterReceive(Message result, Object source) { + public @Nullable Message afterReceive(@Nullable Message result, Object source) { if (result == null) { this.trigger.setDuration(this.idlePollPeriod); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aop/SimplePublisherMetadataSource.java b/spring-integration-core/src/main/java/org/springframework/integration/aop/SimplePublisherMetadataSource.java index 8680388fc5a..27469a0ac5c 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/aop/SimplePublisherMetadataSource.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/aop/SimplePublisherMetadataSource.java @@ -20,6 +20,8 @@ import java.util.Map; import java.util.stream.Collectors; +import org.jspecify.annotations.Nullable; + import org.springframework.expression.Expression; /** @@ -34,18 +36,18 @@ */ public class SimplePublisherMetadataSource implements PublisherMetadataSource { - private volatile String channelName; + private @Nullable volatile String channelName; - private volatile Expression payloadExpression; + private @Nullable volatile Expression payloadExpression; - private volatile Map headerExpressions; + private @Nullable volatile Map headerExpressions; public void setChannelName(String channelName) { this.channelName = channelName; } @Override - public String getChannelName(Method method) { + public @Nullable String getChannelName(Method method) { return this.channelName; } @@ -54,7 +56,7 @@ public void setPayloadExpression(String payloadExpression) { } @Override - public Expression getExpressionForPayload(Method method) { + public @Nullable Expression getExpressionForPayload(Method method) { return this.payloadExpression; } @@ -67,7 +69,7 @@ public void setHeaderExpressions(Map headerExpressions) { } @Override - public Map getExpressionsForHeaders(Method method) { + public @Nullable Map getExpressionsForHeaders(Method method) { return this.headerExpressions; } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aop/package-info.java b/spring-integration-core/src/main/java/org/springframework/integration/aop/package-info.java index 094047f5c18..eb76d2c918e 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/aop/package-info.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/aop/package-info.java @@ -1,4 +1,5 @@ /** * Provides classes to support message publication using AOP. */ +@org.jspecify.annotations.NullMarked package org.springframework.integration.aop; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/support/converter/ConfigurableCompositeMessageConverter.java b/spring-integration-core/src/main/java/org/springframework/integration/support/converter/ConfigurableCompositeMessageConverter.java index eefe1254f30..5e7d96a6c7f 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/support/converter/ConfigurableCompositeMessageConverter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/support/converter/ConfigurableCompositeMessageConverter.java @@ -59,6 +59,7 @@ public class ConfigurableCompositeMessageConverter extends CompositeMessageConve private final boolean registerDefaults; + @SuppressWarnings("NullAway.Init") private BeanFactory beanFactory; /** From 8d64bab35b490a61fc763931bc425f8f8caa41ac Mon Sep 17 00:00:00 2001 From: Glenn Renfro Date: Fri, 27 Jun 2025 17:48:06 -0400 Subject: [PATCH 2/2] Remove voloatile from the attributes of SimplePublisherMetadataSource.java Thanks for the review! --- .../integration/aop/SimplePublisherMetadataSource.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aop/SimplePublisherMetadataSource.java b/spring-integration-core/src/main/java/org/springframework/integration/aop/SimplePublisherMetadataSource.java index 27469a0ac5c..0670cf543d3 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/aop/SimplePublisherMetadataSource.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/aop/SimplePublisherMetadataSource.java @@ -36,11 +36,11 @@ */ public class SimplePublisherMetadataSource implements PublisherMetadataSource { - private @Nullable volatile String channelName; + private @Nullable String channelName; - private @Nullable volatile Expression payloadExpression; + private @Nullable Expression payloadExpression; - private @Nullable volatile Map headerExpressions; + private @Nullable Map headerExpressions; public void setChannelName(String channelName) { this.channelName = channelName;