3

I'm writing a Spring-Boot application to monitor a directory and process files that are being added to it. I start a thread by creating a ApplicationRunner in my Application class that calls a method annotated with @Async:

@SpringBootApplication
@EnableAsync
public class Application {

    @Autowired
    private DirectoryMonitorService directoryMonitorService;

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

    @Bean
    public ApplicationRunner startDirectoryMonitorService() {
        return args -> directoryMonitorService.monitorSourceDirectoty();
    }
}

Here is the code for DirectoryMonitorService that has a method annotated with @Async:

@Service
public class DirectoryMonitorService {

    private static final Logger logger = LogManager.getLogger(DirectoryMonitorService.class);

    @Value("${timeout}")
    private long timeout;

    @Autowired
    private WatchService watchService;

    @Async
    public void monitorSourceDirectoty() {
        while (true) {
            WatchKey watchKey;
            try {
                watchKey = watchService.poll(timeout, TimeUnit.SECONDS);
            } catch (ClosedWatchServiceException | InterruptedException e) {
                logger.error("Exception occured while polling from source file", e);
                return;
            }

            // process the WatchEvents

            if (!watchKey.reset()) {
                break;
            }
        }
    }
}

Finally here is where I create the ThreadPoolTaskExecutor:

public class AsyncConfig extends AsyncConfigurerSupport {

    private static final Logger logger = LogManager.getLogger(AsyncConfig.class);

    private static final String THREAD_NAME_PREFIX = "Parser-";

    @Value("${corePoolSize}")
    public int corePoolSize;

    @Value("${maxPoolSize}")
    public int maxPoolSize;

    @Value("${queueCapacity}")
    public int queueCapacity;

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setThreadNamePrefix(THREAD_NAME_PREFIX);
        executor.initialize();

        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (Throwable ex, Method method, Object... params) -> {
            logger.error("Exception message - " + ex.getMessage());
            logger.error("Method name - " + method.getName());
            for (Object param : params) {
                logger.error("Parameter value - " + param);
            }
        };
    }
}

Somehow I feel this is not most elegant way of starting a main thread. Does anybody have a better solution?

Also I would rather have replace while (true) with a Boolean variable that I can set to false when Spring-Boot shuts down. Does anybody know which interface I need to implement for this?

Zhubin Salehi
  • 281
  • 1
  • 5
  • 14

1 Answers1

0

This is correct if you want a very simple implementation and nothing more reliable.

Use @Async to a shorter tasks and it has very limited capability in terms of restarts etc.

And also, @Async will keep creating the separate threads at every watch sequence activation, and it will overwhelm the thread pool and start trowing exceptions, This is quite noticeable, if you have long running task as,

// process the WatchEvents

Other than that your implementation is correct (In my opinion).

Some suggestions (If you want to make things interesting/ complex):

So you can keep track of the files obviously using some sort of persistence mechanism and trigger decoupled batch (can use Spring Batch) to handle the execution and, get those batches into a separate UI or something and there you can have each of these batch process stopped, start, resume on the UI.

diyoda_
  • 5,274
  • 8
  • 57
  • 89