问题
I'm using spring
with RestTemplate
to send POST
requests to a webserver.
When my application is shut down (eg undeployed from tomcat), the shutdown should be delayed until all pending responses are received (within a timeout).
The restTemplate uses HttpComponentsClientHttpRequestFactory
under the hood.
Question: how can I tell spring
to delay the shutdown? @PreDestroy
could be one possibility, but how can I detect pending requests on the restTemplate?
回答1:
I think there is no out of the box solution as stated in https://github.com/spring-projects/spring-boot/issues/4657
For Tomcat code below should work
@Component
@Scope("singleton")
public class ApplicationContextClosedListener implements ApplicationListener<ContextClosedEvent>, TomcatConnectorCustomizer {
private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationContextClosedListener.class);
private volatile Connector connector;
@Value("${timeout}")
private Integer timeout;
@Override
public void customize(Connector connector) {
this.connector = connector;
}
@Override
public void onApplicationEvent(ContextClosedEvent event) {
if (connector != null) {
shutdownGracefully();
}
}
private void shutdownGracefully() {
connector.pause();
Executor executor = connector.getProtocolHandler().getExecutor();
if (executor instanceof ThreadPoolExecutor) {
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
try {
threadPoolExecutor.shutdown();
if (!threadPoolExecutor.awaitTermination(timeout, TimeUnit.SECONDS)) {
LOGGER.warn("Shutdown: Tomcat thread pool did not shut down gracefully within specified period. Proceeding with forceful shutdown");
}
threadPoolExecutor.shutdownNow();
LOGGER.info("Shutdown: the executor shutdown completed");
} catch (InterruptedException ex) {
LOGGER.error("Shutdown: Interrupt signal received");
threadPoolExecutor.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
}
回答2:
You can execute all the requests with ExecutorService
and then add a @PreDestroy
hook to wait for all tasks to be completed within a given timeout. Your service can be like this
@Slf4j
@Component
public class SenderService {
private static final int TWO_SECONDS = 2;
private RestTemplate restTemplate;
private ExecutorService executorService;
public SenderService() {
this.restTemplate = new RestTemplate();
this.executorService = Executors.newFixedThreadPool(1);
}
public void sendRequest() throws Exception {
executorService.submit(() -> {
ZonedDateTime now = ZonedDateTime.now();
log.info("Sending request at {} ...", now);
restTemplate.getForObject("https://httpbin.org/delay/{delay}", Void.class, TWO_SECONDS, now);
log.info("Response received for request at {}", now);
return null;
}).get();
}
@PreDestroy
public void destroy() throws InterruptedException {
log.info("Shutting down sender service...");
executorService.shutdown();
executorService.awaitTermination(3, TimeUnit.SECONDS);
log.info("Sender service terminated.");
}
}
The simple way to test this is running the application below and terminating it at some point.
@SpringBootApplication
public class Application {
public static void main(final String[] args) throws Exception {
ConfigurableApplicationContext run = SpringApplication.run(Application.class, args);
SenderService senderService = run.getBean(SenderService.class);
while (true) {
senderService.sendRequest();
}
}
}
If you gracefully shut down the application, you'll see that if a request is sent to delay endpoint
, the executorService
is going to wait up to 3 seconds for the task to be completed and then terminate the component. executorService.shutdown()
initiates a shutdown in which previously submitted tasks are executed, but no new tasks will be accepted.
This code is using spring-boot with embedded tomcat, but the same approach could be applied to any spring application context.
来源:https://stackoverflow.com/questions/47570089/how-to-wait-on-resttemplate-responses-on-shutdown