Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
338 views
in Technique[技术] by (71.8m points)

spring - How to call a method after a SpringBoot application has destroyed the ApplicationContext

I have a SpringBoot application that uses a Hikari jdbc connection pool to access a file based H2 database.

application.yml

spring:
  datasource:
    hikari:
      driver-class-name: org.h2.Driver
      jdbc-url: jdbc:h2:${media.db.file};DB_CLOSE_ON_EXIT=FALSE
      username:
      password:
      maximum-pool-size: 5
      connection-test-query: "SELECT 1"
      pool-name: media-pool

Now, I have a requirement that after application shutdown I need to create a local backup of the h2 database file.

Obviously, this will only work after the db connection has been closed and since I have various beans that inject a DataSource, I assume that the db connection will only be closed after those beans have been destroyed (and obviously the class creating the copy cannot be a spring bean).

So my issue is how to detect when all beans have been destroyed and the application context has been destroyed.

Obviously annotation a method with @PreDestroy will not work as such methods are called before beans are destroyed.

I have found various ways to register for application life cycle events, however, if I call BackupUtility.backupDatabase() from any of them I always get the same exception:

java.io.IOException: The process cannot access the file because another process has locked a portion of the file
    at java.base/java.io.FileInputStream.readBytes(Native Method)
    at java.base/java.io.FileInputStream.read(FileInputStream.java:257)
    at ch.bee.mediathek.business.utility.BackupUtility.backupDatabase(BackupUtility.java:63)

I assume that this means that the db connection has not yet been closed and I therefore tried to create the backup too early.

I have tried the following ways to be notified of my application shutdown:

  public static void main(String[] args) {
    ConfigurableApplicationContext context = SpringApplication.run(MediaSpringApplication.class, args);
    context.registerShutdownHook();
    log.info("Application started...");
    context.addApplicationListener(MediaSpringApplication::onApplicationEvent);
  }

  private static void onApplicationEvent(final ApplicationEvent event) {
    if (event instanceof ContextClosedEvent) {
      log.info("===== ContextClosed (1) =====");
      BackupUtility.backupDatabase();
    }
  }

  @Bean
  ServletListenerRegistrationBean<ServletContextListener> myServletListener() {
    ServletListenerRegistrationBean<ServletContextListener> srb = new ServletListenerRegistrationBean<>();
    srb.setListener(new MyServletContextListener());
    return srb;
  }

  public class MyServletContextListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
      log.info("===== ContextDestroyed (2) =====");
      BackupUtility.backupDatabase();
    }
  }

  @EventListener({ContextClosedEvent.class})
  public void contextClosed() {
    log.info("===== ContextClosed (3) =====");
    BackupUtility.backupDatabase();
  }

Looking at the log output I see the following:

[2021-01-06 19:38:12.023] [Thread-8] [INFO ] c.b.m.a.MediaSpringApplication::contextClosed(144) -    ===== ContextClosed (3) =====
[2021-01-06 19:38:12.030] [Thread-8] [ERROR] c.b.m.b.u.BackupUtility::backupDatabase(67) -    Error while copying file contents
java.io.IOException: Der Prozess kann nicht auf die Datei zugreifen, da ein anderer Prozess einen Teil der Datei gesperrt hat
    at java.base/java.io.FileInputStream.readBytes(Native Method)
    at java.base/java.io.FileInputStream.read(FileInputStream.java:257)
    at ch.bee.mediathek.business.utility.BackupUtility.backupDatabase(BackupUtility.java:63)
    at ch.bee.mediathek.app.MediaSpringApplication.contextClosed(MediaSpringApplication.java:145)
[2021-01-06 19:38:12.030] [Thread-8] [INFO ] c.b.m.a.MediaSpringApplication::onApplicationEvent(56) -    ===== ContextClosed (1) =====
[2021-01-06 19:38:12.031] [Thread-8] [ERROR] c.b.m.b.u.BackupUtility::backupDatabase(67) -    Error while copying file contents
java.io.IOException: Der Prozess kann nicht auf die Datei zugreifen, da ein anderer Prozess einen Teil der Datei gesperrt hat
    at java.base/java.io.FileInputStream.readBytes(Native Method)
    at java.base/java.io.FileInputStream.read(FileInputStream.java:257)
    at ch.bee.mediathek.business.utility.BackupUtility.backupDatabase(BackupUtility.java:63)
    at ch.bee.mediathek.app.MediaSpringApplication.onApplicationEvent(MediaSpringApplication.java:57)
[2021-01-06 19:38:12.138] [Thread-8] [INFO ] c.b.m.a.MediaSpringApplication::contextDestroyed(137) -    ===== ContextDestroyed (2) =====
[2021-01-06 19:38:12.140] [Thread-8] [ERROR] c.b.m.b.u.BackupUtility::backupDatabase(67) -    Error while copying file contents
java.io.IOException: Der Prozess kann nicht auf die Datei zugreifen, da ein anderer Prozess einen Teil der Datei gesperrt hat
    at java.base/java.io.FileInputStream.readBytes(Native Method)
    at java.base/java.io.FileInputStream.read(FileInputStream.java:257)
    at ch.bee.mediathek.business.utility.BackupUtility.backupDatabase(BackupUtility.java:63)
    at ch.bee.mediathek.app.MediaSpringApplication$MyServletContextListener.contextDestroyed(MediaSpringApplication.java:138)
[2021-01-06 19:38:12.393] [Thread-8] [WARN ] o.s.b.f.s.DisposableBeanAdapter::destroy(267) -    Invocation of destroy method failed on bean with name 'inMemoryDatabaseShutdownExecutor': org.h2.jdbc.JdbcSQLNonTransientConnectionException: Database is already closed (to disable automatic closing at VM shutdown, add ";DB_CLOSE_ON_EXIT=FALSE" to the db URL) [90121-200]
[2021-01-06 19:38:12.395] [Thread-8] [INFO ] c.z.h.HikariDataSource::close(350) -    media-pool - Shutdown initiated...
[2021-01-06 19:38:12.398] [Thread-8] [INFO ] c.z.h.HikariDataSource::close(352) -    media-pool - Shutdown completed.

Looking at the last four lines of my logs, I see that when I try to backup the database file after the last of my callbacks (the one triggered by the ServletContextListener), the file is apparently still open. However, on the next line I see that the DisposableBeanAdapter tries to do something which fails because the database has already been closed. The next two lines show that the HikariDataSource class only shuts down even later.

So, it seems I need to find a way to start my Backup after the HikariDataSource has shutdown the media pool. I would have expected that this would happen when those beans are destroyed and that my context destruction callback would only be called after that has happened, but it does not seem to work that way.

I have tried to remove the DB_CLOSE_ON_EXIT=FALSE from spring.datasource.hikari.jdbc-url in my application.yml file but that still fails to creat the backup but in addition also adds the following exception at the end of shutting down multiple times (probably once per data pool member):

[2021-01-06 20:04:30.466] [Thread-9] [WARN ] c.z.h.p.PoolBase::isConnectionAlive(184) -    media-pool - Failed to validate connection conn1: url=jdbc:h2:/workspaces/media-spring/database/Media-local user= (Database is already closed (to disable automatic closing at VM shutdown, add ";DB_CLOSE_ON_EXIT=FALSE" to the db URL) [90121-200]). Possibly consider using a shorter maxLifetime value.

Am I doing something wrong? Or is there another way to be notified even later that I haven't found yet?

question from:https://stackoverflow.com/questions/65602139/how-to-call-a-method-after-a-springboot-application-has-destroyed-the-applicatio

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)
Waitting for answers

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...