[英]Spring boot with @RefreshScope @PostConstruct @PreDestroy
[英]Deadlock in Spring Boot Application's @PostConstruct Method
我正在使用Spring TaskScheduler在應用程序啟動時安排任務(顯然......)。
TaskScheduler是在我的SpringConfig中創建的:
@Configuration
@EnableTransactionManagement
public class SpringConfig {
@Bean
public TaskScheduler taskScheduler() {
return new ThreadPoolTaskScheduler();
}
}
Spring啟動應用程序在我的Main.class中啟動,並安排任務@PostConstruct
@SpringBootApplication
@ComponentScan("...")
@EntityScan("...")
@EnableJpaRepositories("... .repositories")
@EnableAutoConfiguration
@PropertySources(value = {@PropertySource("classpath:application.properties")})
public class Main {
private final static Logger LOGGER = LoggerFactory.getLogger(Main.class);
private static SpringApplication application = new SpringApplication(Main.class);
private TaskScheduler taskScheduler;
private AnalysisCleaningThread cleaningThread;
@Inject
public void setCleaningThread(AnalysisCleaningThread cleaningThread) {
this.cleaningThread = cleaningThread;
}
@Inject
public void setTaskScheduler(TaskScheduler taskScheduler) {
this.taskScheduler = taskScheduler;
}
public static void main(String[] args)
throws Exception {
try {
//Do some setup
application.run(args);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
}
@PostConstruct
public void init()
throws Exception {
//Do some setup as well
ScheduledFuture scheduledFuture = null;
LOGGER.info("********** Scheduling one time Cleaning Thread. Starting in 5 seconds **********");
Date nowPlus5Seconds = Date.from(LocalTime.now().plus(5, ChronoUnit.SECONDS).atDate(LocalDate.now()).atZone(ZoneId.systemDefault()).toInstant());
scheduledFuture = this.taskScheduler.schedule(this.cleaningThread, nowPlus5Seconds);
while (true) {
//Somehow blocks thread from running
if (scheduledFuture.isDone()) {
break;
}
Thread.sleep(2000);
}
//schedule next periodic thread
}
應用程序必須等待線程完成,因為它的任務是在意外的應用程序關閉后清理臟數據庫條目。 接下來的任務是獲取已清理的條目並再次處理它們。 清理線程實現如下:
@Named
@Singleton
public class AnalysisCleaningThread implements Runnable {
private static Logger LOGGER = LoggerFactory.getLogger(AnalysisCleaningThread.class);
private AnalysisService analysisService;
@Inject
public void setAnalysisService(AnalysisService analysisService) {
this.analysisService = analysisService;
}
@Override
public void run() {
List<Analysis> dirtyAnalyses = analysisService.findAllDirtyAnalyses();
if(dirtyAnalyses != null && dirtyAnalyses.size() > 0) {
LOGGER.info("Found " + dirtyAnalyses.size() + " dirty analyses. Cleaning... ");
for (Analysis currentAnalysis : dirtyAnalyses) {
//Reset AnalysisState so it is picked up by ProcessingThread on next run
currentAnalysis.setAnalysisState(AnalysisState.CREATED);
}
analysisService.saveAll(dirtyAnalyses);
} else {
LOGGER.info("No dirty analyses found.");
}
}
}
我在第一行run方法和第二行上設置了一個突破點。 如果我使用ScheduledFuture.get(),則調用第一行,然后調用JPA存儲庫方法,但它永遠不會返回...它不會在控制台中生成查詢...
如果我使用ScheduledFuture.isDone(),則根本不會調用run方法...
編輯:
所以我進一步挖掘了這個問題,這就是我發現它停止工作的地方:
synchronized(this.singletonObjects)
應用程序暫停並且永遠不會繼續 從我的觀點來看,似乎this.singletonObjects
目前正在使用,因此線程不能以某種方式繼續...
所以我已經做了很多研究,因為發生了這個問題,最后找到了解決我的罕見情況的方法。
我首先注意到的是,如果沒有future.get(), AnalysisCleaningThread
確實運行沒有任何問題,但是run方法花費了2秒鍾來執行第一行,所以我認為必須在后台進行一些事情。最終可以進行數據庫調用。
我在原始問題編輯中通過調試發現的是,應用程序在第93行的DefaultSingletonBeanRegistry.getSingleton
方法中的同步塊synchronized(this.singletonObjects)
處停止,因此必須持有該鎖定對象。 一旦調用DefaultSingletonBeanRegistry.getSingleton
的迭代方法將“main”作為參數“beanName”傳遞給getSingleton
,它就會在該行停止。
BTW該方法(或更好的方法鏈)被調用以獲得PlatformTransactionManager bean的實例以進行該服務(數據庫)調用。
我的第一個想法是,它必定是一個僵局。
根據我的理解,bean在其生命周期內仍然沒有最終准備好(仍然在其@PostConstruct init()
方法中)。 當spring嘗試獲取平台事務管理器的實例以便進行數據庫查詢時,應用程序會死鎖。 它實際上是死鎖,因為在迭代所有bean名稱以找到PlatformTansactionManager時,它還會嘗試解析當前正在等待的“main”bean,因為@PostConstruct方法中的future.get()。 因此它無法獲取實例並且永遠等待釋放鎖。
因為我不想將該代碼放在另一個類中,因為Main.class是我的入口點,所以我開始尋找一個鈎子,它在應用程序完全啟動后啟動任務。
我弄亂了@EventListener
,在我的情況下,它監聽ApplicationReadyEvent.class
和voilà,它有效。 這是我的代碼解決方案。
@SpringBootApplication
@ComponentScan("de. ... .analysis")
@EntityScan("de. ... .persistence")
@EnableJpaRepositories("de. ... .persistence.repositories")
@EnableAutoConfiguration
@PropertySources(value = {@PropertySource("classpath:application.properties")})
public class Main {
private final static Logger LOGGER = LoggerFactory.getLogger(Main.class);
private static SpringApplication application = new SpringApplication(Main.class);
private TaskScheduler taskScheduler;
private AnalysisProcessingThread processingThread;
private AnalysisCleaningThread cleaningThread;
@Inject
public void setProcessingThread(AnalysisProcessingThread processingThread) {
this.processingThread = processingThread;
}
@Inject
public void setCleaningThread(AnalysisCleaningThread cleaningThread) {
this.cleaningThread = cleaningThread;
}
@Inject
public void setTaskScheduler(TaskScheduler taskScheduler) {
this.taskScheduler = taskScheduler;
}
public static void main(String[] args)
throws Exception {
try {
//Do some setup
application.run(args);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
}
@PostConstruct
public void init() throws Exception {
//Do some other setup
}
@EventListener(ApplicationReadyEvent.class)
public void startAndScheduleTasks() {
ScheduledFuture scheduledFuture = null;
LOGGER.info("********** Scheduling one time Cleaning Thread. Starting in 5 seconds **********");
Date nowPlus5Seconds = Date.from(LocalTime.now().plus(5, ChronoUnit.SECONDS).atDate(LocalDate.now()).atZone(ZoneId.systemDefault()).toInstant());
scheduledFuture = this.taskScheduler.schedule(this.cleaningThread, nowPlus5Seconds);
try {
scheduledFuture.get();
} catch (InterruptedException | ExecutionException e) {
LOGGER.error("********** Cleaning Thread did not finish as expected! Stopping thread. Dirty analyses may still remain in database **********", e);
scheduledFuture.cancel(true);
}
}
}
從@PostConstruct
方法執行spring數據存儲庫調用 - 在極少數情況下 - 如果使用@PostConstruct
注釋的方法未在spring之前結束,則可以獲取PlatformTransactionManager
bean來執行spring數據存儲庫查詢。 無論是無限循環還是future.get()方法都無關緊要.... 只有當迭代所有已注冊的beanNames並最終調用DefaultSingletonBeanRegistry.getSingleton
以找到PlatformTransactionManager bean的方法調用帶有當前在@PostConstruct
方法中的bean名稱的getSingleton時,才會發生這種情況。 如果在此之前找到PlatformTransactionManager
,則不會發生。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.