Issue
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.
Solution
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.
Answered By - M. Deinum
Answer Checked By - Mary Flores (JavaFixing Volunteer)