RequestHandlerRetryAdvice нельзя заставить работать с Ftp.outboundGateway в Spring Integration

Моя ситуация аналогична описанной в это ТАК вопрос. Разница в том, что я использую не WebFlux.outboundGateway, а Ftp.outboundGateway, для которого я вызываю команду AbstractRemoteFileOutboundGateway.Command.GET, а общая проблема заключается в том, что я не могу использовать определенную RequestHandlerRetryAdvice.

Конфигурация выглядит так (урезанная до соответствующих частей):

@RestController
@RequestMapping( value = "/somepath" )
public class DownloadController
{
   private DownloadGateway downloadGateway;

   public DownloadController( DownloadGateway downloadGateway )
   {
      this.downloadGateway = downloadGateway;
   }

   @PostMapping( "/downloads" )
   public void download( @RequestParam( "filename" ) String filename )
   {
      Map<String, Object> headers = new HashMap<>();

      downloadGateway.triggerDownload( filename, headers );
   }
}    
@MessagingGateway
public interface DownloadGateway
{
   @Gateway( requestChannel = "downloadFiles.input" )
   void triggerDownload( Object value, Map<String, Object> headers );
}
@Configuration
@EnableIntegration
public class FtpDefinition
{
   private FtpProperties ftpProperties;

   public FtpDefinition( FtpProperties ftpProperties )
   {
      this.ftpProperties = ftpProperties;
   }

   @Bean
   public DirectChannel gatewayDownloadsOutputChannel()
   {
      return new DirectChannel();
   }

   @Bean
   public IntegrationFlow downloadFiles( RemoteFileOutboundGatewaySpec<FTPFile, FtpOutboundGatewaySpec> getRemoteFile )
   {
      return f -> f.handle( getRemoteFile, getRetryAdvice() )
                   .channel( "gatewayDownloadsOutputChannel" );
   }

   private Consumer<GenericEndpointSpec<AbstractRemoteFileOutboundGateway<FTPFile>>> getRetryAdvice()
   {
      return e -> e.advice( ( (Supplier<RequestHandlerRetryAdvice>) () -> {
         RequestHandlerRetryAdvice advice = new RequestHandlerRetryAdvice();
         advice.setRetryTemplate( getRetryTemplate() );
         return advice;
      } ).get() );
   }

   private RetryTemplate getRetryTemplate()
   {
      RetryTemplate result = new RetryTemplate();

      FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
      backOffPolicy.setBackOffPeriod( 5000 );

      result.setBackOffPolicy( backOffPolicy );
      return result;
   }

   @Bean
   public RemoteFileOutboundGatewaySpec<FTPFile, FtpOutboundGatewaySpec> getRemoteFile( SessionFactory sessionFactory )
   {
      return 
         Ftp.outboundGateway( sessionFactory,
                              AbstractRemoteFileOutboundGateway.Command.GET,
                              "payload" )
            .fileExistsMode( FileExistsMode.REPLACE )
            .localDirectoryExpression( "'" + ftpProperties.getLocalDir() + "'" )
            .autoCreateLocalDirectory( true );
   }

   @Bean
   public SessionFactory<FTPFile> ftpSessionFactory()
   {
      DefaultFtpSessionFactory sessionFactory = new DefaultFtpSessionFactory();
      sessionFactory.setHost( ftpProperties.getServers().get( 0 ).getHost() );
      sessionFactory.setPort( ftpProperties.getServers().get( 0 ).getPort() );
      sessionFactory.setUsername( ftpProperties.getServers().get( 0 ).getUser() );
      sessionFactory.setPassword( ftpProperties.getServers().get( 0 ).getPassword() );
      return sessionFactory;
   }
}
@SpringBootApplication
@EnableIntegration
@IntegrationComponentScan
public class FtpTestApplication {

    public static void main(String[] args) {
        SpringApplication.run( FtpTestApplication.class, args );
    }
}
@Configuration
@PropertySource( "classpath:ftp.properties" )
@ConfigurationProperties( prefix = "ftp" )
@Data
public class FtpProperties
{
   @NotNull
   private String localDir;

   @NotNull
   private List<Server> servers;

   @Data
   public static class Server
   {
      @NotNull
      private String host;

      @NotNull
      private int port;

      @NotNull
      private String user;

      @NotNull
      private String password;
   }
}

Контроллер в основном предназначен только для целей тестирования, в реальной реализации есть опросчик. Мой FtpProperties содержит список серверов, потому что в реальной реализации я использую DelegatingSessionFactory для выбора экземпляра на основе некоторых параметров.

Согласно Гэри Расселу comment, я ожидаю, что неудачная загрузка будет повторена. Но если я прерываю загрузку на стороне сервера (путем «Kick user» в экземпляре FileZilla), я просто получаю немедленную трассировку стека и никаких повторных попыток:

org.apache.commons.net.ftp.FTPConnectionClosedException: FTP response 421 received.  Server closed connection.
[...]

Мне также нужно загрузить файлы, для чего я использую расширение Ftp.outboundAdapter. В этом случае и с тем же RetryTemplate, если я прерываю загрузку на стороне сервера, Spring Integration выполняет еще две попытки с задержкой в ​​5 секунд каждая, и только затем регистрирует java.net.SocketException: Connection reset, все как положено.

Я попытался немного отладить и заметил, что прямо перед первой попыткой загрузки через Ftp.outboundAdapter срабатывает точка останова на RequestHandlerRetryAdvice.doInvoke(). Но при загрузке через Ftp.outboundGateway эта точка останова никогда не срабатывает.

Есть ли проблема с моей конфигурацией, может ли кто-нибудь заставить RequestHandlerRetryAdvice работать с Ftp.outboundGateway/AbstractRemoteFileOutboundGateway.Command.GET?


person Hein Blöd    schedule 25.09.2018    source источник


Ответы (1)


Извините за задержку; мы находимся на платформе SpringOne на этой неделе.

Проблема связана с тем, что спецификация шлюза является bean-компонентом - шлюз в конечном итоге инициализируется до применения рекомендации.

Я изменил ваш код следующим образом...

@Bean
public IntegrationFlow downloadFiles(SessionFactory<FTPFile> sessionFactory) {
    return f -> f.handle(getRemoteFile(sessionFactory), getRetryAdvice())
            .channel("gatewayDownloadsOutputChannel");
}

...

private RemoteFileOutboundGatewaySpec<FTPFile, FtpOutboundGatewaySpec> getRemoteFile(SessionFactory<FTPFile> sessionFactory) {
    return Ftp.outboundGateway(sessionFactory,
            AbstractRemoteFileOutboundGateway.Command.GET,
            "payload")
            .fileExistsMode(FileExistsMode.REPLACE)
            .localDirectoryExpression("'/tmp'")
            .autoCreateLocalDirectory(true);
}

... и это сработало.

Как правило, лучше не иметь дело со спецификациями напрямую, а просто встроить их в определение потока...

@Bean
public IntegrationFlow downloadFiles(SessionFactory<FTPFile> sessionFactory) {
    return f -> f.handle(Ftp.outboundGateway(sessionFactory,
            AbstractRemoteFileOutboundGateway.Command.GET,
            "payload")
            .fileExistsMode(FileExistsMode.REPLACE)
            .localDirectoryExpression("'/tmp'")
            .autoCreateLocalDirectory(true), getRetryAdvice())
        .channel("gatewayDownloadsOutputChannel");
}
person Gary Russell    schedule 26.09.2018
comment
Фантастика, вы спасли мой день, огромное спасибо! Будучи новичком в Spring в целом и Spring Integration в частности, я бы никогда не нашел эту причину и решение. - person Hein Blöd; 27.09.2018