Skip to content
Advertisement

Prototyped bean not triggering Scheduled method in Spring Boot

I’m using Java 17, spring-boot 2.7.3 and spring 5.3.22 dependencies.

I have prototyped beans as follows:

@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class InsertTransactionDetailsByScheduleAPIInteractor
        implements InsertTransactionDetailsByScheduleAPIInputBoundary {

    private final InsertTransactionsInputBoundary insertTransactionsInputBoundary;
    private final RetrieveFileFromSecureFileTransferProtocolInputBoundary retrieveFileFromSecureFileTransferProtocolInputBoundary;

    public InsertTransactionDetailsByScheduleAPIInteractor(
            final InsertTransactionsInputBoundary insertTransactionsInputBoundaryParam,
            @Qualifier(Constants.PROTOTYPE_RETRIEVE_FILE_BEAN_QUALIFIER) final RetrieveFileFromSecureFileTransferProtocolInputBoundary retrieveFileFromSecureFileTransferProtocolInputBoundaryParam) {
        super();
        this.insertTransactionsInputBoundary = insertTransactionsInputBoundaryParam;
        this.retrieveFileFromSecureFileTransferProtocolInputBoundary = retrieveFileFromSecureFileTransferProtocolInputBoundaryParam;
    }

    /**
     * {@inheritDoc}
     */
    @Override
//  @Scheduled(cron = "* 30 1 * * *", zone = "America/Sao_Paulo")
    @Scheduled(initialDelay = 5, fixedRate = 1, timeUnit = TimeUnit.SECONDS)
    public void insertTransactionsBySchedule() throws Exception {
        this.insertTransactionsInputBoundary.insertTransactions(LocalDate.now(Constants.DEFAULT_ZONE_ID),
                this.retrieveFileFromSecureFileTransferProtocolInputBoundary);
    }
}
@Configuration
@RefreshScope
class FTPConfiguration {

    private final ConsulProperties consulProperties;

    public FTPConfiguration(final ConsulProperties consulPropertiesParam) {
        super();
        this.consulProperties = consulPropertiesParam;
    }

    @Bean
    @RequestScope
    @Primary
    RetrieveFileFromSecureFileTransferProtocolInputBoundary createRequestScopedRetrieveFileFromSecureFileTransferProtocolBean()
            throws Exception {
        return this.createRetrieveFileFromSecureFileTransferProtocolBean(true);
    }

    @Bean
    @RequestScope
    @Primary
    ChannelSftp createRequestScopedSecureFileTransferProtocolChannel() throws JSchException {
        return this.createSFTPChannel(true);
    }

    @Bean(destroyMethod = Constants.FTP_SESSION_BEAN_DESTROY_METHOD)
    @RequestScope
    @Primary
    Session createRequestScopeSecureFileTransferProtocolSession() throws JSchException {
        return this.createSFTPSession();
    }

    @Bean(Constants.PROTOTYPE_RETRIEVE_FILE_BEAN_QUALIFIER)
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    RetrieveFileFromSecureFileTransferProtocolInputBoundary createPrototypeScopedRetrieveFileFromSecureFileTransferProtocolBean()
            throws Exception {
        return this.createRetrieveFileFromSecureFileTransferProtocolBean(false);
    }

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    ChannelSftp createPrototypeScopedSecureFileTransferProtocolChannel() throws JSchException {
        return this.createSFTPChannel(false);
    }

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    Session createPrototypeScopedSecureFileTransferProtocolSession() throws JSchException {
        return this.createSFTPSession();
    }

    private RetrieveFileFromSecureFileTransferProtocolInputBoundary createRetrieveFileFromSecureFileTransferProtocolBean(
            final boolean isRequestScope) throws Exception {
        final var channelSFTP = isRequestScope ? this.createRequestScopedSecureFileTransferProtocolChannel()
                : this.createPrototypeScopedSecureFileTransferProtocolChannel();
        return new RetrieveFileFromSecureFileTransferProtocolInteractor(channelSFTP,
                new ListDirFilesFromSecureFileTransferProtocolInteractor(channelSFTP));
    }

    private ChannelSftp createSFTPChannel(final boolean isRequestScope) throws JSchException {
        final var channel = (isRequestScope ? this.createRequestScopeSecureFileTransferProtocolSession()
                : this.createPrototypeScopedSecureFileTransferProtocolSession()).openChannel("sftp");
        channel.connect(this.consulProperties.getChannelTimeout());
        return (ChannelSftp) channel;
    }

    private Session createSFTPSession() throws JSchException {
        final var session = new JSch().getSession(this.consulProperties.getUsername(), this.consulProperties.getHost(),
                this.consulProperties.getPort());
        session.setConfig("StrictHostKeyChecking", "no");
        session.setPassword(this.consulProperties.getFtpPassword());
        session.connect(this.consulProperties.getSessionTimeout());
        return session;
    }
}

My application class:

@SpringBootApplication
@EnableCaching
@EnableScheduling
public class Application implements Closeable {

    private static ConfigurableApplicationContext run;

    public static void main(final String[] args) {
        Application.run = SpringApplication.run(Application.class, args);
    }

    @Override
    public void close() {
        Application.run.close();
    }
}

I annotated the InsertTransactionDetailsByScheduleAPIInteractor also as prototype in order to have a new instance of the inner beans per schedule execution, but somehow the @Scheduled method only runs when I have a singleton InsertTransactionDetailsByScheduleAPIInteractor bean, which in my use case I can’t have, otherwise I wouldn’t close FTP connection. I know before Spring 4.3, @Scheduled methods only works with Singleton beans, but as mentioned before I’m using Spring version 5.3.22.

Advertisement

Answer

You are using Spring but that doesn’t mean everything has to be managed by Spring. There is nothing wrong with opening an SFTP Session inside your class when you need it and close it afterwards. You can probably even use a try-with-resources as I expect the Session to be a AutoClosable which can be used.

So in short manually create the objects you need inside your scheduled job and cleanup after the job finished. This way your scheduler can be a regular singleton and properly cleanup after itself.

User contributions licensed under: CC BY-SA
9 People found this is helpful
Advertisement