如何在SpringWEB項目或者Springboot啓動時直接執行業務代碼(三種方式)

我的博客原文鏈接

前言

通常的我們的項目開發中,經常會遇到那種在服務一啓動就需要自動執行一些業務代碼的情況。比如將數據庫中的配置信息或者數據字典之類的緩存到redis,或者在服務啓動的時候將一些配置化的定時任務開起來。關於spring mvc或者springboot如何在項目啓動的時候就執行一些代碼,方法其實有很多,我這邊介紹一下我使用過的三種。

1、@PostConstruct 註解

從Java EE5規範開始,Servlet中增加了兩個影響Servlet生命週期的註解,@PostConstruct@PreDestroy,這兩個註解被用來修飾一個非靜態的void()方法。@PostConstruct會在所在類的構造函數執行之後執行,在init()方法執行之前執行。(@PreDestroy註解的方法會在這個類的destory()方法執行之後執行。)

  • 使用示例:在Spring容器加載之後,我需要啓動定時任務去做任務的處理(我的定時任務採用的是讀取數據庫配置的方式)。在這裏我使用@PostConstruct 指定了需要啓動的方法。
@Component // 注意 這裏必須有
public class StartAllJobInit {

    protected Logger logger = LoggerFactory.getLogger(getClass().getName());
    @Autowired
    JobInfoService jobInfoService;

    @Autowired
    JobTaskUtil jobTaskUtil;

    @PostConstruct // 構造函數之後執行
    public void init(){
        System.out.println("容器啓動後執行");
        startJob();
    }

    public void startJob() {
        List<JobInfoBO> list = jobInfoService.findList();
        for (JobInfoBO jobinfo :list) {
            try {
                if("0".equals(jobinfo.getStartWithrun())){
                    logger.info("任務{}未設置自動啓動。", jobinfo.getJobName());
                    jobInfoService.updateJobStatus(jobinfo.getId(), BasicsConstantManual.BASICS_SYS_JOB_STATUS_STOP);
                }else{
                    logger.info("任務{}設置了自動啓動。", jobinfo.getJobName());
                    jobTaskUtil.addOrUpdateJob(jobinfo);
                    jobInfoService.updateJobStatus(jobinfo.getId(), BasicsConstantManual.BASICS_SYS_JOB_STATUS_STARTING);
                }
            } catch (SchedulerException e) {
                logger.error("執行定時任務出錯,任務名稱 {} ", jobinfo.getJobName());
            }
        }
    }
}

2、實現CommandLineRunner接口並重寫run()方法

CommandLineRunner接口文檔描述如下:

/**
 * Interface used to indicate that a bean should <em>run</em> when it is contained within
 * a {@link SpringApplication}. Multiple {@link CommandLineRunner} beans can be defined
 * within the same application context and can be ordered using the {@link Ordered}
 * interface or {@link Order @Order} annotation.
 * <p>
 * If you need access to {@link ApplicationArguments} instead of the raw String array
 * consider using {@link ApplicationRunner}.
 *
 * @author Dave Syer
 * @see ApplicationRunner
 */
public interface CommandLineRunner {

	/**
	 * Callback used to run the bean.
	 * @param args incoming main method arguments
	 * @throws Exception on error
	 */
	void run(String... args) throws Exception;

}

如上所說:接口被用作加入Spring容器中時執行run(String… args)方法,通過命令行傳遞參數。SpringBoot在項目啓動後會遍歷所有實現CommandLineRunner的實體類並執行run方法,多個實現類可以並存並且根據order註解排序順序執行。這邊還有個ApplicationRunner接口,但是接收參數是使用的ApplicationArguments。這邊不再贅述。

同樣是啓動時執行定時任務,使用這種方式我的寫法如下:

@Component // 注意 這裏必須有
//@Order(2) 如果有多個類需要啓動後執行 order註解中的值爲啓動的順序
public class StartAllJobInit implements CommandLineRunner {

    protected Logger logger = LoggerFactory.getLogger(getClass().getName());
    @Autowired
    JobInfoService jobInfoService;

    @Autowired
    JobTaskUtil jobTaskUtil;

    @Override
    public void run(String... args) {
        List<JobInfoBO> list = jobInfoService.findList();
        for (JobInfoBO jobinfo :list) {
            try {
                if("0".equals(jobinfo.getStartWithrun())){
                    logger.info("任務{}未設置自動啓動。", jobinfo.getJobName());
                    jobInfoService.updateJobStatus(jobinfo.getId(), BasicsConstantManual.BASICS_SYS_JOB_STATUS_STOP);
                }else{
                    logger.info("任務{}設置了自動啓動。", jobinfo.getJobName());
                    jobTaskUtil.addOrUpdateJob(jobinfo);
                    jobInfoService.updateJobStatus(jobinfo.getId(), BasicsConstantManual.BASICS_SYS_JOB_STATUS_STARTING);
                }
            } catch (SchedulerException e) {
                logger.error("執行定時任務出錯,任務名稱 {} ", jobinfo.getJobName());
            }
        }
    }
}

3、使用ContextRefreshedEvent事件(上下文件刷新事件)

ContextRefreshedEvent 官方在接口上的doc說明

Event raised when an {@code ApplicationContext} gets initialized or refreshed.

ContextRefreshedEvent是Spring的ApplicationContextEvent一個實現,ContextRefreshedEvent 事件會在Spring容器初始化完成後以及刷新時觸發。

在這裏我需要在springboot程序啓動之後加載配置信息和字典信息到Redis緩存中去,我可以這樣寫:

@Component // 注意 這個也是必須有的註解 三種都需要 使spring掃描到這個類並交給它管理
public class InitRedisCache implements ApplicationListener<ContextRefreshedEvent> {
    static final Logger logger = LoggerFactory
            .getLogger(InitRedisCache.class);

    @Autowired
    private SysConfigService sysConfigService;

    @Autowired
    private SysDictService sysDictService;


    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        logger.info("-------加載配置信息 start-------");
        sysConfigService.loadConfigIntoRedis();
        logger.info("-------加載配置信息 end-------");

        logger.info("-------加載字典信息 start-------");
        sysDictService.loadDictIntoRedis();
        logger.info("-------加載字典信息 end-------");
    }
}

注意:這種方式在springmvc-spring的項目中使用的時候會出現執行兩次的情況。這種是因爲在加載spring和springmvc的時候會創建兩個容器,都會觸發這個事件的執行。這時候只需要在onApplicationEvent方法中判斷是否有父容器即可。

@Override  
  public void onApplicationEvent(ContextRefreshedEvent event) {  
      if(event.getApplicationContext().getParent() == null){//root application context 沒有parent,他就是老大.  
           //需要執行的邏輯代碼,當spring容器初始化完成後就會執行該方法。  
      }  
  }  

總結

以上,就是我在實際開發中常用的三種,在項目啓動時執行代碼的方式,開發者可以根據不同的使用情況選擇合適的方法去執行自己的業務邏輯。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章