Spring Integration运行时错误:没有可用的请求通道。无法发送请求消息
我在将SpringBoot 3迁移到SpringBoot 4之后遇到了一个问题。尝试通过SFTP发送文件时,使用 MPUT 提示没有通道:
2026-06-16T10:15:10.331217825Z ERROR [] --- [cdb-job-thread3] com.harry.potter.job.MyExportRestrictionsTask: ExportRestrictions failed! Exception: {} org.springframework.messaging.MessagingException: No request channel available. Cannot send request message.
at org.springframework.integration.gateway.MessagingGatewaySupport.sendAndReceive(MessagingGatewaySupport.java:591)
at org.springframework.integration.gateway.MessagingGatewaySupport.sendAndReceive(MessagingGatewaySupport.java:578)
at org.springframework.integration.gateway.GatewayProxyFactoryBean.sendOrSendAndReceive(GatewayProxyFactoryBean.java:653)
at org.springframework.integration.gateway.GatewayProxyFactoryBean.invokeGatewayMethod(GatewayProxyFactoryBean.java:587)
at org.springframework.integration.gateway.GatewayProxyFactoryBean.doInvoke(GatewayProxyFactoryBean.java:553)
at org.springframework.integration.gateway.GatewayProxyFactoryBean.invoke(GatewayProxyFactoryBean.java:544)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:222)
at jdk.proxy2/jdk.proxy2.$Proxy146.mput(Unknown Source)
at com.harry.potter.job.service.JobService.putExportFiles(JobService.java:234)
at com.harry.potter.job.service.JobService.task(JobService.java:84)
at com.harry.potter.job.MyExportRestrictionsTask.runAsScheduledTask(MyExportRestrictionsTask.java:82)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:158)
at net.javacrumbs.shedlock.core.DefaultLockingTaskExecutor.executeWithLock(DefaultLockingTaskExecutor.java:74)
at net.javacrumbs.shedlock.spring.aop.MethodProxyScheduledLockAdvisor$LockingInterceptor.invoke(MethodProxyScheduledLockAdvisor.java:86)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:719)
at com.harry.potter.job.MyExportRestrictionsTask$$SpringCGLIB$$0.runAsScheduledTask(<generated>)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
at org.springframework.scheduling.support.ScheduledMethodRunnable.runInternal(ScheduledMethodRunnable.java:128)
at org.springframework.scheduling.support.ScheduledMethodRunnable.lambda$run$1(ScheduledMethodRunnable.java:122)
at io.micrometer.observation.Observation.observe(Observation.java:569)
at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:122)
at org.springframework.scheduling.config.Task$OutcomeTrackingRunnable.run(Task.java:88)
at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:94)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source)
我的SFTP配置如下:channel用于定义SFTP连接配置,context定义在传输中要使用的文件名和路径名,sessionFactory用于创建传输会话,以及带有 MPUT 命令路由的集成流(第二条路由尚未启用)和消息SFTP命令网关:
@Configuration
public class MySftpConfiguration {
private static final Logger LOG = LoggerFactory.getLogger(MySftpConfiguration.class);
@Bean
public TransferChannel myChannel() {
LOG.debug("myChannel");
TransferChannel channel = new TransferChannel();
channel.setHost(myEnv.getSftpHost());
channel.setPort(myEnv.getSftpPort());
channel.setUser(myEnv.getSftpUser());
channel.setPrivateKey(myEnv.getSftpPrivateKey());
channel.setPassword(myEnv.getSftpPassword());
return channel;
}
@Bean
public TransferContext myContext(TransferChannel myChannel) {
LOG.debug("myContext");
TransferContext context = new TransferContext();
context.setEnabled(myEnv.isEnabled());
context.setChannel(myChannel);
context.setPreserveTimestamp(true);
context.setLocalDir(myEnv.getLocalDir());
context.setLocalFilename(myEnv.getLocalFilename());
context.setRemoteDir(myEnv.getRemoteDir());
return context;
}
@Bean
public SessionFactory<DirEntry> mySessionFactory(TransferChannel myChannel) {
LOG.debug("mySessionFactory");
DefaultSftpSessionFactory sf = new DefaultSftpSessionFactory(false);
sf.setHost(myChannel.getHost());
sf.setPort(myChannel.getPort());
sf.setUser(myChannel.getUser());
if (Channel.getPrivateKey() != null) {
sf.setPrivateKey(myChannel.getPrivateKey());
} else {
sf.setPassword(myChannel.getPassword());
}
sf.setAllowUnknownKeys(true);
return new CachingSessionFactory<DirEntry>(sf);
}
/**
* The Integration Flow "mySftpCmdGate" is the messaging end-point of
* the SFTP command gate "MessagingGateway".
* This command gate was said just by declaring it
* to encapsulate the method which was called into a message header
* named "method".
* With that, "mySftpCmdGate" end-point receives messages of two types:
* 1. message with header["method"] = "getRequest"
* 2. message with header["method"] = "mput"
* These messages are routed with two routings f1,f2 to the appropriate handler,
* two SFTP OutboundGateways which perform the required command
* on the file - given as message payload - on the SFTP side.
*
* @param mySessionFactory the SFTP session factory
* @param myContext the SFTP context
* @return the message end-point as {@link IntegrationFlow}
*/
@Bean
public IntegrationFlow mySftpFlow(SessionFactory<DirEntry> mySessionFactory, TransferContext myContext) {
LOG.debug("mySftpFlow");
return IntegrationFlow.from(mySftpCmdGate.class, g -> g
.header("method", args -> args.method().getName()))
.log()
.route(Message.class, m -> m.getHeaders().get("method", String.class),
r -> r
// .subFlowMapping("getRequest", f1 -> f1
// .handle(Sftp.outboundGateway(mySessionFactory, Command.GET, "payload")
// .localDirectory(new File(myContext.getLocalDir())))
// .transform(new FileToStringTransformer())
// .transform(Transformers.fromJson(myRequest.class))
// )
.subFlowMapping("mput", f2 -> f2
.handle(Sftp.outboundGateway(mySessionFactory, Command.MPUT, "payload")
.autoCreateDirectory(false)
.useTemporaryFileName(false)
.remoteDirectoryExpression(myContext.getRemoteDir()))
))
.get();
}
@MessagingGateway
public interface mySftpCmdGate {
// myRequest getRequest(File remoteFile);
List<String> mput(File localDir);
}
}
出了什么问题?有什么线索吗?
** 更新1 **
为了将SpringBoot-3.5.5迁移到SpringBoot-4.0.6(Spring迁移到Spring-7.0.7;SpringIntegration-6.5.5迁移到SpringIntegration-7.0.4),让我在迁移后的项目无错误编译所需做的事情,是:
- 将import
org.springframework.integration.sftp.gateway.SftpOutboundGateway修改为org.springframework.integration.sftp.outbound.SftpOutboundGateway - 在应用主类中添加 @IntegrationComponentScan("com.harry.potter.job.config")
- 用
@MessagingGateway标注命令网关 - 将使用命令网关的服务类中的构造注入限定为
@Qualified("mySftpConfiguration.MySftpCmdGate") - 在使用命令网关的服务类中的引用从
com.harry.potter.job.config.MySftpCmdGate改为com.harry.potter.job.config.MySftpConfiguration.MySftpCmdGate
在这些改动之后,SFTP传输再也无法正常工作,并显示了上述异常栈。
有什么想法吗?
解决方案
我又让AI将 SpringBoot-3.5.5迁移到SpringBoot-4.0.6。AI比我懒多了,没动SFTP命令网关。当时我进行迁移时,曾困惑JVM不再识别 MySftpCmdGate.class。
原因在于,在SpringBoot 4中,应用需要同时被 @EnableIntegration 和 @IntegrationComponentScan(...) 注解。以前这可能是可选的,只靠找到Spring Integration依赖就能工作。一旦这两个注解就位,项目就能无错误地编译。
所以我把SFTP命令网关改回到原来的状态:
public interface mySftpCmdGate {
List<String> mput(File localDir);
}
回滚后,SFTP传输又能正常工作。
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。